Які різні способи замінити ISNULL () в пункті WHERE, який використовує лише буквальні значення?


55

Що це не про:

Це не питання щодо всіх запитів на вилов, які приймають користувацькі введення або використовують змінні.

Це суворо щодо запитів, де ISNULL()використовується в WHEREпункті заміни NULLзначень канарним значенням для порівняння з предикатом, та різних способів переписати ці запити, щоб вони були SARGable у SQL Server.

Чому ти не маєш місця там?

Наш приклад запиту відповідає локальній копії бази даних переповнення стека на SQL Server 2016 та шукає користувачів NULLвіком або віком <18 років.

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

План запитів показує сканування досить продуманого некластеризованого індексу.

Горіхи

Оператор сканування показує (завдяки доповненням до фактичного плану виконання XML у останніх версіях SQL Server), що ми читаємо кожен рядок, що смердить.

Горіхи

Загалом ми робимо 9157 зчитування і використовуємо близько половини секунди процесорного часу:

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 485 ms,  elapsed time = 483 ms.

Питання: Які способи переписати цей запит, щоб зробити його більш ефективним і, можливо, навіть SARGable?

Не соромтеся пропонувати інші пропозиції. Я не думаю , що моя відповідь обов'язково відповідь, і є достатньо розумних людей там придумати альтернативні варіанти , які можуть бути краще.

Якщо ви хочете пограти на власному комп’ютері, перейдіть сюди, щоб завантажити базу даних SO .

Дякую!

Відповіді:


57

Розділ відповідей

Існують різні способи переписати це за допомогою різних конструкцій T-SQL. Ми розглянемо плюси і мінуси і зробимо загальне порівняння нижче.

Перший вгору : ВикористанняOR

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

Використання ORдає нам більш ефективний план пошуку, який читає потрібну кількість рядків, однак він додає те, що технічний світ викликає a whole mess of malarkeyу плані запитів.

Горіхи

Також зауважте, що Seek тут виконується двічі, що дійсно повинно бути очевиднішим із графічного оператора:

Горіхи

Table 'Users'. Scan count 2, logical reads 8233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 469 ms,  elapsed time = 473 ms.

Друге вгору : Використання похідних таблиць з UNION ALL нашим запитом також можна переписати так

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Це дає однотипний план із набагато меншим рівнем макіяжу та більш очевидним ступенем чесності щодо того, скільки разів шукали (шукали?) Індекс.

Горіхи

Він виконує таку ж кількість читань (8233), що і ORзапит, але створює близько 100 мс відключеного процесора.

CPU time = 313 ms,  elapsed time = 315 ms.

Однак ви повинні бути дуже обережними, тому що якщо цей план спробує йти паралельно, дві окремі COUNTоперації будуть серіалізовані, оскільки кожна з них вважається глобальною скалярною сукупністю. Якщо ми змусимо паралельний план за допомогою Trace Flag 8649, проблема стає очевидною.

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)
OPTION(QUERYTRACEON 8649);

Горіхи

Цього можна уникнути, трохи змінивши наш запит.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Тепер обидва вузли, що виконують Seek, повністю паралельні, поки ми не потрапимо в оператор конкатенації.

Горіхи

Що варто, цілком паралельна версія має певну користь. Ціною приблизно ще 100 читань та приблизно 90мм додаткового часу процесора час, що минає, скорочується до 93мс.

Table 'Users'. Scan count 12, logical reads 8317, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 500 ms,  elapsed time = 93 ms.

Що з CROSS APPLY? Жодна відповідь не завершена без магії CROSS APPLY!

На жаль, у нас виникає більше проблем COUNT.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Цей план жахливий. Такий план ви закінчуєте, коли з’являєтесь останнім днем ​​Святого Патріка. Хоча це паралельно, чомусь сканування PK / CX. Ew План має вартість 2198 баксів.

Горіхи

Table 'Users'. Scan count 7, logical reads 31676233, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 29532 ms,  elapsed time = 5828 ms.

Що є дивним вибором, адже якщо ми змусимо його використовувати некластеризований індекс, ціна значно знижується до 1798 доларів.

SELECT SUM(Records)
FROM dbo.Users AS u 
CROSS APPLY 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u2 WITH (INDEX(ix_Id_Age))
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

Гей, шукає! Перевірте вас там. Також зауважте, що з магією CROSS APPLYнам не потрібно нічого робити, щоб мати переважно повністю паралельний план.

Горіхи

Table 'Users'. Scan count 5277838, logical reads 31685303, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 27625 ms,  elapsed time = 4909 ms.

Хрестовий набір в кінцевому підсумку набагато краще без COUNTречей там.

SELECT SUM(Records)
FROM dbo.Users AS u
CROSS APPLY 
(
    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id
    AND u2.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u2
    WHERE u2.Id = u.Id 
    AND u2.Age IS NULL
) x (Records);

План виглядає добре, але читання та процесор не є покращенням.

Горіхи

Table 'Users'. Scan count 20, logical reads 17564, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Workfile'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 4844 ms,  elapsed time = 863 ms.

Переписування хреста застосовуватиметься для отриманих результатів приєднання в абсолютно все. Я не збираюсь повторно публікувати план запитів та інформацію про статистику - вони насправді не змінилися.

SELECT COUNT(u.Id)
FROM dbo.Users AS u
JOIN 
(
    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT u.Id
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x ON x.Id = u.Id;

Реляційна алгебра : Щоб бути ретельним, і щоб Джо Челко не переслідував мої мрії, нам потрібно хоча б спробувати деякі дивні реляційні речі. Ось нічого не йде!

Спроба с INTERSECT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   INTERSECT
                   SELECT u.Age WHERE u.Age IS NOT NULL );

Горіхи

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1094 ms,  elapsed time = 1090 ms.

І ось спроба с EXCEPT

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18
                   EXCEPT
                   SELECT u.Age WHERE u.Age IS NULL);

Горіхи

Table 'Users'. Scan count 7, logical reads 9247, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 2126 ms,  elapsed time = 376 ms.

Можливо, є й інші способи їх написання, але я залишу це людям, які, можливо, використовують EXCEPTі INTERSECTчастіше, ніж я.

Якщо вам дійсно потрібен підрахунок, який я використовую COUNTв своїх запитах як скорочення (читайте: я занадто лінивий, щоб іноді придумати більше залучених сценаріїв). Якщо вам просто потрібен підрахунок, ви можете використовувати CASEвираз, щоб зробити приблизно те ж саме.

SELECT SUM(CASE WHEN u.Age < 18 THEN 1
                WHEN u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

SELECT SUM(CASE WHEN u.Age < 18 OR u.Age IS NULL THEN 1
                ELSE 0 END) 
FROM dbo.Users AS u

Вони обидва отримують однаковий план і мають однакові характеристики процесора та зчитування.

Горіхи

Table 'Users'. Scan count 1, logical reads 9157, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 719 ms,  elapsed time = 719 ms.

Переможець? У моїх тестах форсований паралельний план із SUM над отриманою таблицею найкращим чином. І так, багатьом із цих запитів можна було допомогти, додавши пару відфільтрованих індексів для обліку обох предикатів, але я хотів залишити експерименти іншим.

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Дякую!


1
Ці NOT EXISTS ( INTERSECT / EXCEPT )запити можуть працювати без INTERSECT / EXCEPTчастин: WHERE NOT EXISTS ( SELECT u.Age WHERE u.Age >= 18 );Інший спосіб - який використовує EXCEPT: SELECT COUNT(*) FROM (SELECT UserID FROM dbo.Users EXCEPT SELECT UserID FROM dbo.Users WHERE u.Age >= 18) AS u ; (де Ідентіфікатор_пользователя є PK або будь-який унікальний не нульовий стовпець (s)).
ypercubeᵀᴹ

Це тестували? SELECT result = (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age IS NULL) ;Вибачте, якщо я пропустив тестовані мільйони версій!
ypercubeᵀᴹ

@ ypercubeᵀᴹ ось план для цього. Він трохи інший, але має схожі характеристики з UNION ALLпланами (360 мс процесор, 11 к читання).
Ерік Дарлінг

Ей, Еріку, просто блукав світом sql і вискакував, щоб сказати "обчислену колонку", щоб просто роздратувати тебе. <3
тигель

17

Я не грав, щоб відновити базу даних в 110 ГБ лише для однієї таблиці, тому я створив власні дані . Розподіл за віком повинен відповідати тому, що є у переповнюванні стека, але очевидно, що сама таблиця не збігається. Я не думаю, що це занадто велика проблема, оскільки запити все одно потраплять до індексів. Я тестую на комп'ютері з 4 процесорами за допомогою SQL Server 2016 SP1. Варто зазначити, що для запитів, які швидко закінчуються, важливо не включати власне план виконання. Це може досить уповільнити справи.

Я почав, переглянувши деякі рішення на відмінну відповідь Еріка. Для цього:

SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Я отримав такі результати від sys.dm_exec_sesions протягом 10 випробувань (запит, природно, для мене йшов паралельно):

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3532                 975          60830 
╚══════════╩════════════════════╩═══════════════╝

Запит, який працював краще для Еріка, на моїй машині справді гірший:

SELECT SUM(Records)
FROM 
(
    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT 1
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records)   
OPTION(QUERYTRACEON 8649);

Результати 10 випробувань:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     5704                1636          60850 
╚══════════╩════════════════════╩═══════════════╝

Я не відразу можу пояснити, чому це так погано, але незрозуміло, чому ми хочемо змусити майже кожного оператора в плані запитів йти паралельно. У початковому плані у нас є серіальна зона, яка знаходить усі рядки AGE < 18. Є лише кілька тисяч рядів. На своїй машині я отримую 9 логічних зчитувань для цієї частини запиту та 9 мс повідомленого часу процесора та минулого часу. Існує також серіальна зона для глобальної сукупності для рядків з, AGE IS NULLале це обробляє лише один ряд на DOP. На моїй машині це всього чотири ряди.

Моє вирішення полягає в тому, що найважливіше оптимізувати частину запиту, яка знаходить рядки з символом " NULLfor", Ageоскільки таких рядків є мільйони. Мені не вдалося створити індекс із меншою кількістю сторінок, які охоплювали дані, ніж прості сторінки, стиснуті на стовпчику. Я припускаю, що мінімальний розмір індексу на рядок або що багато індексу простору не уникнути за допомогою трюків, які я спробував. Отже, якщо ми застрягли приблизно з однаковою кількістю логічних читань для отримання даних, то єдиний спосіб зробити це швидше - зробити запит паралельнішим, але це потрібно зробити іншим чином, ніж запит Еріка, який використовував TF 8649. У запиті вище ми маємо відношення 3,62 до часу процесора до минулого часу, що досить добре. Ідеальним було б співвідношення 4,0 на моїй машині.

Однією з можливих областей удосконалення є розподіл роботи більш рівномірно між нитками. На скріншоті нижче ми бачимо, що один з моїх процесорів вирішив зробити невелику перерву:

ледача нитка

Сканування покажчиків - один з небагатьох операторів, який можна реалізовувати паралельно, і ми не можемо зробити нічого щодо того, як рядки розподіляються на потоки. У цьому є і елемент шансу, але досить послідовно я бачив одну недоопрацьовану нитку. Один із способів обійти це - зробити паралелізм важким шляхом: на внутрішній частині вкладеної петлі з'єднати. Все, що знаходиться на внутрішній частині вкладеного циклу, буде реалізовано послідовно, але багато послідовних потоків можуть працювати одночасно. Поки ми отримуємо сприятливий паралельний метод розподілу (наприклад, кругла робота), ми можемо точно контролювати, скільки рядків надсилається до кожної нитки.

Я виконую запити з DOP 4, тому мені потрібно рівномірно розділити NULLрядки таблиці на чотири відра. Один із способів зробити це - створити купу індексів на обчислених стовпцях:

ALTER TABLE dbo.Users
ADD Compute_bucket_0 AS (CASE WHEN Age IS NULL AND Id % 4 = 0 THEN 1 ELSE NULL END),
Compute_bucket_1 AS (CASE WHEN Age IS NULL AND Id % 4 = 1 THEN 1 ELSE NULL END),
Compute_bucket_2 AS (CASE WHEN Age IS NULL AND Id % 4 = 2 THEN 1 ELSE NULL END),
Compute_bucket_3 AS (CASE WHEN Age IS NULL AND Id % 4 = 3 THEN 1 ELSE NULL END);

CREATE INDEX IX_Compute_bucket_0 ON dbo.Users (Compute_bucket_0) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_1 ON dbo.Users (Compute_bucket_1) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_2 ON dbo.Users (Compute_bucket_2) WITH (DATA_COMPRESSION = PAGE);
CREATE INDEX IX_Compute_bucket_3 ON dbo.Users (Compute_bucket_3) WITH (DATA_COMPRESSION = PAGE);

Я не зовсім впевнений, чому чотири окремі індекси трохи швидше, ніж один індекс, але це те, що я знайшов у своєму тестуванні.

Щоб отримати паралельний план вкладеного циклу, я буду використовувати недокументований прапор трасування 8649 . Я також збираюся написати код трохи дивним чином, щоб заохотити оптимізатор не обробляти більше рядків, ніж потрібно. Нижче представлена ​​одна реалізація, яка, здається, працює добре:

SELECT SUM(t.cnt) + (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age < 18)
FROM 
(VALUES (0), (1), (2), (3)) v(x)
CROSS APPLY 
(
    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_0 = CASE WHEN v.x = 0 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_1 = CASE WHEN v.x = 1 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_2 = CASE WHEN v.x = 2 THEN 1 ELSE NULL END

    UNION ALL

    SELECT COUNT(*) cnt 
    FROM dbo.Users 
    WHERE Compute_bucket_3 = CASE WHEN v.x = 3 THEN 1 ELSE NULL END
) t
OPTION (QUERYTRACEON 8649);

Результати десяти випробувань:

╔══════════╦════════════════════╦═══════════════╗
 cpu_time  total_elapsed_time  logical_reads 
╠══════════╬════════════════════╬═══════════════╣
     3093                 803          62008 
╚══════════╩════════════════════╩═══════════════╝

З цим запитом у нас є процесор, який минув 3,85! Ми відгонили 17 мс від часу виконання, і для цього знадобилося лише 4 обчислені колонки та індекси! Кожен потік обробляє дуже близько до однакової кількості рядків, оскільки кожен індекс має дуже близьку до однакової кількості рядків, і кожен потік сканує лише один індекс:

добре розділена робота

На завершальній ноті ми також можемо натиснути просту кнопку та додати в Ageколонку некластеризований ІСН :

CREATE NONCLUSTERED COLUMNSTORE INDEX X_NCCI ON dbo.Users (Age);

Наступний запит закінчується через 3 мс на моїй машині:

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18 OR u.Age IS NULL;

Це буде важко перемогти.


7

Хоча у мене немає локальної копії бази даних переповнення стека, я зміг випробувати пару запитів. Моя думка полягала в тому, щоб отримати кількість користувачів із перегляду системного каталогу (на відміну від прямого отримання кількості рядків з нижньої таблиці). Потім отримайте кількість рядків, які (або, можливо, не відповідають) критеріям Еріка, і виконайте просту математику.

Я використовував Stack обмін дані Провідника (поряд з SET STATISTICS TIME ON;і SET STATISTICS IO ON;) для перевірки запитів. Для ознайомлення, ось деякі запити та статистика процесора / виводу:

ЗАПИТАННЯ 1

--Erik's query From initial question.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE ISNULL(u.Age, 17) < 18;

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 0 мс. (1 ряд (и) повернуто)

Таблиця "Користувачі". Підрахунок сканування 17, логічне зчитування 201567, фізичне зчитування 0, зчитування вперед-зчитування 2740, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-зчитування 0.

Часи виконання SQL Server: час процесора = 1829 мс, минулий час = 296 мс.

ЗАПИТАННЯ 2

--Erik's "OR" query.
SELECT COUNT(*)
FROM dbo.Users AS u
WHERE u.Age < 18
OR u.Age IS NULL;

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 0 мс. (1 ряд (и) повернуто)

Таблиця "Користувачі". Підрахунок сканування 17, логічне зчитування 201567, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0.

Часи виконання SQL Server: час процесора = 2500 мс, минулий час = 147 мс.

ЗАПИТАННЯ 3

--Erik's derived tables/UNION ALL query.
SELECT SUM(Records)
FROM 
(
    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age < 18

    UNION ALL

    SELECT COUNT(Id)
    FROM dbo.Users AS u
    WHERE u.Age IS NULL
) x (Records);

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 0 мс. (1 ряд (и) повернуто)

Таблиця "Користувачі". Кількість сканувань 34, логічне зчитування 403134, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування попереднє зчитування 0.

Часи виконання SQL Server: час процесора = 3156 мс, минулий час = 215 мс.

1-а спроба

Це було повільніше, ніж усі запити Еріка, які я тут перераховував ... принаймні з точки зору минулого часу.

SELECT SUM(p.Rows)  -
  (
    SELECT COUNT(*)
    FROM dbo.Users AS u
    WHERE u.Age >= 18
  ) 
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 0 мс. (1 ряд (и) повернуто)

Таблиця "Робочий стіл". Кількість сканувань 0, логічне зчитування 0, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0 зчитування 0. Таблиця 'sysrowsets'. Кількість сканувань 2, логічне зчитування 10, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0 зчитування 0. Таблиця 'sysschobjs'. Кількість сканувань 1, логічне зчитування 4, фізичне зчитування 0, зчитування вперед-0, логічне зчитування 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0. Таблиця "Користувачі". Кількість сканувань 1, логічне зчитування 201567, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0.

Часи виконання SQL Server: час процесора = 593 мс, минулий час = 598 мс.

2-а спроба

Тут я вибрав змінну для зберігання загальної кількості користувачів (замість підзапиту). Кількість сканувань зросла з 1 до 17 порівняно з першою спробою. Логічні читання залишилися колишніми. Однак минулий час значно впав.

DECLARE @Total INT;

SELECT @Total = SUM(p.Rows)
FROM sys.objects o
JOIN sys.partitions p
    ON p.object_id = o.object_id
WHERE p.index_id < 2
AND o.name = 'Users'
AND SCHEMA_NAME(o.schema_id) = 'dbo'
GROUP BY o.schema_id, o.name

SELECT @Total - COUNT(*)
FROM dbo.Users AS u
WHERE u.Age >= 18

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 0 мс. Таблиця "Робочий стіл". Кількість сканувань 0, логічне зчитування 0, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0 зчитування 0. Таблиця 'sysrowsets'. Кількість сканувань 2, логічне зчитування 10, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0 зчитування 0. Таблиця 'sysschobjs'. Кількість сканувань 1, логічне зчитування 4, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування попереднє зчитування 0.

Часи виконання SQL Server: час процесора = 0 мс, минулий час = 1 мс. (1 ряд (и) повернуто)

Таблиця "Користувачі". Підрахунок сканування 17, логічне зчитування 201567, фізичне зчитування 0, зчитування вперед-зчитування 0, логічне зчитування лобі 0, лобічне фізичне зчитування 0, лобічне зчитування вперед-0.

Часи виконання SQL Server: час процесора = 1471 мс, минулий час = 98 мс.

Інші примітки: DBCC TRACEON заборонено в Провіднику даних стека Exchange, як зазначено нижче:

Користувач 'STACKEXCHANGE \ svc_sede' не має дозволу на запуск DBCC TRACEON.


1
Вони, мабуть, не мають тих самих індексів, що і я, звідси і відмінності. І, хто знає? Можливо, мій домашній сервер на кращому обладнанні;) Хоча чудова відповідь!
Ерік Дарлінг

ви повинні використати наступний запит для своєї першої спроби (буде набагато швидше, оскільки він позбавляється більшої частини sys.objects-overhead): SELECT SUM(p.Rows) - (SELECT COUNT(*) FROM dbo.Users AS u WHERE u.Age >= 18 ) FROM sys.partitions p WHERE p.index_id < 2 AND p.object_id = OBJECT_ID('dbo.Users')
Томас Франц,

PS: майте на увазі, що індекси пам'яті In-Memory (NONCLUSTERED HASH) не мають індексу id = 0/1, як мав би загальний індекс купи / кластеризованих)
Thomas Franz

1

Використовувати змінні?

declare @int1 int = ( select count(*) from table_1 where bb <= 1 )
declare @int2 int = ( select count(*) from table_1 where bb is null )
select @int1 + @int2;

За коментарем можна пропустити змінні

SELECT (select count(*) from table_1 where bb <= 1) 
     + (select count(*) from table_1 where bb is null);

3
Також:SELECT (select count(*) from table_1 where bb <= 1) + (select count(*) from table_1 where bb is null);
ypercubeᵀᴹ

3
Ви можете спробувати це під час перевірки процесора та IO. Підказка: це те саме, що одна з відповідей Еріка.
Брент Озар

0

Добре використовуючи SET ANSI_NULLS OFF;

SET ANSI_NULLS OFF; 
SET STATISTICS TIME ON;
SET STATISTICS IO ON;

SELECT COUNT(*)
FROM dbo.Users AS u
WHERE age=NULL or age<18

Table 'Users'. Scan count 17, logical reads 201567

 SQL Server Execution Times:
 CPU time = 2344 ms,  elapsed time = 166 ms.

Це щось щойно спливе в моїй свідомості. Просто виконали це на https://data.stackexchange.com

Але не настільки ефективний, як @blitz_erik


0

Тривіальним рішенням є обчислення count (*) - count (вік> = 18):

SELECT
    (SELECT COUNT(*) FROM Users) -
    (SELECT COUNT(*) FROM Users WHERE Age >= 18);

Або:

SELECT COUNT(*)
     - COUNT(CASE WHEN Age >= 18)
FROM Users;

Результати тут

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.