如何在3个表上优化m:n关系查询

时间:2009-05-27 17:47:37

标签: sql database-design relation

这是我的SQL问题 - 有3个表:

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

这意味着Paul(Id = 1)和Joe(Id = 2)在足球队(Lists.Id = 1),Paul和Jenny在篮球队等等......

现在我需要一个SQL语句,它返回特定Name组合的Lists.Id: Paul,Joe和Jenny在哪个列表中是该列表中唯一的成员?只回答Lists.Id = 4(早餐俱乐部) - 但不是5(午夜俱乐部)因为蒂娜也在那个名单中。

我已经尝试过INNER JOINS和SUB QUERIES:

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)

看起来有点复杂,呃?如何优化? 我只需要Lists.Id中的特定名称(并且只有这些名称和其他人)。也许使用SELECT IN?

此致  丹尼斯

4 个答案:

答案 0 :(得分:2)

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;

编辑:感谢Chris Gow的评论;子选择是排除在其上有其他人的列表所必需的。 编辑2 由于Dennis的评论

,更正了表名

答案 1 :(得分:2)

以Carl Manaster的解决方案为出发点,我想出了:

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

答案 2 :(得分:1)

更新:

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'))

答案 3 :(得分:0)

我刚刚解决了一个问题,也可能适用于您的情况。这可能有点矫枉过正。

我采用了创建可能是正确解决方案的候选关联列表的方法,然后使用游标或队列表来查看可能正确的解决方案以进行完全验证。

在我的情况下,这是通过像

这样实现的
select
ParentId
count(*) as ChildCount
checksum_agg(checksum(child.*) as ChildAggCrc
from parent join child on parent.parentId = child.parentId

然后,您可以将计数和聚合校验和与查找数据进行比较(即要检查的3个名称)。如果没有匹配的行,则保证不匹配。如果任何行匹配,则可以通过并执行该特定ParentId的连接以验证行集之间是否存在任何差异。

清除泥土? :)