我有桌子:
表站点
╔════╦═══════════════╗
║ 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的所有网站?永远在一起,而不是单独。
答案 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_id
对site_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);
窗口小部件,则第一个公式可能更容易通过复制和粘贴来扩展。