Cómo optimizar m: n consulta relación en 3 mesas
-
06-09-2019 - |
Pregunta
Este es mi problema sql - hay 3 tablas:
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
Lo que significa que Pablo (Id = 1) y Joe (Id = 2) están en el equipo de Fútbol (Lists.Id = 1), Paul y Jenny en el equipo de baloncesto, etc ...
Ahora necesito una instrucción SQL que devuelve el Lists.Id de una combinación específica Nombre: En el que las listas son Paul, Joe y Jenny los únicos miembros de esa lista? Sólo contestar Lists.Id = 4 (Breakfast Club) - pero no 5 (Midnight Club), ya que Tina está en esa lista, también.
Lo he intentado con combinaciones internas y consultas SUB:
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)
Parece un poco complicado, eh? Cómo optimizar eso? Sólo necesito que Lists.Id en que los nombres específicos están en (y sólo estos nombres y nadie más). Tal vez con SELECT IN?
Saludos, Dennis
Solución
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;
Editar : Se ha corregido gracias al comentario de Chris Gow; la subselección es necesario excluir las listas que tienen otras personas en ellos. Editar 2 Se ha corregido el nombre de la tabla gracias a comentario de Dennis
Otros consejos
Utilizando la solución de Carl Manaster como punto de partida que se me ocurrió:
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
Actualizado:
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'))
Sólo estaba resolviendo un problema recientemente y que pueden funcionar bien para su caso también. Puede ser una exageración.
Tomé el enfoque de crear una lista de las asociaciones de candidatos que pueden ser la solución correcta y, a continuación, utilizando una tabla de cursor o cola para pasar por las soluciones correctas probable que hacer la validación completa.
En mi caso esto fue implementado por hacer como
select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId
A continuación, se puede comparar el recuento y la suma de comprobación agregada contra tus datos de búsqueda (es decir, sus 3 nombres para comprobar si hay). Si no hay filas coincide, está garantizado para tener coincidencias. Si cualquier fila coincide a continuación, puede ir a través y hacer una unión de ese parentid específica para validar si hay alguna discrepancia entre los conjuntos de filas.
Claro como el barro? :)