Utk*_*nos 1 mysql sql database join
我有一个类似于以下内容的 SQL 设置:
文章
标签
...以及第三个表记录两者之间的关联,因为每篇文章可以有多个标签:
ARTICLE_TAG_ASSOCS
通过这个问题,我设法构建了一个查询,该查询可以找到至少标有多个标签之一的文章,例如
SELECT articles.*
FROM articles
JOIN article_tag_assocs ata ON articles.id = ata.article_id
JOIN tags ON tags.id = ata.tag_id
WHERE tags.tag = 'budgie' OR tags.tag = 'parrot';
Run Code Online (Sandbox Code Playgroud)
问题:如何更改上述内容以查找匹配所有标签的文章,即“budgie”和“parrot”,而不仅仅是一个?
清楚地将逻辑修改为
WHERE tags.tag = 'budgie' && tags.tag = 'parrot';
Run Code Online (Sandbox Code Playgroud)
...在逻辑上有缺陷,因为 MySQL 正在单独考虑每个标签,一次一个,但希望你明白我的意思。
有几种可行的方法。
一种方法是为每个标签执行单独的 JOIN 操作。例如:
SELECT articles.*
FROM articles
JOIN article_tag_assocs ata
ON ata.article_id = articles.id
JOIN tags ta
ON ta.id = ata.tag_id
AND ta.tag = 'budgie'
JOIN article_tag_assocs atb
ON atb.article_id = articles.id
JOIN tags tb
ON tb.id = atb.tag_id
AND tb.tag = 'parrot'
Run Code Online (Sandbox Code Playgroud)
请注意,如果给定的文章不止一次与同一个标签值相关联,这可能会返回“重复”行。(添加DISTINCT关键字或GROUP BY子句是消除重复项的方法。)
另一种方法,如果我们保证给定的文章没有重复的标签值,则使用内联视图获取article_id与两个标签相关联的列表,然后将其设置到articles表中。例如:
SELECT a.*
FROM ( SELECT ata.article_id
FROM article_tag_assocs ata
JOIN tags t
ON t.id = ata.tag_id
WHERE t.tag IN ('budgie','parrot')
GROUP BY ata.article_id
HAVING COUNT(1) = 2
) s
JOIN articles a
ON a.id = s.article_id
Run Code Online (Sandbox Code Playgroud)
请注意,HAVING 子句中的文字“2”与tag列的谓词中的值数相匹配。内联视图(别名为s)返回一个不同的 列表article_id,我们可以将其连接到articles表中。
如果你想匹配,例如,这种方法是非常有用的至少有三个出4个标签。我们可以在内联视图查询中使用这样的行。
WHERE t.tag IN ('fee','fi','fo','fum')
HAVING COUNT(1) >= 3
Run Code Online (Sandbox Code Playgroud)
然后,任何与这四个标签中至少三个匹配的文章都将被返回。
这些不是返回指定结果的唯一方法,还有其他几种方法。
正如罗兰的回答所指出的,你也可以这样做:
FROM articles a
WHERE a.id IN ( <select article id values related to tag 'parrot'> )
AND a.id IN ( <select article id values related to tag 'bungie'> )
Run Code Online (Sandbox Code Playgroud)
您还可以将 EXISTS 子句与相关子查询一起使用,但由于子查询的执行次数,这种方法通常不会在大型集合中表现良好
FROM articles a
WHERE EXISTS ( SELECT 1
FROM article_tag_assocs s1
JOIN tags t1 ON t1.tag = 'bungie'
WHERE s1.article_id = a.id
)
AND EXISTS ( SELECT 1
FROM article_tag_assocs s2
JOIN tags t2 ON t2.tag = 'parrot'
WHERE s2.article_id = a.id
)
Run Code Online (Sandbox Code Playgroud)
注意:在这种情况下,可以在每个子查询中重用相同的表别名,因为它不会导致歧义,但我仍然更喜欢不同的别名,因为表别名显示在 EXPLAIN 输出中,并且不同的别名使更容易将 EXPLAIN 输出中的行与查询中的引用进行匹配。)