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

时间:2014-07-01 21:01:47

标签: mysql sql database join

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

制品

  • id(PK)
  • 名称

标签

  • id(PK)
  • 标签

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

ARTICLE_TAG_ASSOCS

  • id(PK)
  • article_id(FK)
  • tag_id(FK)

通过this question我设法构建了一个查询,该查询可以找到用多个标签中的至少一个标记的文章,例如。

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

问题:如何更改以上内容以查找与所有标签匹配的文章,即“疯狂”和“#”和' parrot',而不只是一个?

明确地将逻辑修改为

WHERE tags.tag = 'budgie' && tags.tag = 'parrot';

...在逻辑上存在缺陷,因为MySQL正在单独考虑每个标签,一次一个,但希望你明白我的意思。

1 个答案:

答案 0 :(得分:2)

有几种可行的方法。

一种方法是为每个标签执行单独的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'

请注意,如果给定的文章多次与同一标记值相关联,则可以返回“重复”行。 (添加DISTINCT关键字或GROUP BY子句是消除重复项的方法。)


另一种方法是,如果我们保证给定文章没有重复的标记值,则使用内联视图来获取与这两个标记关联的article_id列表,然后将JOIN设置为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 

请注意,HAVING子句中的文字“2”与tag列上谓词中的值的数量相匹配。内联视图(别名为s)返回article_id的不同列表,我们可以将其加入articles表。

如果您希望匹配至少三个 四个标记,则此方法非常有用。我们可以在内联视图查询中使用这样的行。

          WHERE t.tag IN ('fee','fi','fo','fum')

         HAVING COUNT(1) >= 3

然后,将返回与这四个标签中至少三个匹配的任何文章。

这些不是返回指定结果的唯一方法,还有其他几种方法。


正如罗兰的回答所指出的那样,你也可以这样做:

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

您也可以使用带有相关子查询的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
               )

注意:在这种情况下,可以在每个子查询中重用相同的表别名,因为它不会导致歧义,但我仍然喜欢不同的别名,因为表别名显示在EXPLAIN输出中,并且不同的别名可以更容易地将EXPLAIN输出中的行与查询中的引用相匹配。)