Jak działają instrukcje SQL EXISTS?

88

Próbuję nauczyć się języka SQL i mam trudności ze zrozumieniem instrukcji ISTNIEJE. Natknąłem się na ten cytat o „istnieje” i czegoś nie rozumiem:

Używając operatora exist, twoje podzapytanie może zwrócić zero, jeden lub wiele wierszy, a warunek po prostu sprawdza, czy podzapytanie zwróciło jakieś wiersze. Jeśli spojrzysz na klauzulę select w podzapytaniu, zobaczysz, że składa się ona z jednego literału (1); ponieważ warunek w zapytaniu zawierającym musi tylko wiedzieć, ile wierszy zostało zwróconych, rzeczywiste dane zwrócone przez podzapytanie są nieistotne.

Nie rozumiem, skąd zapytanie zewnętrzne wie, który wiersz jest sprawdzane przez podzapytanie? Na przykład:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

Rozumiem, że jeśli identyfikator dostawcy i tabeli zamówień będzie się zgadzać, podzapytanie zwróci wartość true i zostaną wyprowadzone wszystkie kolumny z pasującego wiersza w tabeli dostawców. Nie rozumiem, w jaki sposób podzapytanie informuje, który konkretny wiersz (powiedzmy wiersz z identyfikatorem dostawcy 25) powinien zostać wydrukowany, jeśli zwracana jest tylko prawda lub fałsz.

Wydaje mi się, że nie ma związku między zapytaniem zewnętrznym a podzapytaniem.

Dan
źródło

Odpowiedzi:

98

Pomyśl o tym w ten sposób:

Dla „każdego” wiersza z Suppliers, sprawdź, czy „istnieje” wiersz w Ordertabeli, który spełnia warunek Suppliers.supplier_id(pochodzi z bieżącego „wiersza” zapytania Outer) = Orders.supplier_id. Gdy znajdziesz pierwszy pasujący wiersz, zatrzymaj się w tym miejscu - WHERE EXISTSzostał spełniony.

Magiczne połączenie między zapytaniem zewnętrznym a podzapytaniem polega na tym, że Supplier_idjest ono przekazywane z zapytania zewnętrznego do podzapytania dla każdego ocenianego wiersza.

Inaczej mówiąc, podzapytanie jest wykonywane dla każdego wiersza tabeli zapytania zewnętrznego.

To NIE jest tak, że podzapytanie jest wykonywane w całości i pobiera „prawda / fałsz”, a następnie próbuje dopasować ten warunek „prawda / fałsz” z zapytaniem zewnętrznym.

pobyt
źródło
7
Dzięki! „To NIE jest jak podzapytanie wykonywane w całości i pobiera„ prawda / fałsz ”, a następnie próbuje dopasować ten warunek„ prawda / fałsz ”z zapytaniem zewnętrznym." jest tym, co naprawdę wyjaśniło to dla mnie, wciąż myślę, że tak działają podzapytania (i wiele razy tak się dzieje), ale to, co powiedziałeś, ma sens, ponieważ podzapytanie opiera się na zapytaniu zewnętrznym i dlatego musi być wykonywane raz na wiersz
Clarence Liu
32

Wydaje mi się, że nie ma związku między zapytaniem zewnętrznym a podzapytaniem.

Jak myślisz, co robi klauzula WHERE w przykładzie EXISTS? Jak doszedłeś do takiego wniosku, skoro odniesienia SUPPLIERS nie ma w klauzulach FROM ani JOIN w klauzuli EXISTS?

ISTNIEJE przyjmuje wartości PRAWDA / FAŁSZ i wychodzi jako PRAWDA przy pierwszym dopasowaniu kryteriów - dlatego może być szybsze niż IN. Należy również pamiętać, że klauzula SELECT w EXISTS jest ignorowana - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... powinien osiągnąć błąd dzielenia przez zero, ale tak się nie stanie. Klauzula WHERE jest najważniejszym elementem klauzuli EXISTS.

Należy również pamiętać, że JOIN nie zastępuje bezpośrednio EXISTS, ponieważ będą zduplikowane rekordy nadrzędne, jeśli istnieje więcej niż jeden rekord podrzędny powiązany z nadrzędnym.

Kucyki OMG
źródło
1
Nadal czegoś mi brakuje. Jeśli kończy się przy pierwszym dopasowaniu, w jaki sposób wynikiem końcowym są wszystkie wyniki, gdzie o.supplierid = s.supplierid? Czy zamiast tego nie wyświetliłby tylko pierwszego wyniku?
Dan
3
@Dan: Exits EXISTS, zwracające TRUE przy pierwszym dopasowaniu - ponieważ dostawca istnieje przynajmniej raz w tabeli ORDERS. Jeśli chcesz zobaczyć powielenie danych DOSTAWCY z powodu posiadania więcej niż jednej relacji podrzędnej w ZAMÓWIENIACH, musisz użyć JOIN. Ale większość nie chce tego duplikowania, a uruchomienie GROUP BY / DISTINCT może zwiększyć obciążenie zapytania. EXISTSjest bardziej wydajny niż SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...na SQL Server, ostatnio nie testowałem na Oracle ani MySQL.
Kucyki OMG
Miałem pytanie, czy dopasowywanie jest wykonywane dla każdego rekordu, który jest WYBRANY w zewnętrznym zapytaniu. Tak jak w przypadku 5 razy pobieramy z Zamówienia, jeśli wybrano 5 wierszy z dostawców.
Rahul Kadukar
24

Można produkować identyczne wyniki stosując albo JOIN, EXISTS, IN, lub INTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o
Anthony Faull
źródło
1
wielki odpowiedź, ale także pamiętać, że lepiej nie istnieje stosowanie w celu uniknięcia korelację
Florian Fröhlich
1
Jak myślisz, które zapytanie będzie działać szybciej, jeśli dostawcy mają 10 mln wierszy, a zamówienia mają 100 mln wierszy i dlaczego?
Teja
7

Gdybyś miał klauzulę where, która wyglądała tak:

WHERE id in (25,26,27) -- and so on

możesz łatwo zrozumieć, dlaczego niektóre wiersze są zwracane, a inne nie.

Kiedy klauzula where wygląda następująco:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

oznacza to po prostu: zwraca wiersze, które mają istniejący rekord w tabeli zamówień o tym samym identyfikatorze.

Menahem
źródło
2

To bardzo dobre pytanie, dlatego postanowiłem napisać bardzo szczegółowy artykuł na ten temat na swoim blogu.

Model tabeli bazy danych

Załóżmy, że w naszej bazie danych mamy następujące dwie tabele, które tworzą relację jeden-do-wielu.

Tabele SQL EXISTS

studentStół jest rodzicem, a student_gradeto tabela dziecko, ponieważ ma to student_id kolumny klucz obcy odniesienia do identyfikatora kolumny klucza podstawowego w tabeli studentów.

student tableZawiera następujące dwa rekordy:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

, A student_gradetabela przechowuje klas otrzymali uczniowie:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL ISTNIEJE

Powiedzmy, że chcemy zebrać wszystkich uczniów, którzy otrzymali 10 ocen z matematyki.

Jeśli interesuje nas tylko identyfikator ucznia, możemy uruchomić zapytanie takie jak to:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

Ale aplikacja jest zainteresowana wyświetlaniem pełnej nazwy a student, a nie tylko identyfikatora, więc potrzebujemy również informacji z studenttabeli.

Aby odfiltrować studentrekordy, które mają ocenę 10 z matematyki, możemy użyć operatora SQL EXISTS, na przykład:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

Po uruchomieniu powyższego zapytania widzimy, że wybrany jest tylko wiersz Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

Zapytanie zewnętrzne wybiera studentkolumny wierszy, które chcemy zwrócić klientowi. Jednak klauzula WHERE używa operatora EXISTS z powiązanym podzapytaniem wewnętrznym.

Operator EXISTS zwraca wartość true, jeśli podzapytanie zwróci co najmniej jeden rekord, lub false, jeśli nie wybrano żadnego wiersza. Silnik bazy danych nie musi całkowicie uruchamiać podzapytania. Jeśli dopasowany zostanie pojedynczy rekord, operator EXISTS zwraca wartość true i wybrany jest powiązany z nim inny wiersz zapytania.

Podzapytanie wewnętrzne jest skorelowane, ponieważ kolumna student_id student_gradetabeli jest dopasowana do kolumny id zewnętrznej tabeli uczniów.

Vlad Mihalcea
źródło
Co za świetna odpowiedź. Myślę, że nie zrozumiałem koncepcji, ponieważ użyłem złego przykładu. Działa EXISTtylko z skorelowanym podzapytaniem? Bawiłem się zapytaniem zawierającym tylko 1 tabelę, na przykład SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Wiem, że to, co napisałem, można osiągnąć za pomocą jednego prostego zapytania GDZIE, ale używałem go tylko do zrozumienia ISTNIEJE. Mam wszystkie rzędy. Czy rzeczywiście wynika to z faktu, że nie użyłem skorelowanego podzapytania? Dzięki.
Bowen Liu
Ma to sens tylko w przypadku skorelowanych podzapytań, ponieważ chcesz filtrować rekordy zapytania zewnętrznego. W twoim przypadku wewnętrzne zapytanie można zastąpić pytaniem WHERE TRUE
Vlad Mihalcea
Dzięki, Vlad. Tak myślałem. To po prostu dziwny pomysł, który pojawił się, kiedy się z nim bawiłem. Szczerze mówiąc, nie znałem pojęcia skorelowanego podzapytania. A teraz znacznie bardziej sensowne jest odfiltrowywanie wierszy zapytania zewnętrznego za pomocą zapytania wewnętrznego.
Bowen Liu
0

ISTNIEJE oznacza, że ​​podzapytanie zwraca co najmniej jeden wiersz, to naprawdę wszystko. W takim przypadku jest to podzapytanie skorelowane, ponieważ sprawdza dostawca_id tabeli zewnętrznej z identyfikatorem dostawcy w tabeli wewnętrznej. To zapytanie mówi w efekcie:

WYBIERZ wszystkich dostawców Dla każdego ID dostawcy sprawdź, czy istnieje zamówienie dla tego dostawcy Jeśli dostawca nie jest obecny w tabeli zamówień, usuń dostawcę z wyników POWRÓT wszystkich dostawców, którzy mają odpowiednie wiersze w tabeli zamówień

Możesz zrobić to samo w tym przypadku z INNER JOIN.

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

Komentarz kucyków jest poprawny. Musisz grupować za pomocą tego połączenia lub wybierać różne w zależności od potrzebnych danych.

David Fells
źródło
4
Sprzężenie wewnętrzne da inne wyniki niż EXISTS, jeśli więcej niż jeden rekord podrzędny jest powiązany z rodzicem - nie są one identyczne.
Kucyki OMG
Myślę, że moje zamieszanie może polegać na tym, że przeczytałem, że podzapytanie z ISTNIEJE zwraca prawdę lub fałsz; ale to nie może być jedyna rzecz, którą zwraca, prawda? Czy podzapytanie zwraca również wszystkich „dostawców, którzy mają odpowiednie wiersze w tabeli zamówień”? Ale jeśli tak, w jaki sposób instrukcja EXISTS zwraca wynik boolowski? Wszystko, co czytam w podręcznikach, mówi, że zwraca on tylko wynik boolowski, więc mam trudności z pogodzeniem wyniku kodu z tym, co mi powiedziano, że zwraca.
Dan
Czytaj ISTNIEJE jak funkcję ... ISTNIEJE (zbiór wyników). Funkcja ISTNIEJE wtedy zwróci prawdę, jeśli zestaw wyników zawiera wiersze, lub fałsz, jeśli jest pusty. W zasadzie to wszystko.
David Fells
3
@Dan, weź pod uwagę, że EXISTS () jest logicznie oceniane niezależnie dla każdego wiersza źródłowego - nie jest to pojedyncza wartość dla całego zapytania.
Arvo
-1

To, co opisujesz, to tak zwane zapytanie ze skorelowanym podzapytaniem .

(Ogólnie) jest to coś, czego powinieneś starać się unikać, pisząc zapytanie za pomocą złączenia:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

Ponieważ w przeciwnym razie podzapytanie zostanie wykonane dla każdego wiersza w zapytaniu zewnętrznym.

Wouter van Nifterick
źródło
2
Te dwa rozwiązania nie są równoważne. JOIN daje inny wynik niż podzapytanie EXISTS, jeśli jest więcej niż jeden wiersz, ordersktóry spełnia warunek łączenia.
a_horse_with_no_name
1
dzięki za alternatywne rozwiązanie. ale czy sugerujesz, że jeśli otrzymam opcję między skorelowanym podzapytaniem a złączeniem, powinienem wybrać złączenie, ponieważ jest bardziej wydajne?
sunny_dev,