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


738

Zastanawiam się tylko, czy ktoś z was używa Count(1)więcej Count(*)i czy istnieje zauważalna różnica w wydajności, czy też jest to zwyczajny nawyk, który pojawił się w przeszłości?

Konkretna baza danych to SQL Server 2005.


7
Nie wiem o SQL Server, ale w MySQL nie ma różnicy. Z drugiej strony COUNT (kolumna) jest inny
Greg

118
Nie prawda. COUNT (SomeColumn) zwróci tylko liczbę wierszy zawierających wartości inne niż null dla SomeColumn. COUNT (*) i COUNT („Foo”) zwrócą całkowitą liczbę wierszy w tabeli.
Steve Broberg,


4
Wow Steve i tutaj byłem 5 lat w TSQL bez znajomości count (*) vs Count (ColumnName). Dzięki
Harindaka,

3
Zwróć też uwagę na odpowiedzi na COUNT(*)vs COUNT(1)vs COUNT(pk)- co jest lepsze? . Jest też COUNT(*)vs COUNT(column-name)- co jest bardziej poprawne? . Mogą istnieć inne duplikaty.
Jonathan Leffler

Odpowiedzi:


598

Nie ma różnicy.

Powód:

Książki online mówią „ COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )

„1” jest wyrażeniem innym niż null: więc jest takie samo jak COUNT(*). Optymalizator rozpoznaje, czym jest: trywialny.

To samo co EXISTS (SELECT * ...lubEXISTS (SELECT 1 ...

Przykład:

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

To samo IO, ten sam plan, działa

Edytuj, sierpień 2011 r

Podobne pytanie na DBA.SE .

Edycja, grudzień 2011 r

COUNT(*)jest wymieniony szczególnie w ANSI-92 (poszukaj „ Scalar expressions 125”)

Walizka:

a) Jeśli podano COUNT (*), to wynikiem jest liczność T.

Oznacza to, że standard ANSI rozpoznaje to jako krwawienie oczywiste, co masz na myśli. COUNT(1)został zoptymalizowany przez dostawców RDBMS z powodu tego przesądu. W przeciwnym razie byłby oceniany zgodnie z ANSI

b) W przeciwnym razie niech TX będzie tabelą jednokolumnową, która jest wynikiem zastosowania <wyrażenie wartości> do każdego wiersza T i wyeliminowania wartości zerowych. Jeśli jedna lub więcej wartości zerowych zostanie wyeliminowanych, wówczas pojawia się warunek ukończenia: ostrzeżenie-


73

W SQL Server instrukcje te dają te same plany.

Wbrew powszechnej opinii, w Oracle też tak robią.

SYS_GUID() w Oracle jest dość intensywną funkcją obliczeniową.

W mojej testowej bazie danych t_evenznajduje się tabela z 1,000,000wierszami

To zapytanie:

SELECT  COUNT(SYS_GUID())
FROM    t_even

działa przez 48kilka sekund, ponieważ funkcja musi ocenić każde SYS_GUID()zwrócone, aby upewnić się, że nie jest to NULL.

Jednak to zapytanie:

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

działa przez 2kilka sekund, ponieważ nawet nie próbuje ocenić SYS_GUID()(pomimo *tego, że jest argumentem COUNT(*))


powinien ocenić SYS_GUID()przynajmniej (dokładnie dokładnie) raz, aby zapytanie podrzędne zwróciło wynik, prawda?
asgs

@asgs: dlaczego tak myślisz? Jak to COUNT(*)zależy od wartości SYS_GUID?
Quassnoi

teraz, kiedy pytasz, nie jestem pewien. Pomyślałem, że COUNT(*)aby uruchomić, potrzebuje tabeli, więc pod-zapytanie powinno działać jak jeden. W przeciwnym razie nie widzę sposobu na COUNT(*)zwrócenie znaczącej wartości
asgs

1
@asgs: zakładając, że wiesz, co robi mapmetoda, czy widzisz, jak te dwa wyrażenia: t_even.map(() => sys_guid()).lengthi t_even.lengthzawsze zwraca tę samą wartość? Optymalizator Oracle jest wystarczająco inteligentny, aby go zobaczyć i zoptymalizować mapczęść.
Quassnoi

1
@asgs dokładnie. Drobna korekta: lengthnie zależy od tego, z czego składa się kolekcja, tylko od liczby jej elementów. Jeśli ta liczba jest przechowywana w metadanych kolekcji (nie dotyczy to Oracle ani większości innych współczesnych RDBMS, ale jest tak w przypadku starego silnika pamięci MySQL, MyISAM), COUNT(*)wystarczy pobrać wartość z metadanych.
Quassnoi

65

Oczywiste jest, COUNT(*)i COUNT(1)będzie zawsze zwraca ten sam wynik. Dlatego, jeśli jeden byłby wolniejszy od drugiego, byłoby to skutecznie spowodowane błędem optymalizatora. Ponieważ obie formy są bardzo często używane w zapytaniach, nie ma sensu, aby DBMS pozwalał na usunięcie takiego błędu. Stąd przekonasz się, że wydajność obu formularzy jest (prawdopodobnie) identyczna we wszystkich głównych SQL DBMS.


Nie uważałbym tego za błąd, gdyby count (1) był wolniejszy niż count (*). Jeśli poprosisz dbms o wygenerowanie 1 i policzenie tych, które nie są zerowe, wtedy tak, sprowadza się to do liczby rekordów, ale nie możesz oczekiwać, że dbms wykryje każdą bzdurę, którą napiszesz i obejdzie ją dla ciebie.
Thorsten Kettner

1
Cóż, optymalizator ma na celu optymalizację, a dla zliczania należy wziąć pod uwagę tylko 2 przypadki: wyrażenie, które może być zerowe, wyrażenie, które nigdy nie będzie zerowe: liczba (1) wpada do tego ostatniego, więc DBMS nie musi „generuj” 1s, aby odpowiedzieć na pytanie. (BTW, nigdy nie użyłbym niczego oprócz liczenia (*), tylko ze względów estetycznych.)
Tony Andrews

46

Pracuję w zespole SQL Server i mam nadzieję, że wyjaśnię kilka punktów w tym wątku (nie widziałem tego wcześniej, więc przepraszam, że zespół inżynierów nie zrobił tego wcześniej).

Po pierwsze, nie ma różnicy między semantyczne select count(1) from tablevs. select count(*) from table. Zwracają te same wyniki we wszystkich przypadkach (i jest to błąd, jeśli nie). Jak zauważono w innych odpowiedziach, select count(column) from tablejest semantycznie inny i nie zawsze zwraca takie same wyniki jak count(*).

Po drugie, w odniesieniu do wydajności, w SQL Server (i SQL Azure) ważne są dwa aspekty: praca w czasie kompilacji i praca w czasie wykonywania. Czas pracy kompilacji to trywialnie niewielka ilość dodatkowej pracy w bieżącej implementacji. W niektórych przypadkach występuje rozszerzenie * do wszystkich kolumn, po czym następuje redukcja z powrotem do 1 kolumny wynikającej z tego, jak niektóre operacje wewnętrzne działają w wiązaniu i optymalizacji. Wątpię, aby pojawił się w jakimkolwiek mierzalnym teście i prawdopodobnie zgubiłby się w hałasie wszystkich innych rzeczy, które dzieją się pod przykryciem (takich jak automatyczne statystyki, xevent sesje, koszty magazynu zapytań, wyzwalacze itp.). To może kilka tysięcy dodatkowych instrukcji procesora. Więc, count (1) wykonuje odrobinę mniej pracy podczas kompilacji (co zwykle dzieje się raz, a plan jest buforowany w wielu kolejnych wykonaniach). Jeśli chodzi o czas realizacji, przy założeniu, że plany są takie same, nie powinno być żadnej mierzalnej różnicy. (Jeden z wcześniejszych przykładów pokazuje różnicę - najprawdopodobniej wynika to z innych czynników na komputerze, jeśli plan jest taki sam).

Co do tego, jak plan może potencjalnie być inny. Jest to bardzo mało prawdopodobne, ale jest to potencjalnie możliwe w architekturze obecnego optymalizatora. Optymalizator programu SQL Server działa jako program wyszukiwania (pomyśl: program komputerowy grający w szachy, przeszukujący różne alternatywy dla różnych części zapytania i szukający alternatywnych sposobów znalezienia najtańszego planu w rozsądnym czasie). To wyszukiwanie ma kilka ograniczeń dotyczących działania, dzięki czemu kompilacja zapytań kończy się w rozsądnym czasie. W przypadku zapytań wykraczających poza najbardziej trywialne są etapy wyszukiwania i dotyczą one transz zapytań w oparciu o koszt, jaki optymalizator uważa, że ​​zapytanie może zostać wykonane. Istnieją 3 główne fazy wyszukiwania, a każda faza może przebiegać bardziej agresywnie (kosztownie) heurystycznie, próbując znaleźć tańszy plan niż jakiekolwiek poprzednie rozwiązanie. Ostatecznie na końcu każdej fazy podejmowany jest proces decyzyjny, który ma na celu ustalenie, czy powinien zwrócić znaleziony dotąd plan, czy też powinien kontynuować wyszukiwanie. W tym procesie wykorzystano całkowity czas do tej pory w porównaniu z szacowanym kosztem najlepszego znalezionego planu. Tak więc na różnych komputerach z różnymi prędkościami procesorów możliwe (choć rzadkie) jest uzyskanie różnych planów z powodu przekroczenia limitu czasu we wcześniejszej fazie z planem w porównaniu z przejściem do następnej fazy wyszukiwania. Istnieje również kilka podobnych scenariuszy związanych z przekroczeniem limitu czasu w ostatniej fazie i potencjalnie brakiem pamięci w bardzo, bardzo drogich zapytaniach, które zajmują całą pamięć na komputerze (zwykle nie jest to problem w 64-bitach, ale był to większy problem z powrotem na serwerach 32-bitowych). Ostatecznie, jeśli otrzymasz inny plan, wydajność w czasie wykonywania może się różnić. Ja nie

Net-net: Proszę użyć dowolnej z dwóch opcji, ponieważ nic z tego nie ma znaczenia w żadnej praktycznej formie. (Szczerze mówiąc, istnieją znacznie większe czynniki, które wpływają na wydajność SQL poza tym tematem).

Mam nadzieję, że to pomoże. Napisałem rozdział w książce o tym, jak działa optymalizator, ale nie wiem, czy jest to właściwe, aby opublikować go tutaj (ponieważ wciąż otrzymuję niewielkie opłaty licencyjne). Zamiast więc zamieszczać link do wykładu, który wygłosiłem na SQLBits w Wielkiej Brytanii na temat tego, jak optymalizator działa na wysokim poziomie, aby można było zobaczyć bardziej szczegółowo różne główne fazy wyszukiwania, jeśli chcesz aby się o tym dowiedzieć. Oto link do filmu: https://sqlbits.com/Sessions/Event6/inside_the_sql_server_query_optimizer


2
wierzę, że 1również podlega tej samej ekspansji. Opieram to na testach perf tutaj tutaj stackoverflow.com/questions/1597442/... patrz także przykład w tej odpowiedzi na zapytanie, który 1nieoczekiwanie kończy się niepowodzeniem, gdy w grę wchodzą uprawnienia na poziomie kolumny
Martin Smith

21

W standardzie SQL-92 w COUNT(*)szczególności oznacza „liczność wyrażenia tabelowego” (może to być tabela podstawowa, „WIDOK, tabela pochodna, CTE itp.).

Myślę, że pomysł był taki, że COUNT(*)łatwo go przeanalizować. Użycie dowolnego innego wyrażenia wymaga, aby parser upewnił się, że nie odwołuje się do żadnych kolumn ( COUNT('a')gdzie ajest literał, a COUNT(a)gdzie akolumna może dawać różne wyniki).

Podobnie, COUNT(*)może być łatwo wybrany przez kodera znającego standardy SQL, co jest przydatną umiejętnością podczas pracy z ofertą SQL więcej niż jednego dostawcy.

Ponadto, w szczególnym przypadku SELECT COUNT(*) FROM MyPersistedTable;, myślenie jest takie, że DBMS może przechowywać statystyki dotyczące liczności tabeli.

Dlatego, ponieważ COUNT(1)i COUNT(*)są semantycznie równoważne, używam COUNT(*).


1
Tekst SQL-92 połączony z moją odpowiedzią na DBA.SE: dba.stackexchange.com/questions/2511/...
gbn


12

Oczekiwałbym, że optymalizator upewni się, że nie ma prawdziwej różnicy poza dziwnymi przypadkami krawędzi.

Jak ze wszystkim, jedynym prawdziwym sposobem na stwierdzenie jest zmierzenie konkretnych przypadków.

To powiedziawszy, zawsze używałem COUNT(*).


Zgodnie z przyjętą odpowiedzią nie jest to prawdą w przypadku MS SQL - w rzeczywistości nie ma między nimi żadnej różnicy.
David Manheim

10

Gdy to pytanie wciąż się pojawia, oto jeszcze jedna odpowiedź. Mam nadzieję dodać tutaj coś dla początkujących, którzy zastanawiają się nad „najlepszą praktyką” tutaj.

SELECT COUNT(*) FROM something liczy rekordy, co jest łatwym zadaniem.

SELECT COUNT(1) FROM something pobiera 1 na rekord i zlicza te, które nie są zerowe, co w zasadzie zlicza rekordy, tylko bardziej skomplikowane.

Powiedziawszy to: Dobra dbms zauważa, że ​​druga instrukcja spowoduje taką samą liczbę jak pierwsza instrukcja i odpowiednio ją ponownie zinterpretuje, aby nie wykonywać niepotrzebnej pracy. Tak więc zwykle oba wyciągi skutkują tym samym planem wykonania i zajmują tyle samo czasu.

Jednak z punktu widzenia czytelności powinieneś użyć pierwszej instrukcji. Chcesz policzyć rekordy, więc policz rekordy, a nie wyrażenia. Użyj COUNT (wyrażenie) tylko wtedy, gdy chcesz policzyć niepuste wystąpienia czegoś.


8

Uruchomiłem szybki test na SQL Server 2012 na 8 GB pamięci RAM hyper-v. Możesz zobaczyć wyniki dla siebie. Podczas uruchamiania tych testów nie uruchomiłem żadnej innej aplikacji okienkowej oprócz SQL Server Management Studio.

Mój schemat tabeli:

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

Całkowita liczba rekordów w Employeetabeli: 178090131 (~ 178 milionów wierszy)

Pierwsze zapytanie:

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

Wynik pierwszego zapytania:

 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.

Drugie zapytanie:

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

Wynik drugiego zapytania:

 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.

Można zauważyć, że istnieje różnica 83 (= 70265 - 70182) milisekund, którą można łatwo przypisać dokładnemu stanowi systemu w czasie uruchamiania zapytań. Zrobiłem również jeden bieg, więc różnica ta stanie się bardziej dokładna, jeśli wykonam kilka biegów i wykonam uśrednianie. Jeśli dla tak ogromnego zestawu danych różnica będzie mniejsza niż 100 milisekund, wówczas możemy łatwo stwierdzić, że te dwa zapytania nie mają żadnej różnicy w wydajności wykazywanej przez silnik SQL Server.

Uwaga : RAM osiąga prawie 100% zużycie w obu biegach. Zrestartowałem usługę SQL Server przed uruchomieniem obu uruchomień.


7
SET STATISTICS TIME ON

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

Czasy wykonania programu SQL Server:
czas procesora = 31 ms, czas, który upłynął = 36 ms.

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

Czasy wykonania programu SQL Server:
czas procesora = 46 ms, czas, który upłynął = 37 ms.

Uruchomiłem to setki razy, za każdym razem czyszcząc pamięć podręczną. Rezultaty zmieniają się od czasu do czasu, gdy obciążenie serwera jest różne, ale prawie zawsze count(*)ma wyższy czas procesora.


14
Nie mogę tego odtworzyć. count(*)i count(1)zwracam wyniki w ciągu kilku ms od siebie, nawet licząc tabelę z 4,5 milionami wierszy, w mojej instancji SQL 2008.
Jeff Atwood

2
Czasami w niektórych systemach instrukcja uruchamiana jako pierwsza zawsze działa szybciej ... czy losowo ustawiłeś kolejność uruchamiania?
JosephDoggie,

@JosephDoggie należy zawsze ponownie uruchomić usługę SQL Server przed uruchomieniem każdego zapytania podczas wykonywania takich pomiarów / statystyk. Po uruchomieniu usługi SQL Server każde uruchomienie staje się całkowicie niezależne i dlatego kolejność zapytań nie powinna mieć znaczenia. Z drugiej strony, jeśli nie zrestartujesz usługi SQL Server, a silnik wykona jakieś buforowanie planów wykonania, zapytanie uruchamiane później powinno być uruchamiane szybciej, a nie pierwsze.
RBT

Czasy wykonania muszą zawierać dokładne plany zapytań podczas dokonywania porównań. Jeśli są różne (powiedzmy, agregacja skrótu vs. sort + agregacja strumienia), wyniki nie są porównywalne. Zachęcam więc do ostrożnego wyciągania wniosków bez dodatkowych danych.
Conor Cunningham MSFT

3

Jest artykuł pokazujący, że COUNT(1)na Oracle jest tylko pseudonim COUNT(*), z dowodem na to.

Zacytuję niektóre części:

Istnieje część oprogramowania bazy danych o nazwie „Optymalizator”, która jest zdefiniowana w oficjalnej dokumentacji jako „Wbudowane oprogramowanie bazy danych, które określa najbardziej efektywny sposób wykonania instrukcji SQL”.

Jeden ze składników optymalizatora nazywa się „transformatorem”, którego rolą jest określenie, czy korzystne jest przepisanie oryginalnej instrukcji SQL na semantycznie równoważną instrukcję SQL, która mogłaby być bardziej wydajna.

Czy chcesz zobaczyć, co robi optymalizator, pisząc zapytanie przy użyciu COUNT (1)?

Z użytkownik z ALTER SESSIONuprawnieniami, można umieścić tracefile_identifier, umożliwiają śledzenie Optimizer i uruchomić COUNT(1)wybierz, jak: SELECT /* test-1 */ COUNT(1) FROM employees;.

Następnie musisz zlokalizować pliki śledzenia, co można zrobić SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';. Później w pliku znajdziesz:

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

Jak widać, jest to tylko alias COUNT(*).

Kolejny ważny komentarz: COUNT(*)było naprawdę szybciej dwie dekady temu na Oracle, przed Oracle 7.3:

Count (1) został przepisany w count (*) od 7.3, ponieważ Oracle lubi automatyczne dostrajanie mitycznych instrukcji. We wcześniejszej wersji Oracle7 oracle musiał oceniać (1) dla każdego wiersza jako funkcję, zanim istniały DETERMINISTIC i NON-DETERMINISTIC.

Dwie dekady temu liczba (*) była szybsza

W przypadku innych baz danych, takich jak Sql Server, należy je zbadać indywidualnie dla każdej z nich.

Wiem, że to pytanie jest specyficzne dla Sql Server, ale inne pytania dotyczące SO na ten sam temat, bez wzmianki o bazie danych, zostały zamknięte i oznaczone jako duplikaty tej odpowiedzi.


1

We wszystkich RDBMS dwa sposoby liczenia są równoważne pod względem wyników, jakie dają. Jeśli chodzi o wydajność, nie zauważyłem żadnej różnicy wydajności w SQL Server, ale warto zauważyć, że niektóre RDBMS, np. PostgreSQL 11, mają mniej optymalne implementacje, COUNT(1)ponieważ sprawdzają nullability wyrażenia argumentu, co można zobaczyć w tym poście .

Znalazłem 10% różnicy wydajności dla 1M wierszy podczas działania:

-- Faster
SELECT COUNT(*) FROM t;

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

0

COUNT (1) nie różni się zasadniczo od COUNT (*), jeśli w ogóle. Jeśli chodzi o COUNTING NULLable COLUMN, może to być proste, aby pokazać różnice między COUNT (*) i COUNT (<some col>) -

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.