Comment optimiser m: n requête relation sur 3 tables
-
06-09-2019 - |
Question
est mon problème sql - il y a 3 tables:
Names Lists ListHasNames Id Name Id Desc ListsId NamesId =-------- ------------ ---------------- 1 Paul 1 Football 1 1 2 Joe 2 Basketball 1 2 3 Jenny 3 Ping Pong 2 1 4 Tina 4 Breakfast Club 2 3 5 Midnight Club 3 2 3 3 4 1 4 2 4 3 5 1 5 2 5 3 5 4
Ce qui signifie que Paul (Id = 1) et Joe (Id = 2) sont dans l'équipe de football (Lists.Id = 1), Paul et Jenny dans l'équipe de basket-ball, etc ...
Maintenant, je besoin d'une instruction SQL qui renvoie la Lists.Id d'une combinaison spécifique Nom: Où les listes sont Paul, Joe et Jenny les seuls membres de cette liste? Réponse seulement Lists.Id = 4 (Breakfast Club) - mais pas 5 (Midnight Club) parce que Tina est dans cette liste, aussi.
Je l'ai essayé avec et SUB REJOINT INNER INTERROGATIONS:
SELECT Q1.Lists_id FROM ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Paul') and (T1.Id=T2.Names_ID) and ( ( SELECT count(*) FROM listhasnames as Z1 where (Z1.lists_id = T2.lists_Id) ) = 3) ) AS Q1 INNER JOIN ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Joe') and (T1.Id=T2.Names_ID) and ( (SELECT count(*) FROM listhasnames as Z1 WHERE (Z1.Lists_id = T2.lists_id) ) = 3) ) AS Q2 ON (Q1.Lists_id=Q2.Lists_id) INNER JOIN ( SELECT Lists_Id FROM names as T1, listhasnames as T2 WHERE (T1.Name='Jenny') and (T1.Id=T2.Names_ID) and ( (SELECT count(*) FROM listhasnames as Z1 WHERE (Z1.Lists_id = T2.lists_id) ) = 3) ) AS Q3 ON (Q1.Lists_id=Q3.Lists_id)
On dirait un peu compliqué, hein? Comment optimiser cela? Je dois seulement que Lists.Id dans lequel les noms spécifiques sont (et seulement ces noms et personne d'autre). Peut-être avec SELECT IN?
Cordialement, Dennis
La solution
SELECT ListsId
FROM ListHasNames a
WHERE NamesId in (1, 2, 3)
AND NOT EXISTS
(SELECT * from ListHasNames b
WHERE b.ListsId = a.ListsId
AND b.NamesId not in (1, 2, 3))
GROUP BY ListsId
HAVING COUNT(*) = 3;
Modifier : Corrigés grâce au commentaire de Chris Gow; la sous-sélection est nécessaire d'exclure des listes qui ont d'autres personnes sur eux. Edit 2 Correction du nom de la table grâce au commentaire de Dennis
Autres conseils
En utilisant la solution de Carl Manaster comme point de départ, je suis venu avec:
SELECT listsid
FROM listhasnames
GROUP BY listsid HAVING COUNT(*) = 3
INTERSECT
SELECT x.listsid
FROM listhasnames x, names n
WHERE n.name IN('Paul', 'Joe', 'Jenny')
AND n.id = x.namesid
Mise à jour:
select a.ListsId from
(
--lists with three names only
select lhn.ListsId, count(*) as count
from ListHasNames lhn
inner join Names n on lhn.NamesId = n.Id
group by lhn.ListsId
having count(*) = 3
) a
where a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Paul'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Joe'))
and a.ListsId in (select ListsId from ListHasNames lhn where NamesId = (select NamesId from names where Name = 'Jenny'))
Je viens de résoudre un problème récemment qui pourrait bien fonctionner pour votre cas aussi. Il peut être surpuissant.
Je pris l'approche de la création d'une liste des associations de candidats qui peuvent être la bonne solution, puis en utilisant une table de curseur ou file d'attente pour passer par les solutions susceptibles de faire corriger une validation complète.
a été mis en œuvre dans mon cas, en faisant comme
select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId
Ensuite, vous pouvez comparer le nombre et la somme de contrôle total par rapport à vos données de recherche (à savoir vos 3 noms pour vérifier pour). Si aucune ligne ne correspond, vous êtes assuré d'avoir aucune correspondance. Si une ligne correspond alors vous pouvez passer et faire une jointure de cette ParentId spécifique pour valider s'il y a des différences entre les ensembles de lignes.
Clair comme la boue? :)