SQL: Jak poprawnie sprawdzić, czy rekord istnieje

207

Podczas czytania dokumentacji związanej z tuningiem SQL znalazłem:

SELECT COUNT(*) :

  • Liczy liczbę wierszy.
  • Często jest niewłaściwie wykorzystywany do weryfikacji istnienia zapisu.

Czy to SELECT COUNT(*)naprawdę takie złe?

Jaki jest właściwy sposób sprawdzenia istnienia zapisu?

systempuntoout
źródło

Odpowiedzi:

252

Lepiej użyć jednej z następujących opcji:

-- Method 1.
SELECT 1
FROM table_name
WHERE unique_key = value;

-- Method 2.
SELECT COUNT(1)
FROM table_name
WHERE unique_key = value;

Pierwsza alternatywa nie powinna dać żadnego wyniku lub jednego wyniku, druga liczba powinna wynosić zero lub jeden.

Ile lat używasz dokumentacji? Chociaż przeczytałeś dobrą radę, większość optymalizatorów zapytań w ostatnich RDBMS optymalizuje SELECT COUNT(*), więc chociaż istnieje różnica w teorii (i starszych bazach danych), nie powinieneś zauważać żadnej różnicy w praktyce.

Martin Schapendonk
źródło
1
Wyjaśnię, że zamierzałem „unikalny klucz” z klauzulą ​​„klucz = wartość”, ale poza tym wciąż jestem za moją odpowiedzią.
Martin Schapendonk,
1
DOBRZE. Przy takim założeniu zapytanie zwróci tylko jeden lub zero rekordów. ALE: Pytanie nie ogranicza się do unikalnej kolumny. Ponadto: 2. liczba zapytań (1) jest równoważna liczbie (*) z praktycznego POV.
Martin Ba
1
Pytanie brzmi „jaki jest właściwy sposób weryfikacji istnienia rekordu A”. Zinterpretowałem to jako osobliwe, jak w: 1 płycie. Różnica między liczbą (*) a liczbą (1) jest już uwzględniona w mojej odpowiedzi. Wolę count (1), ponieważ nie zależy od konkretnej implementacji RDBMS.
Martin Schapendonk
192

Wolałbym wcale nie używać funkcji Count:

IF [NOT] EXISTS ( SELECT 1 FROM MyTable WHERE ... )
     <do smth>

Na przykład, jeśli chcesz sprawdzić, czy użytkownik istnieje przed wstawieniem go do bazy danych, zapytanie może wyglądać następująco:

IF NOT EXISTS ( SELECT 1 FROM Users WHERE FirstName = 'John' AND LastName = 'Smith' )
BEGIN
    INSERT INTO Users (FirstName, LastName) VALUES ('John', 'Smith')
END
Pavel Morshenyuk
źródło
Zasadniczo korzystamy z niego (weryfikujemy), kiedy chcesz coś zrobić, a następnie twoja odpowiedź jest bardziej kompletna.
Abner Escócio,
Warto wspomnieć, że używając T-SQL
Bronek
20

Możesz użyć:

SELECT 1 FROM MyTable WHERE <MyCondition>

Jeśli nie ma rekordu pasującego do warunku, wynikowy zestaw rekordów jest pusty.

Cătălin Pitiș
źródło
Miałeś na myśli TOP 1? -> (WYBIERZ TOP 1 Z MyTable GDZIE <MójWarunek>)
Jacob
6
Nie, miałem na myśli dokładnie „1”
Cătălin Pitiș
1
aby włączyć optymalizator zapytań, aby nawet wiedzieć, że nie będziesz czytać / potrzebować pozostałych zestawów danych, powinieneś podać WYBIERZ TOP 1 1 OD ... GDZIE ... (lub użyj odpowiednich wskazówek dotyczących zapytania dla RDBS)
eFloh
3
Sam operator Exists próbuje pobrać absolutne minimum informacji, więc dodanie TOP 1 nie robi nic poza dodaniem 5 znaków do wielkości zapytania. - sqlservercentral.com/blogs/sqlinthewild/2011/04/05/…
AquaAlex
13

Inne odpowiedzi są dość dobre, ale warto również dodać LIMIT 1(lub odpowiednik , aby zapobiec sprawdzaniu niepotrzebnych wierszy.

JesseW
źródło
3
Jeśli jakiekolwiek zapytanie „sprawdzanie istnienia” zwraca więcej niż jeden wiersz, myślę, że bardziej użyteczne jest podwójne sprawdzenie klauzuli WHERE zamiast OGRANICZANIA liczby wyników.
Martin Schapendonk,
2
Myślę, że Limit jest używany w Oracle, a nie w SQL Server
Shantanu Gupta
7
Rozważam przypadek, w którym mogą one być legalnie wieloma wierszami - gdzie pytanie brzmi: „Czy istnieje (jeden lub więcej) wierszy, które spełniają ten warunek?” W takim przypadku nie chcesz patrzeć na wszystkie, tylko na jeden.
JesseW
1
@Shantanu - Wiem, dlatego połączyłem się z (bardzo przez) en.wikipedia artykułem wyjaśniającym inne formy.
JesseW
11
SELECT COUNT(1) FROM MyTable WHERE ...

zapętli wszystkie rekordy. To jest powód, dla którego używanie rekordu jest złe.

użyłbym

SELECT TOP 1 * FROM MyTable WHERE ...

Po znalezieniu 1 rekordu zakończy pętlę.

oski
źródło
W przypadku SELECT TOP 1, czy rzeczywiście zakończy się po znalezieniu jednego, czy też nadal znajdzie wszystko, aby móc powiedzieć, który jest NA GÓRZE?
Eirik H
3
PS: Dla pewności zawszeIF EXISTS (SELECT TOP 1 1 FROM ... WHERE ..)
Eirik H
operator Star wymusi na DBMS dostęp do indeksu klastrowego zamiast tylko indeksu (ów), które będą potrzebne dla twojego warunku przyłączenia. dlatego lepiej jest użyć stałej valua jako wyniku, tj. wybierz top 1 1 .... Zwróci 1 lub DB-Null, w zależności od warunku, czy jest to dopasowanie, czy nie.
eFloh,
to miłe. Podoba mi się ten pierwszy.
isxaker
10

Możesz użyć:

SELECT COUNT(1) FROM MyTable WHERE ... 

lub

WHERE [NOT] EXISTS 
( SELECT 1 FROM MyTable WHERE ... )

Będzie to bardziej wydajne niż SELECT *ponieważ po prostu wybierasz wartość 1 dla każdego wiersza, a nie dla wszystkich pól.

Istnieje również subtelna różnica między COUNT (*) a COUNT (nazwa kolumny):

  • COUNT(*) policzy wszystkie wiersze, w tym null
  • COUNT(column name)będzie liczyć tylko niepuste wystąpienia nazwy kolumny
Winston Smith
źródło
2
Przyjmujesz błędne założenie, że DBMS w jakiś sposób sprawdzi wszystkie te kolumny. Różnica w wydajności pomiędzy count(1)i count(*)będzie różna tylko w najbardziej pozbawionym mózgu DBMS.
paxdiablo
2
Nie, mówię, że tak naprawdę polegasz na szczegółach implementacji, kiedy twierdzisz, że będzie to bardziej wydajne. Jeśli naprawdę chcesz zapewnić najlepszą wydajność, powinieneś profilować ją dla konkretnej implementacji przy użyciu reprezentatywnych danych lub po prostu całkowicie o niej zapomnieć. Wszystko inne może potencjalnie wprowadzać w błąd i może zmienić się drastycznie podczas przenoszenia (na przykład) z DB2 do MySQL.
paxdiablo
1
Chcę wyjaśnić, że nie odrzucam twojej odpowiedzi. Jest to użyteczne. Jedyny problem, z którym mam problem, to deklaracja wydajności, ponieważ przeprowadziliśmy oceny w DB2 / z i stwierdziliśmy, że nie ma prawdziwej różnicy między count(*)i count(1). Czy tak jest w przypadku innych DBMS, nie mogę powiedzieć.
paxdiablo
3
„Wszystko inne może potencjalnie wprowadzać w błąd i może zmienić się drastycznie podczas przenoszenia (na przykład) z bazy danych DB2 do MySQL”. Prawdopodobieństwo ugryzienia jest większe w wyniku obniżenia wydajności SELECT COUNT (*) podczas przenoszenia DBMS niż różnicy implementacji w SELECT 1 lub COUNT (1). Jestem głęboko zwolennikiem pisania kodu, który najwyraźniej wyraża dokładnie to, co chcesz osiągnąć, zamiast polegać na optymalizatorach lub kompilatorach, które domyślnie zachowują pożądane zachowanie.
Winston Smith,
1
Mylące stwierdzenie „COUNT (*)” oznacza „liczenie wierszy” kropka. Nie wymaga dostępu do żadnej konkretnej kolumny. I w większości przypadków nawet nie będzie wymagał dostępu do samego wiersza, ponieważ wystarczy dowolny unikalny indeks.
James Anderson
9

Możesz użyć:

SELECT 1 FROM MyTable WHERE... LIMIT 1

Służy select 1do zapobiegania sprawdzaniu niepotrzebnych pól.

Służy LIMIT 1 do zapobiegania sprawdzaniu niepotrzebnych wierszy.

użytkownik3059943
źródło
3
Dobra uwaga, ale Limit działa na MySQL i PostgreSQL, najlepsze działa na SQL Server, powinieneś zwrócić uwagę na swoją odpowiedź
Leo Gurdian
0

Używam w ten sposób:

IIF(EXISTS (SELECT TOP 1 1 
                FROM Users 
                WHERE FirstName = 'John'), 1, 0) AS DoesJohnExist
DiPix
źródło
0

Inna opcja:

SELECT CASE
    WHEN EXISTS (
        SELECT 1
        FROM [MyTable] AS [MyRecord])
    THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
END
Pranaw
źródło