2

我有桌子:

表站点

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

表格小部件

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

表站点小部件

╔═════════╦═══════════╗
║ 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 - NOT OK - without 4), yahoo.com (4 - NOT OK, without 2) 和 cnn.com (2, 4 - OK)。我怎样才能获得所有带有 2 和 4 的网站?永远在一起,而不是一个人。

4

7 回答 7

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;
于 2013-02-03T10:30:15.083 回答
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_id每个强制执行唯一性site_idDISTINCT则需要关键字。

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

其他链接

于 2013-02-03T10:23:54.777 回答
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
于 2013-02-03T10:20:53.723 回答
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
于 2013-02-03T10:24:12.123 回答
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
于 2013-02-03T10:27:40.220 回答
1

这是另一种方式,小提琴(感谢@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
于 2013-02-03T10:36:49.277 回答
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认为Site这是GROUP BY对.SELECTSite.id

我发现第一个公式更清晰、更安全,而且我希望它或多或少和第二个公式一样快。

This is both because the many-to-many join table is very small (and index-covered to boot), and because this kind of operation was standard from day one, and is one of the most optimized. For example, I expect checks for widget_id 2 and 4 to run in parallel with a single logical read of the SiteWidget table in the join buffer. Even if they didn't, they would likely be loaded in parallel with a single physical read, the other hitting a SQL cache or, at the very least, the IOSS cache.

You might also try this slight variation, which should be the faster:

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

which ought to run the main JOIN against the smallest SiteWidget table, and come out with id lookups into Site. This is actually what is likely to get done even if you word the query as in the first instance.

The first formula is perhaps easier to extend by copying and pasting if you ever need to add, say, the polling widget.

于 2013-02-03T10:50:46.520 回答