EXISTS (SELEZIONA 1 ...) vs EXISTS (SELEZIONA * ...) Uno o l'altro?


38

Ogni volta che devo verificare l'esistenza di una riga in una tabella, tendo a scrivere sempre una condizione come:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT *  -- This is what I normally write
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Alcune altre persone lo scrivono come:

SELECT a, b, c
  FROM a_table
 WHERE EXISTS
       (SELECT 1   --- This nice '1' is what I have seen other people use
          FROM another_table
         WHERE another_table.b = a_table.b
       )

Quando la condizione è NOT EXISTSinvece di EXISTS: In alcune occasioni, potrei scriverla con una LEFT JOINe una condizione extra (a volte chiamata antijoin ):

SELECT a, b, c
  FROM a_table
       LEFT JOIN another_table ON another_table.b = a_table.b
 WHERE another_table.primary_key IS NULL

Cerco di evitarlo perché penso che il significato sia meno chiaro, specialmente quando ciò che è tuo primary_keynon è così ovvio, o quando la tua chiave primaria o la tua condizione di join è multi-colonna (e puoi facilmente dimenticare una delle colonne). Tuttavia, a volte mantieni il codice scritto da qualcun altro ... ed è proprio lì.

  1. C'è qualche differenza (oltre allo stile) da usare al SELECT 1posto di SELECT *?
    C'è qualche caso angolare in cui non si comporta allo stesso modo?

  2. Anche se quello che ho scritto è (AFAIK) SQL standard: esiste una tale differenza per diversi database / versioni precedenti?

  3. C'è qualche vantaggio sulla scrittura esplicita di un antijoin?
    I pianificatori / ottimizzatori contemporanei lo trattano in modo diverso dalla NOT EXISTSclausola?


5
Nota che PostgreSQL supporta le selezioni senza colonne, quindi puoi semplicemente scrivere EXISTS (SELECT FROM ...).
rightfold

1
Ho fatto quasi la stessa domanda su SO un paio di anni fa: stackoverflow.com/questions/7710153/…
Erwin Brandstetter il

Risposte:


45

No, non vi è alcuna differenza di efficienza tra (NOT) EXISTS (SELECT 1 ...)e (NOT) EXISTS (SELECT * ...)in tutti i principali DBMS. Ho visto spesso anche (NOT) EXISTS (SELECT NULL ...)essere usato.

In alcuni puoi persino scrivere (NOT) EXISTS (SELECT 1/0 ...)e il risultato è lo stesso - senza alcun errore (divisione per zero), il che dimostra che l'espressione non viene nemmeno valutata.


Sul LEFT JOIN / IS NULLmetodo antijoin, una correzione: questo equivale a NOT EXISTS (SELECT ...).

In questo caso, NOT EXISTSvsLEFT JOIN / IS NULL, potresti ricevere piani di esecuzione diversi. In MySQL per esempio e principalmente nelle versioni precedenti (prima della 5.7) i piani sarebbero abbastanza simili ma non identici. Gli ottimizzatori di altri DBMS (SQL Server, Oracle, Postgres, DB2) sono - per quanto ne so - più o meno in grado di riscrivere questi 2 metodi e considerare gli stessi piani per entrambi. Tuttavia, non esiste tale garanzia e quando si esegue l'ottimizzazione, è bene controllare i piani da diverse riscritture equivalenti in quanto potrebbero esserci casi in cui ogni ottimizzatore non riscrive (ad esempio query complesse, con molti join e / o tabelle derivate / sottoquery all'interno della sottoquery, in cui le condizioni da più tabelle, colonne composite utilizzate nelle condizioni di unione) o le scelte e i piani dell'ottimizzatore sono influenzati in modo diverso dagli indici, dalle impostazioni, ecc. disponibili

Si noti inoltre che USINGnon può essere utilizzato in tutti i DBMS (ad esempio SQL Server). Le JOIN ... ONopere più comuni ovunque.
E le colonne devono essere precedute dal nome / alias della tabella in SELECTper evitare errori / ambiguità quando abbiamo join.
Di solito preferisco anche mettere la colonna unita nel IS NULLsegno di spunta (anche se il PK o qualsiasi colonna non nullable sarebbe OK, potrebbe essere utile per l'efficienza quando il piano per LEFT JOINutilizza un indice non cluster):

SELECT a_table.a, a_table.b, a_table.c
  FROM a_table
       LEFT JOIN another_table 
           ON another_table.b = a_table.b
 WHERE another_table.b IS NULL ;

C'è anche un terzo metodo per gli antijoin, usando NOT INma questo ha semantica (e risultati!) Differenti se la colonna della tabella interna è nullable. Può essere usato però escludendo le righe con NULL, rendendo la query equivalente alle precedenti 2 versioni:

SELECT a, b, c
  FROM a_table
 WHERE a_table.b NOT IN 
       (SELECT another_table.b
          FROM another_table
         WHERE another_table.b IS NOT NULL
       ) ;

Questo di solito produce anche piani simili nella maggior parte dei DBMS.


1
Fino a versioni molto recenti di MySQL [NOT] IN (SELECT ...), sebbene equivalenti, funzionavano molto male. Evitatelo!
Rick James,

4
Questo non è vero per PostgreSQL . SELECT *sta sicuramente facendo più lavoro. Per semplicità, consiglierei di usareSELECT 1
Evan Carroll

11

Esiste una categoria di casi in cui SELECT 1e SELECT *non sono intercambiabili - più specificamente, uno sarà sempre accettato in quei casi mentre l'altro per lo più no.

Sto parlando di casi in cui è necessario verificare l'esistenza di righe di un set raggruppato . Se la tabella Tha colonne C1e C2stai verificando l'esistenza di gruppi di righe che corrispondono a una condizione specifica, puoi utilizzare in SELECT 1questo modo:

EXISTS
(
  SELECT
    1
  FROM
    T
  GROUP BY
    C1
  HAVING
    AGG(C2) = SomeValue
)

ma non puoi usare SELECT *allo stesso modo.

Questo è solo un aspetto sintattico. Laddove entrambe le opzioni siano accettate sintatticamente, molto probabilmente non avrai alcuna differenza in termini di prestazioni o risultati restituiti, come è stato spiegato nell'altra risposta .

Note aggiuntive a seguito di commenti

Sembra che non molti prodotti di database supportino effettivamente questa distinzione. Prodotti come SQL Server, Oracle, MySQL e SQLite accetteranno felicemente SELECT *nella query sopra senza errori, il che probabilmente significa che trattano un EXISTS SELECTin un modo speciale.

PostgreSQL è un RDBMS in cui SELECT *potrebbe non funzionare, ma in alcuni casi potrebbe comunque funzionare. In particolare, se stai raggruppando per PK, SELECT *funzionerà bene, altrimenti fallirà con il messaggio:

ERRORE: la colonna "T.C2" deve apparire nella clausola GROUP BY o essere utilizzata in una funzione aggregata


1
Aspetti positivi, anche se questo non è esattamente il caso di cui ero preoccupato. Questo mostra una differenza concettuale . Perché, quando tu GROUP BY, il concetto di non ha *senso (o, almeno, non è così chiaro).
joanolo,

5

Un modo discutibilmente interessante di riscrivere la EXISTSclausola che si traduce in una query più pulita e forse meno fuorviante, almeno in SQL Server sarebbe:

SELECT a, b, c
  FROM a_table
 WHERE b = ANY
       (
          SELECT b
          FROM another_table
       );

La versione anti-semi-join sarebbe simile a:

SELECT a, b, c
  FROM a_table
 WHERE b <> ALL
       (
          SELECT b
          FROM another_table
       );

Entrambi sono in genere ottimizzati per lo stesso piano di WHERE EXISTSo WHERE NOT EXISTS, ma l'intenzione è inconfondibile e non hai "strani" 1o *.

È interessante notare che i problemi di controllo null associati NOT IN (...)sono problematici per <> ALL (...), mentre il NOT EXISTS (...)non soffre di quel problema. Considera le seguenti due tabelle con una colonna nullable:

IF OBJECT_ID('tempdb..#t') IS NOT NULL
BEGIN
    DROP TABLE #t;
END;
CREATE TABLE #t 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

IF OBJECT_ID('tempdb..#s') IS NOT NULL
BEGIN
    DROP TABLE #s;
END;
CREATE TABLE #s 
(
    ID INT NOT NULL IDENTITY(1,1)
    , SomeValue INT NULL
);

Aggiungeremo alcuni dati ad entrambi, con alcune righe corrispondenti e altre che non lo fanno:

INSERT INTO #t (SomeValue) VALUES (1);
INSERT INTO #t (SomeValue) VALUES (2);
INSERT INTO #t (SomeValue) VALUES (3);
INSERT INTO #t (SomeValue) VALUES (NULL);

SELECT *
FROM #t;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +
INSERT INTO #s (SomeValue) VALUES (1);
INSERT INTO #s (SomeValue) VALUES (2);
INSERT INTO #s (SomeValue) VALUES (NULL);
INSERT INTO #s (SomeValue) VALUES (4);

SELECT *
FROM #s;
+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
| 4 | 4 |
+ -------- + ----------- +

La NOT IN (...)domanda:

SELECT *
FROM #t 
WHERE #t.SomeValue NOT IN (
    SELECT #s.SomeValue
    FROM #s 
    );

Ha il seguente piano:

inserisci qui la descrizione dell'immagine

La query non restituisce righe poiché i valori NULL rendono impossibile l'uguaglianza da confermare.

Questa query, con <> ALL (...)mostra lo stesso piano e non restituisce righe:

SELECT *
FROM #t 
WHERE #t.SomeValue <> ALL (
    SELECT #s.SomeValue
    FROM #s 
    );

inserisci qui la descrizione dell'immagine

La variante che utilizza NOT EXISTS (...)mostra una forma del piano leggermente diversa e restituisce righe:

SELECT *
FROM #t 
WHERE NOT EXISTS (
    SELECT 1
    FROM #s 
    WHERE #s.SomeValue = #t.SomeValue
    );

Il piano:

inserisci qui la descrizione dell'immagine

I risultati di quella query:

+ -------- + ----------- +
| ID | SomeValue |
+ -------- + ----------- +
| 3 | 3 |
| 4 | NULL |
+ -------- + ----------- +

Questo rende l'utilizzo <> ALL (...)altrettanto incline a risultati problematici come NOT IN (...).


3
Devo dire che non trovo *strano: ho letto EXISTS (SELECT * FROM t WHERE ...) AS there is a _row_ in table _t_ that.... Ad ogni modo, mi piace avere alternative e la tua è chiaramente leggibile. Un dubbio / avvertimento: come si comporterà se bè nullable? [Ho avuto brutte esperienze e alcune notti brevi quando ho cercato di scoprire un errore causato da un x IN (SELECT something_nullable FROM a_table)]
joanolo

EXISTS ti dice se una tabella ha una riga e restituisce vero o falso. EXISTS (SELECT x FROM (valori (null)) è true. IN è = ANY e NOT IN è <> ALL. Questi 4 accettano una riga RHS con NULL per eventualmente corrispondere. (X) = ANY (valori (null)) & (x) <> ALL (valori (null)) sono sconosciuti / null ma EXISTS (valori (null)) è vero. (IN & = ANY hanno gli stessi "problemi di controllo null associati a NOT IN (...) [& ] <> ALL (...) ". QUALSIASI E TUTTO itera OR & AND. Ma ci sono solo" problemi "se non organizzi la semantica come previsto.) Non consigliarti di usarli per EXISTS. Sono fuorvianti , non "meno fuorviante".
philipxy

@philliprxy - Se sbaglio, non ho problemi ad ammetterlo. Sentiti libero di aggiungere la tua risposta se ne hai voglia.
Max Vernon,

4

La "prova" che sono identici (in MySQL) è da fare

EXPLAIN EXTENDED
    SELECT EXISTS ( SELECT * ... ) AS x;
SHOW WARNINGS;

quindi ripetere con SELECT 1. In entrambi i casi, l'output "esteso" mostra che è stato trasformato in SELECT 1.

Allo stesso modo, COUNT(*)si trasforma in COUNT(0).

Un'altra cosa da notare: nelle ultime versioni sono stati apportati miglioramenti all'ottimizzazione. Potrebbe valere la pena confrontare EXISTSvs anti-join. La tua versione potrebbe fare un lavoro migliore con l'una rispetto all'altra.


4

In alcuni database questa ottimizzazione non funziona ancora. Come ad esempio in PostgreSQL Dalla versione 9.6, questo fallirà.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT *
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

E questo avrà successo.

SELECT *
FROM ( VALUES (1) ) AS g(x)
WHERE EXISTS (
  SELECT 1  -- This changed from the first query
  FROM ( VALUES (1),(1) )
    AS t(x)
  WHERE g.x = t.x
  HAVING count(*) > 1
);

Non riesce perché ciò che segue fallisce ma ciò significa ancora che c'è una differenza.

SELECT *
FROM ( VALUES (1),(1) ) AS t(x)
HAVING count(*) > 1;

Puoi trovare ulteriori informazioni su questa particolare stranezza e violazione delle specifiche nella mia risposta alla domanda, la specifica SQL richiede un GROUP BY in EXISTS ()


Un raro caso angolare, forse un po ' strano , ma ancora una volta, a dimostrazione che devi fare molti compromessi durante la progettazione di un database ...
joanolo

-1

Ho sempre usato select top 1 'x'(SQL Server)

Teoricamente, select top 1 'x'sarebbe più efficiente che select *, poiché il primo sarebbe completo dopo aver selezionato una costante sull'esistenza di una riga qualificante, mentre il secondo selezionerebbe tutto.

TUTTAVIA, sebbene molto presto possa essere stato rilevante, l'ottimizzazione ha reso la differenza irrilevante in probabilmente tutti i principali RDBS.


Ha senso. Potrebbe essere (o potrebbe essere stato) uno dei pochissimi casi in cui top nsenza order bysono una buona idea.
joanolo,

3
"In teoria, ...." No, in teoria select top 1 'x'non dovrebbe essere più efficiente rispetto select *a Existun'espressione. In pratica potrebbe essere più efficiente se l'ottimizzatore funziona in modo non ottimale ma teoricamente entrambe le espressioni sono equivalenti.
miracle173

-4

IF EXISTS(SELECT TOP(1) 1 FROMè un'abitudine migliore a lungo termine e attraverso le piattaforme semplicemente perché non è nemmeno necessario iniziare a preoccuparsi di quanto sia buona o cattiva la propria piattaforma / versione attuale; e SQL si sta spostando da TOP nverso parametrizzabile TOP(n). Questa dovrebbe essere un'abilità impara una volta.


3
Che cosa intendi con "su più piattaforme" ? TOPnon è nemmeno valido SQL.
ypercubeᵀᴹ

"SQL is moving .." è chiaramente sbagliato. Non esiste TOP (n)"SQL", il linguaggio di query standard. Ce n'è uno su T-SQL che è il dialetto che sta utilizzando Microsoft SQL Server.
a_horse_with_no_name

Il tag sulla domanda originale è "SQL Server". Ma va bene sottovalutare e contestare ciò che ho detto - lo scopo di questo sito è quello di consentire un facile downvoting. Chi sono io a piovere sulla tua sfilata con noiosa attenzione ai dettagli?
ajeh
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.