IN用于许多元素

时间:2013-02-03 10:14:52

标签: mysql sql select relational-division

我有桌子:

表站点

╔════╦═══════════════╗
║ ID ║     NAME      ║
╠════╬═══════════════╣
║  1 ║ stackoverflow ║
║  2 ║ google.com    ║
║  3 ║ yahoo.com     ║
║  4 ║ cnn.com       ║
╚════╩═══════════════╝

表格小部件

╔════╦════════════╗
║ ID ║    NAME    ║
╠════╬════════════╣
║  1 ║ polling    ║
║  2 ║ comments   ║
║  3 ║ newsletter ║
║  4 ║ mail       ║
╚════╩════════════╝

表SiteWidget

╔═════════╦═══════════╗
║ SITE_ID ║ WIDGET_ID ║
╠═════════╬═══════════╣
║       1 ║         1 ║
║       1 ║         2 ║
║       2 ║         2 ║
║       2 ║         3 ║
║       4 ║         2 ║
║       3 ║         1 ║
║       3 ║         3 ║
║       1 ║         4 ║
║       3 ║         4 ║
║       4 ║         1 ║
║       4 ║         4 ║
╚═════════╩═══════════╝

我希望所有网站都有评论(2)和邮件(4)。

我试试:

SELECT * FROM Site 
LEFT JOIN SiteWidget ON Site.id = SiteWidget.site_id 
WHERE SiteWidget.widget_id IN (2, 4) 

但这会让我回复stackoverflow(2,4 - OK),google.com(2 - 不行 - 没有4),yahoo.com(4 - 不行,没有2)和cnn.com(2,4 - 好)。 如何获得2和4的所有网站?永远在一起,而不是单独。

7 个答案:

答案 0 :(得分:4)

这是一种方法 - 使用额外的连接,以便您可以查找2个小部件的组合:

SELECT * FROM Site s
INNER JOIN SiteWidget w1 ON (s.id = w1.site_id)
INNER JOIN SiteWidget w2 ON (s.id = w2.site_id)
WHERE w1.widget_id=2 and w2.widget_id=4;

答案 1 :(得分:3)

此问题称为Relational Division

SELECT  a.Name
FROM    Site a
        INNER JOIN SiteWidget b
            ON a.ID = b.Site_ID
        INNER JOIN Widget c
            ON b.Widget_ID = c.ID
WHERE   c.Name IN ('comments','mail')
GROUP   BY a.Name
HAVING  COUNT(*) = 2

如果widget_idsite_id每个DISTINCT强制执行唯一性,则需要SELECT a.Name FROM Site a INNER JOIN SiteWidget b ON a.ID = b.Site_ID INNER JOIN Widget c ON b.Widget_ID = c.ID WHERE c.Name IN ('comments','mail') GROUP BY a.Name HAVING COUNT(DISTINCT c.Name) = 2 个关键字。

{{1}}

其他链接

答案 2 :(得分:2)

尝试:

SELECT * FROM Site
INNER JOIN SiteWidget SW1
    ON SW1.widget_id = 2 
    AND Site.id = SW1.site_id
INNER JOIN SiteWidget SW2
    ON SW2.widget_id = 4
    AND Site.id = SW2.site_id

答案 3 :(得分:1)

如果您希望按窗口小部件名称过滤

,则可以使用此选项
SELECT
    S.id,
    S.name 
FROM Site S
    JOIN SiteWidget SW
        ON S.id = SW.site_id
    JOIN Widget W
        ON SW.widget_id = W.id
WHERE W.name IN ('comments', 'mail')
GROUP BY S.Id,S.name
HAVING COUNT(DISTINCT W.name) = 2

或者如果您想按小部件ID进行过滤

SELECT
    S.id,
    S.name 
FROM Site S
    JOIN SiteWidget SW
        ON S.id = SW.site_id
WHERE SW.widget_id IN (2, 4)
GROUP BY S.Id,S.name
HAVING COUNT(DISTINCT SW.widget_id) = 2

答案 4 :(得分:1)

要加入两次

SELECT * FROM Site 
inner JOIN SiteWidget m ON Site.id = m.site_id and m.widget_id = 4
inner Join SiteWidget c ON Site.id = c.site_id and c.widget_id = 2

答案 5 :(得分:1)

这是另一种方式,Fiddle(感谢@JW。为小提琴表和数据)

select s.id, s.name
from site s join (
   select sw.site_id, count(w.id) cnt
   from SiteWidget sw join widget w on sw.widget_id = w.id 
   where w.id in (2,4) 
   group by sw.site_id
) T on s.id = T.site_id and T.cnt = 2

答案 6 :(得分:1)

从字面上看,你需要两种不同的JOIN:

SELECT * FROM Site
    JOIN SiteWidget AS mail     ON (Site.id = mail.site_id AND mail.widget_id = 4)
    JOIN SiteWidget AS comments ON (Site.id = comments.site_id AND comments.widget_id = 2);

如果您确定SiteWidget表没有重复项,例如因为(site_id,widget_id)是通常为MtM关系所做的主键,所以你也可以使用HAVING:这是MySQL语法:

SELECT Site.* FROM Site
    JOIN SiteWidget ON (SiteWidget.site_id = Site.id AND widget_id IN (2,4))
    GROUP BY Site.id HAVING COUNT(*) = 2;

因为,由于唯一性,网站出现两次的唯一可能性是拥有两个小部件。有些人认为这是对GROUP BY的滥用,有些SQL(PostgreSQL,如果我没记错的话)将需要Site的字段显示在GROUP BY中,或者聚合函数显示在{{} 1}}即使它们在功能上依赖于分组列SELECT

我发现第一个公式更清晰,更安全,我希望,或多或少与第二个一样快。

这是因为多对多连接表非常小(并且引导索引覆盖),并且因为这种操作从第一天开始就是标准的,并且是最优化的之一。例如,我希望检查widget_id 2和4是否与连接缓冲区中SiteWidget表的单个逻辑读取并行运行。即使它们没有,它们也可能与单个物理读取并行加载,另一个命中SQL缓存,或者至少是IOSS缓存。

您也可以尝试这种轻微变化,这应该更快:

Site.id

应该针对最小的SiteWidget表运行主JOIN,然后将id查找到Site中。这实际上是可能完成的事情,即使您在第一个实例中将查询说出来

如果您需要添加SELECT Site.* FROM Site JOIN SiteWidget AS mail ON (Site.id = mail.site_id AND mail.widget_id = 4) JOIN SiteWidget AS comments ON (mail.site_id = comments.site_id AND comments.widget_id = 2); 窗口小部件,则第一个公式可能更容易通过复制和粘贴来扩展。