MySQL - 匹配所有标签而不是任何标签

Utk*_*nos 1 mysql sql database join

我有一个类似于以下内容的 SQL 设置:

文章

  • 身份证(PK)
  • 姓名

标签

  • 身份证(PK)
  • 标签

...以及第三个表记录两者之间的关联,因为每篇文章可以有多个标签:

ARTICLE_TAG_ASSOCS

  • 身份证(PK)
  • article_id (FK)
  • tag_id (FK)

通过这个问题,我设法构建了一个查询,该查询可以找到至少标有多个标签之一的文章,例如

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 正在单独考虑每个标签,一次一个,但希望你明白我的意思。

spe*_*593 5

有几种可行的方法。

一种方法是为每个标签执行单独的 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 输出中的行与查询中的引用进行匹配。)