Count (*) vs Count (1) - SQL Server


738

Просто цікаво, чи хтось із вас користується Count(1)над цим, Count(*)і чи є помітна різниця в продуктивності, чи це лише звична звичка, яку висунули з минулих днів?

Конкретна база даних є SQL Server 2005.


7
Не знаєте про SQL Server, але в MySQL немає різниці. COUNT (стовпець) з іншого боку
Грег

118
Неправда. COUNT (SomeColumn) поверне лише кількість рядків, що містять ненульові значення для SomeColumn. COUNT (*) та COUNT ('Foo') повернуть загальну кількість рядків у таблиці.
Стів Броберг


4
Вау Стів, і ось я 5 років працював у TSQL, не знаючи count (*) проти Count (ColumnName). Дякую
Harindaka

3
Зверніть увагу також на відповіді COUNT(*)vs COUNT(1)vs COUNT(pk)- що краще? . Там також COUNT(*)проти COUNT(column-name)- що правильніше? . Цілком можуть бути й інші дублікати.
Джонатан Леффлер

Відповіді:


598

Різниці немає.

Причина:

Книги в режимі он-лайн кажуть " COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )"

"1" - ненульовий вираз: значить, це те саме, що COUNT(*). Оптимізатор розпізнає його за тим, що він є: тривіальним.

Те саме, що EXISTS (SELECT * ...абоEXISTS (SELECT 1 ...

Приклад:

SELECT COUNT(1) FROM dbo.tab800krows
SELECT COUNT(1),FKID FROM dbo.tab800krows GROUP BY FKID

SELECT COUNT(*) FROM dbo.tab800krows
SELECT COUNT(*),FKID FROM dbo.tab800krows GROUP BY FKID

Той же IO, той же план, роботи

Редагувати, серпень 2011 р

Схожий питання на DBA.SE .

Редагувати, грудень 2011 р

COUNT(*)конкретно згадується в ANSI-92 (шукайте " Scalar expressions 125")

Справа:

а) Якщо вказано COUNT (*), то результат - кардинальність T.

Тобто стандарт ANSI визнає це кровотечею очевидним, що ви маєте на увазі. COUNT(1)були оптимізовані виробниками RDBMS через це забобони. В іншому випадку це буде оцінено відповідно до ANSI

b) В іншому випадку нехай TX - це стовпчикова таблиця, яка є результатом застосування <значення виразу> до кожного рядка T та усунення нульових значень. Якщо одне або декілька нульових значень усунене, тоді підвищується умова завершення:


73

У SQL Server ці твердження дають однакові плани.

Всупереч поширеній думці, в Oracle вони теж роблять.

SYS_GUID() в Oracle досить обчислювальна функція.

У моїй тестовій базі даних t_evenє таблиця з 1,000,000рядками

Цей запит:

SELECT  COUNT(SYS_GUID())
FROM    t_even

працює протягом 48секунд, оскільки функція повинна оцінювати кожен SYS_GUID()повертається, щоб переконатися, що це не a NULL.

Однак цей запит:

SELECT  COUNT(*)
FROM    (
        SELECT  SYS_GUID()
        FROM    t_even
        )

працює лише 2секунди, оскільки навіть не намагається оцінити SYS_GUID()(незважаючи *на аргументи COUNT(*))


вона повинна оцінити SYS_GUID()хоча б (я маю на увазі точно) один раз, щоб підзапрос повертав результат, правда?
асгс

@asgs: чому ти так вважаєш? Як COUNT(*)залежить значення SYS_GUID?
Quassnoi

тепер, коли ви запитаєте, я не впевнений. Я думав, що для COUNT(*)запуску потрібна таблиця, тому підзапит повинен діяти як один. Інакше я не бачу способу COUNT(*)повернути змістовне значення
asgs

1
@asgs: якщо припустити, що ви знаєте, що робить mapметод, чи бачите ви, як ці два вирази: t_even.map(() => sys_guid()).lengthі t_even.lengthзавжди повертають одне і те ж значення? Оптимізатор Oracle досить розумний, щоб побачити його та оптимізувати mapдеталь.
Quassnoi

1
@asgs саме. Просто незначна корекція: lengthне зовсім залежить від того, з чого складається колекція, лише від кількості її елементів. Якщо це число зберігається у метаданих колекції (це не стосується Oracle або більшості інших сучасних RDBMS, але це стосується старого механізму зберігання даних MySQL, MyISAM), тоді COUNT(*)просто потрібно взяти значення з метаданих.
Quassnoi

65

Ясна річ, COUNT(*)і завждиCOUNT(1) буде повертати однаковий результат. Тому, якщо один був повільнішим, ніж інший, це було б ефективно через помилку оптимізатора. Оскільки обидві форми дуже часто використовуються в запитах, СУБД не має сенсу дозволяти такій помилці залишатись невиправленою. Отже, ви побачите, що продуктивність обох форм (ймовірно) однакова у всіх основних SQL СУБД.


Я б не вважав це помилкою, якщо count (1) був повільнішим, ніж count (*). Якщо ви попросите dbms згенерувати 1 і порахувати ті, які не є нульовими, тоді так, це зводиться до числа записів, але ви не можете очікувати, що dbms виявить кожну дурницю, яку ви пишете, і обійде її для вас.
Торстен Кеттнер

1
Ну, оптимізатор призначений для оптимізації, і для підрахунку необхідно враховувати лише два випадки: вираз, який може бути нульовим, вираз, який ніколи не буде нульовим: count (1) потрапляє в останнє, тому немає необхідності в СУБД, щоб "генерувати" 1s, щоб відповісти на питання. (До речі, я б ніколи нічого не використовував, окрім підрахунку (*), лише з естетичних причин.)
Тоні Ендрюс

46

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

По- перше, не існує семантична різниця між select count(1) from tableVS. select count(*) from table. Вони повертають однакові результати у всіх випадках (і це помилка, якщо ні). Як зазначається в інших відповідях, select count(column) from tableсемантично відрізняється і не завжди повертає ті самі результати, що і count(*).

По-друге, що стосується продуктивності, то в SQL Server (і SQL Azure) важливі два аспекти: робота в компіляції та час роботи. Час роботи на компіляцію - це тривіально невелика кількість додаткової роботи в поточній реалізації. У деяких випадках відбувається розширення * на всі стовпці з подальшим скороченням до 1 стовпця, що виводиться через те, як деякі внутрішні операції працюють у прив'язці та оптимізації. Я сумніваюся, що він виявиться в будь-якому вимірюваному тесті, і він, швидше за все, загубиться в шумі всіх інших речей, що трапляються під кришками (таких як автоматична статистика, сеанси xevent, накладні сховища запитів, тригери тощо). Це, можливо, кілька тисяч додаткових інструкцій щодо процесора. Тому, count (1) робить трохи менше роботи під час компіляції (що, як правило, трапляється один раз, і план кешується через декілька наступних виконань). Під час виконання, якщо припустити, що плани є однаковими, не повинно бути ніякої вимірюваної різниці. (Один з попередніх прикладів показує різницю - це, швидше за все, пов'язано з іншими факторами на машині, якщо план однаковий).

Щодо того, як план потенційно може бути різним. Це вкрай малоймовірно, але це можливо в архітектурі поточного оптимізатора. Оптимізатор SQL Server працює як програма пошуку (подумайте: комп’ютерна програма, що грає в шахи, шукаючи різні альтернативи для різних частин запиту, і витрачає альтернативи, щоб знайти найдешевший план у розумні строки). Цей пошук має кілька обмежень щодо того, як він працює, щоб тримати завершення складання запитів у розумні строки. Для запитів, які виходять за межі найтривіальніших, існують фази пошуку, і вони мають справу з траншами запитів, виходячи з того, наскільки оптимізатор вважає, що запит може бути виконаний. Існує 3 основні фази пошуку, і кожна фаза може виконувати більш агресивну (дорогу) евристику, намагаючись знайти дешевший план, ніж будь-яке попереднє рішення. Зрештою, в кінці кожної фази відбувається процес прийняття рішень, який намагається визначити, чи повинен він повертати знайдений досі план чи слід продовжувати шукати. Цей процес використовує загальний час, витрачений на даний момент, відповідно до розрахункової вартості найкращого плану, знайденого до цього часу. Так, на різних машинах з різною швидкістю процесорів можна (хоч і рідко) отримувати різні плани за рахунок вичерпання часу на більш ранній фазі з планом порівняно з продовженням наступної фази пошуку. Існує також кілька подібних сценаріїв, пов’язаних із тимчасовим закінченням останньої фази та потенційним вичерпанням пам’яті на дуже, дуже дорогі запити, які споживають всю пам'ять на машині (як правило, це не проблема для 64-розрядних, але це викликало більшу проблему назад на 32-бітних серверах). Зрештою, якщо ви отримаєте інший план, продуктивність під час виконання буде відрізнятися. Я не '

Net-net: Будь ласка, використовуйте те, що ви хочете з двох, оскільки жодне з цих питань не має жодної практичної форми. (Є багато, набагато більших факторів, які впливають на продуктивність у SQL поза цією темою, якщо чесно).

Я сподіваюся, що це допомагає. Я написав розділ книги про те, як працює оптимізатор, але я не знаю, чи доцільно розмістити його тут (оскільки я все-таки отримую крихітні роялті від нього, я все-таки вірю). Тож замість публікації я опублікую посилання на розмову, яку я виклав у SQLBits у Великобританії про те, як оптимізатор працює на високому рівні, щоб ви могли детальніше ознайомитись з різними основними фазами пошуку, якщо хочете щоб дізнатися про це. Ось відео посилання: https://sqlbits.com/Sessions/Event6/inside_the_sql_server_query_optimizer


2
я вважаю, що 1також зазнає такого ж розширення. Я базую це на тестах парфуму тут stackoverflow.com/questions/1597442/… також бачу приклад у цій відповіді на запит із використанням 1несподіваного відмови, коли грають дозволи на рівні стовпців
Мартін Сміт,

21

У стандарті SQL-92 COUNT(*)конкретно означає "кардинальність вираження таблиці" (може бути базовою таблицею, `VIEW, похідною таблицею, CTE тощо).

Я думаю, ідея полягала в тому, що COUNT(*)легко розібратися. Використання будь-якого іншого виразу вимагає аналізатора, щоб переконатися, що він не посилається на жодні стовпці ( COUNT('a')де aє літералом і COUNT(a)де aстовпець може дати різні результати).

У цьому ж ключі COUNT(*)може бути легко обраний людський кодер, знайомий зі стандартами SQL, що є корисним навиком роботи з більш ніж однією пропозицією SQL постачальника.

Крім того, у спеціальному випадку SELECT COUNT(*) FROM MyPersistedTable;мислення полягає в тому, що СУБД, ймовірно, утримує статистичні дані щодо простоти таблиці.

Тому, оскільки COUNT(1)і COUNT(*)семантично рівнозначні, я використовую COUNT(*).


1
Текст SQL-92 пов'язаний з моєю відповіддю на DBA.SE: dba.stackexchange.com/questions/2511/…
1111


12

Я би сподівався, що оптимізатор переконається, що немає справжньої різниці за межами дивних випадків.

Як і все, єдиний реальний спосіб сказати - це виміряти ваші конкретні випадки.

Це казав, я завжди використовував COUNT(*).


Відповідно до прийнятої відповіді, це не вірно для MS SQL - різниці між ними фактично немає.
Девід Манхайм

10

Оскільки це запитання виникає знову і знову, ось ще одна відповідь. Я сподіваюся додати щось для початківців, які цікавляться "найкращою практикою" тут.

SELECT COUNT(*) FROM something підраховує записи, що є легким завданням.

SELECT COUNT(1) FROM something отримує 1 на запис і, ніж підраховує 1, які не є нульовими, а це, по суті, підрахунок записів, лише складніше.

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

Однак з точки зору читабельності ви повинні використовувати перше твердження. Ви хочете рахувати записи, тому рахуйте записи, а не вирази. Використовуйте COUNT (вираз) лише тоді, коли ви хочете рахувати ненулі випадки чогось.


8

Я провів швидкий тест на SQL Server 2012 на 8 ГБ оперативної пам’яті гіпер-v. Ви можете бачити результати самі. Під час виконання цих тестів я не запускав жодної іншої віконної програми, окрім програми SQL Server Management Studio.

Моя схема таблиці:

CREATE TABLE [dbo].[employee](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_employee] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Загальна кількість записів у Employeeтаблиці: 178090131 (~ 178 млн. Рядків)

Перший запит:

Set Statistics Time On
Go    
Select Count(*) From Employee
Go    
Set Statistics Time Off
Go

Результат першого запиту:

 SQL Server parse and compile time: 
 CPU time = 0 ms, elapsed time = 35 ms.

 (1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 10766 ms,  elapsed time = 70265 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Другий запит:

    Set Statistics Time On
    Go    
    Select Count(1) From Employee
    Go    
    Set Statistics Time Off
    Go

Результат другого запиту:

 SQL Server parse and compile time: 
   CPU time = 14 ms, elapsed time = 14 ms.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 11031 ms,  elapsed time = 70182 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

Ви можете помітити, що різниця становить 83 (= 70265 - 70182) мілісекунд, яку можна легко віднести до точного стану системи під час виконання запитів. Також я зробив один пробіг, тому ця різниця стане більш точною, якщо я виконаю кілька пробіжок і проведу деяке усереднення. Якщо для такого величезного набору даних різниця досягає менше 100 мілісекунд, то ми можемо легко зробити висновок, що два запити не мають різниці в продуктивності, яку демонструє система SQL Server Engine.

Примітка . Оперативна пам’ять досягає майже 100% використання в обох прогонах Я перезапустив службу SQL Server перед тим, як запустити обидва запуски.


7
SET STATISTICS TIME ON

select count(1) from MyTable (nolock) -- table containing 1 million records. 

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

select count(*) from MyTable (nolock) -- table containing 1 million records. 

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

Я проводив це сотні разів, очищаючи кеш-пам'ять щоразу. Результати час від часу змінюються, оскільки завантаження сервера змінюється, але майже завжди count(*)має більший час процесора.


14
Я не можу це відтворити. count(*)і count(1)повертати результати протягом декількох мс один від одного, навіть при підрахунку таблиці з 4,5 мільйонами рядків у моєму екземплярі SQL 2008.
Джефф Етвуд

2
Іноді в деяких системах оператор, що виконується спочатку, завжди працює швидше ... Ви рандомізували порядок їх виконання?
ДжозефДоггі

@JosephDoggie завжди слід перезапустити сервіс SQL Server перед тим, як запускати кожен запит під час проведення таких вимірювань / статистики. Коли ви тільки що запустили сервіс SQL Server, то кожен запуск стає абсолютно незалежним, а отже, порядок запитів не має значення. З іншого боку, якщо ви не перезапускаєте службу SQL Server і двигун виконує якесь кешування планів виконання, то запит, який запускається пізніше, повинен запускатися швидше, а не перший.
RBT

Терміни виконання повинні дивитись на точні плани запитів при порівнянні. Якщо вони різні (скажімо, хеш-агрегат проти сортування + потік сукупності), то результати не порівнянні. Отже, я закликаю обережно робити тут висновки без додаткових даних.
Conor Cunningham MSFT

3

Існує стаття показує , що COUNT(1)на Oracle це просто псевдонім COUNT(*), з доказом про це.

Я цитую деякі частини:

Є частина програмного забезпечення бази даних, яка називається "Оптимізатор", яка в офіційній документації визначається як "Вбудоване програмне забезпечення бази даних, яке визначає найефективніший спосіб виконання оператора SQL".

Один із компонентів оптимізатора називається "трансформатор", роль якого полягає у визначенні того, чи вигідно переписати оригінальний оператор SQL на семантично еквівалентний оператор SQL, який міг би бути більш ефективним.

Чи хотіли б ви побачити, що робить оптимізатор, коли ви пишете запит за допомогою COUNT (1)?

З користувачем з ALTER SESSIONпривілеєм ви можете поставити tracefile_identifier, увімкнути відстеження оптимізатора і запустити COUNT(1)вибір, наприклад:SELECT /* test-1 */ COUNT(1) FROM employees; .

Після цього потрібно локалізувати файли трас, що можна зробити SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';. Пізніше у файлі ви знайдете:

SELECT COUNT(*) COUNT(1)” FROM COURSE”.”EMPLOYEES EMPLOYEES

Як бачите, це лише псевдонім для COUNT(*) .

Ще один важливий коментар: два десятиліття тому на Oracle, перш ніж Oracle 7.3, COUNT(*)було дійсно швидше :

Count (1) переписаний у count (*) з 7.3, тому що Oracle любить автоматично налаштовувати міфічні висловлювання. У попередньому Oracle7, oracle повинен був оцінювати (1) для кожного ряду, як функцію, перш ніж існували DETERMINISTIC і NONDETERMINISTIC.

Тож два десятиліття тому підрахунок (*) був швидшим

Для інших баз даних, як Sql Server, її слід досліджувати індивідуально для кожної з них.

Я знаю, що це питання є специфічним для Sql Server, але інші запитання щодо SO щодо тієї ж теми, без згадки про базу даних, були закриті та позначені як дублюються з цієї відповіді.


1

У всіх RDBMS два способи підрахунку є рівнозначними з точки зору того, який результат вони дають. Щодо продуктивності, то я не спостерігав різниці в продуктивності в SQL Server, але, можливо, варто зазначити, що деякі RDBMS, наприклад PostgreSQL 11, мають менш оптимальні реалізації, COUNT(1)оскільки вони перевіряють на нікчемність вираження аргументу, як це можна побачити в цій публікації .

Я знайшов 10% різниці в продуктивності для 1М рядків під час запуску:

-- Faster
SELECT COUNT(*) FROM t;

-- 10% slower
SELECT COUNT(1) FROM t;

0

COUNT (1) взагалі істотно не відрізняється від COUNT (*). Що стосується питання КОНТУРНУВАННЯ НЕЗАЄМНИХ КОЛІЙ, це може бути просто продемонстровано різниці між COUNT (*) та COUNT (<деяким колом>) -

USE tempdb;
GO

IF OBJECT_ID( N'dbo.Blitzen', N'U') IS NOT NULL DROP TABLE dbo.Blitzen;
GO

CREATE TABLE dbo.Blitzen (ID INT NULL, Somelala CHAR(1) NULL);

INSERT dbo.Blitzen SELECT 1, 'A';
INSERT dbo.Blitzen SELECT NULL, NULL;
INSERT dbo.Blitzen SELECT NULL, 'A';
INSERT dbo.Blitzen SELECT 1, NULL;

SELECT COUNT(*), COUNT(1), COUNT(ID), COUNT(Somelala) FROM dbo.Blitzen;
GO

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