Come funzionano le istruzioni SQL EXISTS?


89

Sto cercando di imparare SQL e sto avendo difficoltà a capire le dichiarazioni EXISTS. Mi sono imbattuto in questa citazione su "esiste" e non capisco qualcosa:

Utilizzando l'operatore exist, la sottoquery può restituire zero, una o più righe e la condizione controlla semplicemente se la sottoquery ha restituito righe. Se guardi la clausola select della sottoquery, vedrai che è costituita da un singolo letterale (1); poiché la condizione nella query che la contiene deve solo sapere quante righe sono state restituite, i dati effettivi restituiti dalla sottoquery sono irrilevanti.

Quello che non capisco è come fa la query esterna a sapere quale riga sta controllando la sottoquery? Per esempio:

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

Capisco che se l'ID del fornitore e la tabella degli ordini corrispondono, la sottoquery restituirà true e tutte le colonne dalla riga corrispondente nella tabella dei fornitori verranno emesse. Quello che non capisco è come la sottoquery comunica quale riga specifica (diciamo la riga con ID fornitore 25) deve essere stampata se viene restituito solo un vero o falso.

Mi sembra che non vi sia alcuna relazione tra la query esterna e la sottoquery.

Risposte:


100

Pensare in questo modo:

Per "ogni" riga da Suppliers, controlla se "esiste" una riga nella Ordertabella che soddisfa la condizione Suppliers.supplier_id(proviene dalla "riga" corrente della query esterna) = Orders.supplier_id. Quando trovi la prima riga corrispondente, fermati lì: WHERE EXISTSè stata soddisfatta.

Il collegamento magico tra la query esterna e la sottoquery risiede nel fatto che Supplier_idviene passato dalla query esterna alla sottoquery per ogni riga valutata.

Oppure, per dirla in un altro modo, la sottoquery viene eseguita per ogni riga della tabella della query esterna.

NON è come se la sottoquery venga eseguita nel suo insieme e ottiene il "vero / falso" e quindi cerca di far corrispondere questa condizione "vero / falso" con la query esterna.


7
Grazie! "NON è come una sottoquery eseguita nel suo insieme e ottiene il 'vero / falso', quindi cerca di far corrispondere questa condizione 'vero / falso' con la query esterna." è ciò che mi ha veramente chiarito, continuo a pensare che è così che funzionano le sottoquery (e molte volte lo fanno), ma quello che hai detto ha senso perché la sottoquery si basa sulla query esterna e quindi deve essere eseguita una volta per riga
Clarence Liu

32

Mi sembra che non vi sia alcuna relazione tra la query esterna e la sottoquery.

Cosa pensi che stia facendo la clausola WHERE nell'esempio EXISTS? Come si arriva a questa conclusione quando il riferimento FORNITORI non è nelle clausole FROM o JOIN all'interno della clausola EXISTS?

EXISTS valuta VERO / FALSO ed esce come VERO alla prima corrispondenza dei criteri: ecco perché può essere più veloce di IN. Tieni inoltre presente che la clausola SELECT in un EXISTS viene ignorata - IE:

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

... dovrebbe colpire una divisione per errore zero, ma non lo farà. La clausola WHERE è l'elemento più importante di una clausola EXISTS.

Inoltre, tieni presente che un JOIN non è una sostituzione diretta di EXISTS, perché ci saranno record padre duplicati se c'è più di un record figlio associato al padre.


1
Mi manca ancora qualcosa. Se esce alla prima corrispondenza, in che modo l'output finisce per essere tutti i risultati in cui o.supplierid = s.supplierid? Non produrrebbe invece solo il primo risultato?
Dan

3
@ Dan: le EXISTSuscite, restituendo TRUE alla prima corrispondenza, perché il fornitore esiste almeno una volta nella tabella ORDERS. Se volessi vedere la duplicazione dei dati FORNITORE a causa di più di una relazione figlio in ORDERS, dovresti usare un JOIN. Ma la maggior parte non vuole quella duplicazione e l'esecuzione di GROUP BY / DISTINCT può aggiungere overhead alla query. EXISTSè più efficiente che SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...su SQL Server, non è stato testato su Oracle o MySQL ultimamente.
OMG Ponies

Avevo una domanda, è l'abbinamento fatto per ogni record SELEZIONATO nella query esterna. Allo stesso modo, recuperiamo da Ordini 5 volte se ci sono 5 righe selezionate da Fornitori.
Rahul Kadukar

24

È possibile produrre risultati identici utilizzando JOIN, EXISTS, IN, o 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

1
ottima risposta, ma ricorda anche che è meglio non usare esiste per evitare correlazioni
Florian Fröhlich

1
Quale query pensi che verrà eseguita più velocemente se i fornitori hanno 10 milioni di righe e gli ordini hanno 100 milioni di righe e perché?
Teja

7

Se hai una clausola where simile a questa:

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

puoi facilmente capire perché alcune righe vengono restituite e altre no.

Quando la clausola where è così:

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

significa semplicemente: restituisci righe che hanno un record esistente nella tabella degli ordini con lo stesso id.


2

Modello di tabella di database

Supponiamo di avere le seguenti due tabelle nel nostro database, che formano una relazione di tabella uno-a-molti.

Tabelle SQL EXISTS

La studenttabella è la tabella principale e la student_gradetabella figlia poiché ha una colonna Student_id Foreign Key che fa riferimento alla colonna id Primary Key nella tabella student.

Il student tablecontiene i seguenti due record:

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

Inoltre, la student_gradetabella memorizza i voti ricevuti dagli studenti:

| 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 ESISTE

Diciamo che vogliamo ottenere tutti gli studenti che hanno ricevuto un voto 10 in una classe di matematica.

Se siamo interessati solo all'identificatore dello studente, possiamo eseguire una query come questa:

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

Tuttavia, l'applicazione è interessata a visualizzare il nome completo di a student, non solo l'identificatore, quindi abbiamo bisogno anche di informazioni dalla studenttabella.

Per filtrare i studentrecord che hanno un voto 10 in matematica, possiamo usare l'operatore SQL EXISTS, in questo modo:

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

Quando si esegue la query sopra, possiamo vedere che è selezionata solo la riga Alice:

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

La query esterna seleziona le studentcolonne delle righe che ci interessa restituire al client. Tuttavia, la clausola WHERE utilizza l'operatore EXISTS con una sottoquery interna associata.

L'operatore EXISTS restituisce true se la sottoquery restituisce almeno un record e false se non è selezionata alcuna riga. Il motore di database non deve eseguire completamente la sottoquery. Se viene trovato un singolo record, l'operatore EXISTS restituisce true e viene selezionata l'altra riga di query associata.

La sottoquery interna è correlata perché la colonna student_id della student_gradetabella viene confrontata con la colonna id della tabella student esterna.


Che bella risposta. Penso di non aver capito il concetto perché stavo usando un esempio sbagliato. Funziona EXISTsolo con una sottoquery correlata? Stavo giocando con una query contenente solo 1 tabella, come SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). So che quello che ho scritto potrebbe essere ottenuto con una semplice query WHERE, ma lo stavo usando solo per capire EXISTS. Ho tutte le righe. È davvero dovuto al fatto che non ho utilizzato la sottoquery correlata? Grazie.
Bowen Liu

Ha senso solo per le sottoquery correlate poiché si desidera filtrare i record della query esterna. Nel tuo caso la query interna può essere sostituita con WHERE TRUE
Vlad Mihalcea

Grazie Vlad. È quello che pensavo. È solo una strana idea che mi è venuta in mente mentre stavo scherzando. Onestamente non conoscevo il concetto di sottoquery correlata. E ora ha molto più senso filtrare le righe della query esterna con la query interna.
Bowen Liu

0

EXISTS significa che la sottoquery restituisce almeno una riga, è davvero così. In tal caso, è una sottoquery correlata perché controlla il fornitore_id della tabella esterna con il fornitore_id della tabella interna. Questa query dice, in effetti:

SELEZIONA tutti i fornitori Per ogni ID fornitore, verifica se esiste un ordine per questo fornitore Se il fornitore non è presente nella tabella ordini, rimuovi il fornitore dai risultati RETURN tutti i fornitori che hanno righe corrispondenti nella tabella ordini

Potresti fare la stessa cosa in questo caso con un INNER JOIN.

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

Il commento dei pony è corretto. Avresti bisogno di raggruppare con quel join o selezionare distinti a seconda dei dati di cui hai bisogno.


4
Il join interno produrrà risultati diversi da EXISTS se più di un record figlio è associato a un genitore - non sono identici.
OMG Ponies

Penso che la mia confusione potrebbe essere che ho letto che la sottoquery con EXISTS restituisce vero o falso; ma questa non può essere l'unica cosa che ritorna, giusto? La sottoquery restituisce anche tutti i "fornitori che hanno righe corrispondenti nella tabella degli ordini"? Ma se lo è, in che modo l'istruzione EXISTS restituisce un risultato booleano? Tutto quello che sto leggendo nei libri di testo dice che restituisce solo un risultato booleano, quindi sto avendo difficoltà a riconciliare il risultato del codice con quello che mi viene detto che restituisce.
Dan

Leggi EXISTS come una funzione ... EXISTS (set di risultati). La funzione EXISTS restituirà quindi true se il gruppo di risultati ha righe, false se è vuoto. Fondamentalmente è così.
David Fells

3
@ Dan, considera che EXISTS () viene valutato logicamente per ogni riga di origine in modo indipendente: non è un valore singolo per l'intera query.
Arvo

-1

Ciò che descrivi è una cosiddetta query con una sottoquery correlata .

(In generale) è qualcosa che dovresti cercare di evitare scrivendo la query utilizzando invece un join:

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

Perché altrimenti, la sottoquery verrà eseguita per ogni riga nella query esterna.


2
Queste due soluzioni non sono equivalenti. JOIN fornisce un risultato diverso rispetto alla sottoquery EXISTS se è presente più di una riga ordersche corrisponde alla condizione di join.
a_horse_with_no_name

1
grazie per la soluzione alternativa. ma suggerisci che se viene data un'opzione tra sottoquery correlata e join, dovrei andare con join perché è più efficiente?
sunny_dev
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.