Comment sélectionner des lignes sans entrée correspondante dans une autre table?


323

Je fais des travaux de maintenance sur une application de base de données et j'ai découvert que, joie de joie, même si les valeurs d'une table sont utilisées dans le style des clés étrangères, il n'y a pas de contraintes de clé étrangère sur les tables.

J'essaie d'ajouter des contraintes FK sur ces colonnes, mais je trouve que, parce qu'il y a déjà une charge entière de mauvaises données dans les tableaux d'erreurs précédentes qui ont été naïvement corrigées, je dois trouver les lignes qui ne le font pas correspondre à l'autre table, puis supprimez-les.

J'ai trouvé quelques exemples de ce type de requête sur le Web, mais ils semblent tous fournir des exemples plutôt que des explications, et je ne comprends pas pourquoi ils fonctionnent.

Quelqu'un peut-il m'expliquer comment construire une requête qui renvoie toutes les lignes sans correspondance dans une autre table, et ce qu'il fait, afin que je puisse faire ces requêtes moi-même, plutôt que de venir en cours d'exécution sur SO pour chaque table de ce désordre qui a pas de contraintes FK?

Réponses:


614

Voici une requête simple:

SELECT t1.ID
FROM Table1 t1
    LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

Les points clés sont:

  1. LEFT JOINest utilisé; cela renverra TOUTES les lignes Table1, qu'il y ait ou non une ligne correspondante Table2.

  2. La WHERE t2.ID IS NULLclause; cela limitera les résultats renvoyés uniquement aux lignes où l'ID renvoyé Table2est nul - en d'autres termes, il n'y a AUCUN enregistrement Table2pour cet ID particulier de Table1. Table2.IDsera retourné comme NULL pour tous les enregistrements d' Table1où l'ID ne correspond pas Table2.


4
Échoue si une ID est NULL
Michael

169
@Michael - Si avoir un NULLID est valide dans votre schéma, vous pourriez avoir de plus gros problèmes, n'est-ce pas? :)
rinogo

1
cela fonctionnera-t-il même si table1 a plus d'enregistrements que table2? si table1 a 100 enregistrements et table2 a 200 enregistrements (100 qui correspondent / se joignent et 100 qui ne correspondent pas / se joignent) obtiendrions-nous les 200 enregistrements retournés?
Juan Velez

1
J'aime souvent encapsuler la jointure gauche en tant que vue de sous-requête / en ligne afin de garantir qu'il n'y a pas d'interaction entre la clause WHERE et la LEFT JOIN.
Andrew Wolfe

1
@Jas Point clé 1 de la réponse, TOUTES les lignes de la première table, même celles ne correspondant pas à la condition t1.ID = t2.ID de la jointure gauche. Si vous changez la première ligne en SELECT t1.ID, t2.IDet supprimez la ligne OERE vous aurez une meilleure idée de comment cela fonctionne.
Peter Laboš

97

J'utiliserais l' EXISTSexpression car elle est plus puissante, vous pouvez donc choisir plus précisément les lignes que vous souhaitez rejoindre, au cas où LEFT JOINvous devriez prendre tout ce qui est dans la table jointe. Son efficacité est probablement la même qu'en cas de LEFT JOINtest nul.

SELECT t1.ID
FROM Table1 t1
WHERE NOT EXISTS (SELECT t2.ID FROM Table2 t2 WHERE t1.ID = t2.ID)

Quelque chose d'aussi simple est facilement géré par l'optimiseur de requête pour une meilleure exécution.
Andrew Wolfe

2
Oui, le principal avantage de EXISTSsa variabilité.
Ondrej Bozek

1
Simple, élégant et il a résolu mon problème! Joli!
MikeMighty

2
En fait, la vitesse d'une requête que j'ai eue a été réduite de 7 secondes à 200 ms ... (par rapport à WHERE t2.id IS NULL) Merci.
Moti Korets

4
@MotiKorets que vous voulez dire a augmenté la vitesse :)
Ondrej Bozek

14
SELECT id FROM table1 WHERE foreign_key_id_column NOT IN (SELECT id FROM table2)

Le tableau 1 comporte une colonne à laquelle vous souhaitez ajouter la contrainte de clé étrangère, mais les valeurs dans le foreign_key_id_columnne correspondent pas toutes idà celles du tableau 2.

  1. La sélection initiale répertorie les ids de table1. Ce seront les lignes que nous voulons supprimer.
  2. La NOT INclause de l'instruction where limite la requête aux seules lignes où la valeur dans le foreign_key_id_columnn'est pas dans la liste des tables 2 ids.
  3. L' SELECTinstruction entre parenthèses obtiendra une liste de tous les ids du tableau 2.

@ zb226: Votre lien vers concerne les limites de la INclause avec une liste de valeurs littérales. Elle ne s'applique pas à l'utilisation d'une INclause avec le résultat d'une sous-requête. Cette réponse acceptée à cette question résout en fait le problème en utilisant une sous-requête. (Une grande liste de valeurs littérales est problématique car elle crée une énorme expression SQL. Une sous-requête fonctionne très bien car, même si la liste résultante est grande, l'expression SQL elle-même est petite.)
Kannan Goundan

@KannanGoundan Vous avez absolument raison. Retrait du commentaire défectueux.
zb226

8

T2est la table à laquelle vous ajoutez la contrainte:

SELECT *
FROM T2
WHERE constrained_field NOT
IN (
    SELECT DISTINCT t.constrained_field
    FROM T2 
    INNER JOIN T1 t
    USING ( constrained_field )
)

Et supprimez les résultats.


4

Soit les 2 tableaux suivants (salaire et employé) entrez la description de l'image ici

Maintenant, je veux ces enregistrements de la table des employés qui ne sont pas en salaire. Nous pouvons le faire de 3 manières:

  1. Utilisation de la jointure interne
select * from employee
where id not in(select e.id from employee e inner join salary s on e.id=s.id)

entrez la description de l'image ici

  1. Utilisation de la jointure externe gauche
select * from employee e 
left outer join salary s on e.id=s.id  where s.id is null

entrez la description de l'image ici

  1. Utilisation de la jointure complète
select * from employee e
full outer join salary s on e.id=s.id where e.id not in(select id from salary)

entrez la description de l'image ici


2

De la même question ici MySQL Inner Join Query pour obtenir des enregistrements non présents dans une autre table J'ai réussi à le faire fonctionner

SELECT * FROM bigtable 
LEFT JOIN smalltable ON bigtable.id = smalltable.id 
WHERE smalltable.id IS NULL

smalltableest l'endroit où vous avez des enregistrements manquants, bigtablec'est où vous avez tous les enregistrements. La requête répertorie tous les enregistrements qui n'existent pas smalltablemais existent sur le bigtable. Vous pouvez remplacer idpar tout autre critère correspondant.


0

Vous pouvez opter pour les vues comme indiqué ci-dessous:

CREATE VIEW AuthorizedUserProjectView AS select t1.username as username, t1.email as useremail, p.id as projectid, 
(select m.role from userproject m where m.projectid = p.id and m.userid = t1.id) as role 
FROM authorizeduser as t1, project as p

puis travaillez sur la vue pour sélectionner ou mettre à jour:

select * from AuthorizedUserProjectView where projectid = 49

ce qui donne le résultat comme indiqué dans l'image ci-dessous, c'est-à-dire que pour la colonne non correspondante, null a été rempli.

[Result of select on the view][1]

0

Je ne sais pas lequel est optimisé (par rapport à @AdaTheDev) mais celui-ci semble être plus rapide lorsque j'utilise (au moins pour moi)

SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2

Si vous souhaitez obtenir tout autre attribut spécifique, vous pouvez utiliser:

SELECT COUNT(*) FROM table_1 where id in (SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2);


-2

Vous pouvez faire quelque chose comme ça

   SELECT IFNULL(`price`.`fPrice`,100) as fPrice,product.ProductId,ProductName 
          FROM `products` left join `price` ON 
          price.ProductId=product.ProductId AND (GeoFancingId=1 OR GeoFancingId 
          IS NULL) WHERE Status="Active" AND Delete="No"

-6

Comment sélectionner des lignes sans entrée correspondante dans les deux tableaux?

    sélectionnez * dans [dbo]. [EmppDetails] e
     rejoindre à droite [Employé]. [Sexe] d sur e.Gid = d.Gid
    où e.Gid est Null

    syndicat 
    sélectionnez * dans [dbo]. [EmppDetails] e
     rejoindre à gauche [Employé]. [Sexe] d sur e.Gid = d.Gid
    où d.Gid est Null

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.