SQL - rechercher des enregistrements d'une table qui n'existent pas dans une autre

310

J'ai les deux tables SQL suivantes (dans 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 |
+----+------+--------------+

Comment savoir quels appels ont été passés par des personnes qui phone_numberne sont pas dans le Phone_book? La sortie souhaitée serait:

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

Toute aide serait très appréciée.

Philip Morton
la source

Réponses:

439

Il existe plusieurs façons de le faire, avec une efficacité variable, selon la qualité de votre optimiseur de requêtes et la taille relative de vos deux tables:

Il s'agit de la déclaration la plus courte, et peut être plus rapide si votre répertoire téléphonique est très court:

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

alternativement (grâce à Alterlife )

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

ou (grâce à WOPR)

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

(en ignorant que, comme d'autres l'ont dit, il est généralement préférable de sélectionner uniquement les colonnes que vous souhaitez, pas ' *')

Alnitak
la source
1
éviter IN, utiliser EXISTS - l'indication est dans le titre de la question
annakata
28
La jointure externe gauche est probablement la plus rapide dans le cas général car elle empêche l'exécution répétée de la sous-requête.
WOPR
Pour ne pas être difficile, mais la sous-requête de ma suggestion renvoie <code> sélectionnez 'x' </code> et non <code> sélectionnez * </code>
Alterlife
oui - Le manuel MySQL suggère que c'est normal pour une requête 'EXISTS'
Alnitak
2
@Alnitak: Dans la deuxième requête, vous n'avez pas besoin SELECT *de la sous-requête. Au lieu de cela, par exemple SELECT 1, devrait être assez joli.
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

Devrait supprimer la sous-requête, permettant à l'optimiseur de requête de travailler sa magie.

Évitez également "SELECT *" car cela peut casser votre code si quelqu'un modifie les tables ou les vues sous-jacentes (et c'est inefficace).

WOPR
la source
10
C'est généralement la méthode la plus efficace car elle n'effectue pas plusieurs passes sur la deuxième table ... j'espère que certaines personnes lisent les comemnts.
Nerdfest
3
J'espère plutôt que le profil des gens: à moins que vous ne soyez un grand gourou des performances SQL, il est assez difficile de dire à l'avance ce qui sera le plus rapide (et dépend du moteur de SGBD que vous utilisez).
bortzmeyer
2
La notation Big O vous indiquera facilement ce que vous pouvez espérer être le plus rapide dans ce cas. Ce sont des ordres de grandeur différents.
Jonesopolis
Voir la réponse d'Afterlife et mon commentaire là-bas, s'il y a une 1:Nrelation entre vos deux tables. OU ajoutez DISTINCTcomme vu dans la réponse de Vlado
ToolmakerSteve
25

Le code ci-dessous serait un peu plus efficace que les réponses présentées ci-dessus lorsqu'il s'agit de grands ensembles de données.

SELECT * FROM Call WHERE 
NOT EXISTS (SELECT 'x' FROM Phone_book where 
Phone_book.phone_number = Call.phone_number)
Alterlife
la source
1
Comme toujours, il convient de profiler les performances des requêtes par rapport à votre ensemble de données cible pour choisir celle qui offre les meilleures performances. Les optimiseurs SQL sont suffisamment performants de nos jours pour que les résultats de performance soient souvent surprenants.
Greg Hewgill
1
Un avantage de cette approche (par rapport à LEFT OUTER JOIN by WOPR) est qu'elle évite de retourner plusieurs lignes par ligne de Call, s'il y a plusieurs lignes correspondantes dans Phone_book. Autrement dit, s'il existe une 1:Nrelation entre vos deux tables.
ToolmakerSteve
Je commencerais par celui-ci - il représente directement l'intention. Si les performances ne sont pas assez bonnes, assurez-vous que les index appropriés existent. Alors seulement, essayez le moins évident LEFT OUTER JOIN, voyez si ses performances sont meilleures.
ToolmakerSteve
6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Cela renverra les identifiants supplémentaires qui manquent dans votre table Phone_book.

Vlado
la source
4

je pense

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
Nat Geo
la source
La idcolonne du calltableau n'est pas la même valeur que la idcolonne du Phone_booktableau, vous ne pouvez donc pas vous associer à ces valeurs. Voir la réponse de WOPR pour une approche similaire.
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
Harvinder Sidhu
la source
Cela vous renverra les données d'une table si les données ne sont pas présentes dans une autre table pour la même colonne
Harvinder Sidhu
1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
JoshYates1980
la source
Cela ne fournit pas de réponse à la question. Pour critiquer ou demander des éclaircissements à un auteur, laissez un commentaire sous son article. - De l'avis
Dennis Kriechel
@DennisKriechel a mis à jour la requête afin qu'elle soit plus spécifique à la question.
JoshYates1980
1

Alternativement,

select id from call
minus
select id from phone_number
elfekz
la source
1
Je ne suis pas sûr que cela réponde à la question telle quelle (bien que l'opérateur MOINS) soit un nouvel ajout. Cela s'est retrouvé dans la file d'attente de faible qualité - vous aimeriez peut-être améliorer cette réponse.
ste-fu