SQL: trova record da una tabella che non esistono in un'altra


310

Ho le seguenti due tabelle SQL (in MySQL):

Phone_book
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1  | John | 111111111111 |
+----+------+--------------+
| 2  | Jane | 222222222222 |
+----+------+--------------+

Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 1  | 0945 | 111111111111 |
+----+------+--------------+
| 2  | 0950 | 222222222222 |
+----+------+--------------+
| 3  | 1045 | 333333333333 |
+----+------+--------------+

Come faccio a sapere quali chiamate sono state effettuate da persone che phone_numbernon si trovano in Phone_book? L'output desiderato sarebbe:

Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 3  | 1045 | 333333333333 |
+----+------+--------------+

Qualsiasi aiuto sarebbe molto apprezzato.

Risposte:


439

Esistono diversi modi per farlo, con varia efficienza, a seconda di quanto è buono il tuo ottimizzatore di query e la dimensione relativa delle tue due tabelle:

Questa è la frase più breve e può essere la più veloce se la tua rubrica è molto breve:

SELECT  *
FROM    Call
WHERE   phone_number NOT IN (SELECT phone_number FROM Phone_book)

in alternativa (grazie ad Alterlife )

SELECT *
FROM   Call
WHERE  NOT EXISTS
  (SELECT *
   FROM   Phone_book
   WHERE  Phone_book.phone_number = Call.phone_number)

o (grazie a WOPR)

SELECT * 
FROM   Call
LEFT OUTER JOIN Phone_Book
  ON (Call.phone_number = Phone_book.phone_number)
  WHERE Phone_book.phone_number IS NULL

(ignorando che, come altri hanno già detto, in genere è meglio selezionare solo le colonne desiderate, non ' *')


1
evitare IN, usare EXISTS - il suggerimento è nel titolo della domanda
annakata,

28
Il join esterno sinistro è probabilmente il più veloce nel caso generale poiché impedisce l'esecuzione ripetuta della subquery.
WOPR,

Non essere pignoli, ma la sottoquery sul mio suggerimento restituisce <code> seleziona 'x' </code> e non <code> seleziona * </code>
Alterlife

si - Il manuale di MySQL suggerisce che questo è normale per una query "EXISTS"
Alnitak

2
@Alnitak: nella seconda query non è necessario SELECT *nella sottoquery. Invece, per esempio SELECT 1, dovrebbe essere abbastanza carino.
Alexander Abakumov,

90
SELECT Call.ID, Call.date, Call.phone_number 
FROM Call 
LEFT OUTER JOIN Phone_Book 
  ON (Call.phone_number=Phone_book.phone_number) 
  WHERE Phone_book.phone_number IS NULL

Dovrebbe rimuovere la subquery, consentendo a Query Optimizer di fare la sua magia.

Inoltre, evita "SELEZIONA *" perché può violare il codice se qualcuno modifica le tabelle o le viste sottostanti (ed è inefficiente).


10
Questo è generalmente il metodo più efficiente in quanto non esegue più passaggi sul secondo tavolo ... spero che alcune persone leggano i commenti.
Nerdfest,

3
Spero piuttosto che il profilo delle persone: a meno che tu non sia un guru delle prestazioni SQL, dire in anticipo quale sarà il più veloce è abbastanza difficile (e dipende dal motore DBMS che usi).
bortzmeyer,

2
La notazione Big O ti dirà facilmente cosa puoi aspettarti di essere più veloce in questo caso. Gli ordini di grandezza sono diversi.
Jonesopolis,

Vedi la risposta di Afterlife e il mio commento lì, se c'è una 1:Nrelazione tra le tue due tabelle. O aggiungi DISTINCTcome si vede nella risposta di Vlado
ToolmakerSteve

25

Il codice seguente sarebbe un po 'più efficiente rispetto alle risposte presentate sopra quando si gestiscono set di dati più grandi.

SELECT * FROM Call WHERE 
NOT EXISTS (SELECT 'x' FROM Phone_book where 
Phone_book.phone_number = Call.phone_number)

1
Come sempre, vale la pena profilare le prestazioni delle query rispetto al set di dati di destinazione per scegliere quello con le migliori prestazioni. Gli ottimizzatori SQL sono abbastanza buoni in questi giorni che i risultati delle prestazioni sono spesso sorprendenti.
Greg Hewgill,

1
Un vantaggio di questo approccio (rispetto a LEFT OUTER JOIN di WOPR) è che evita di restituire più righe per riga di Call, se sono presenti più righe corrispondenti Phone_book. Cioè, se c'è una 1:Nrelazione tra le tue due tabelle.
ToolmakerSteve

Vorrei iniziare con questo - rappresenta direttamente l'intento. Se le prestazioni non sono abbastanza buone, assicurarsi che esistano gli indici appropriati. Solo allora, prova il meno ovvio LEFT OUTER JOIN, vedi se le sue prestazioni sono migliori.
ToolmakerSteve

6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Ciò restituirà gli ID aggiuntivi mancanti nella tabella Phone_book.


4

penso

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL

La idcolonna nella calltabella non ha lo stesso valore della idcolonna nella Phone_booktabella, quindi non puoi unirti a questi valori. Vedi la risposta del WOPR per un approccio simile.
Michael Fredrickson,

3
SELECT t1.ColumnID,
CASE 
    WHEN NOT EXISTS( SELECT t2.FieldText  
                     FROM Table t2 
                     WHERE t2.ColumnID = t1.ColumnID) 
    THEN t1.FieldText
    ELSE t2.FieldText
END FieldText       
FROM Table1 t1, Table2 t2

Questo ti restituirà i dati da una tabella se i dati non sono presenti in un'altra tabella per la stessa colonna
Harvinder Sidhu

1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)

Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post. - Dalla recensione
Dennis Kriechel,

@DennisKriechel ha aggiornato la query in modo che sia più specifica alla domanda.
JoshYates1980,

1

In alternativa,

select id from call
minus
select id from phone_number

1
Non sono sicuro che questo risponda alla domanda così com'è (sebbene l'operatore MINUS) sia una nuova aggiunta. Questo è finito nella coda di bassa qualità - potresti voler migliorare questa risposta.
ste-fu,
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.