SQL一对多LEFT OUTER JOIN:执行AND查询

时间:2012-12-29 23:51:17

标签: mysql sql

这应该很常见,我正在寻找在一个SQL查询(MySQL)中执行此操作的“最佳”方法。

我有三个表,一个items表,一个linker表和一个tags表。项可以多次标记,因此链接器是一个简单的外键链接器表:

items   | linker  | tags  
--------+---------+-------
item_id | item_id | tag_id
...     | tag_id  | name  
--------+---------+-------

我可以轻松搜索items单个标签,如何搜索包含2个或更多特定标签的商品?

SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` = "tag-a"

理智的人如何搜索2个或更多标签,项目必须包含所有标签,即AND个查询?


编辑:到目前为止我所拥有的是以下内容,它起作用并且看起来并不慢,但看起来很疯狂:

SELECT `items`.* FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE (
        `item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-a")
    AND `item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-b")
    AND `item_id` IN (SELECT item_id FROM linker LEFT JOIN tags USING (tag_id) WHERE name = "tag-c")
    AND `item_stuff` = "whatever"
)

4 个答案:

答案 0 :(得分:1)

假设链接器表的PK是(item_id,tag_id),我会使用以下内容:

select *
  from items
  where item_id in (
    select item_id
      from linker
      join tags using(tag_id)
     where name in ('tag1', 'tag2', 'tag3')
     group by item_id
     having count(tag_id)=3
  )
;

上述查询应该易于维护。您可以轻松添加或减去所需的标签名称。您只需要确保计数与列表中的名称数相匹配。

如果链接器表PK不是(item_id,tag_id),那么having子句必须更改为having count(distinct tag_id)=3,尽管该查询可能执行得不好,具体取决于重复次数(item_id,tag_id)对存在。

关于上述内容的另一个不错的功能是,您可以轻松回答以下问题:哪些项目与以下标签列表中的至少2个相关联('tag1','tag2','tag3')。您只需要将计数设置为正确的值。

答案 1 :(得分:0)

如果我理解正确(我不确定:) ...),你想找到包含某个字符串的结果(比如正则表达式搜索)。

您可以尝试RLIKE功能

SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` RLIKE("tag-a"|"tag-b")

我认为这就是你的意思,但也许不是:

http://dev.mysql.com/doc/refman/5.0/en/regexp.html


或者,如果每个条目每个条目只有一个标记,那么使用IN

SELECT *, `tags`.`name`
FROM `items`
LEFT OUTER JOIN `linker` USING (`item_id`)
LEFT OUTER JOIN `tags` USING (`tag_id`)
WHERE `tags`.`name` IN ("tag-a","tag-b")

http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_in


为什么不只是一个基本的OR

 WHERE `tags`.`name` = "tag-a" OR `tags`.`name` = "tag-b"

我希望我能正确理解你的目标,如果我没有,请告诉我。

编辑我误读了你问题的一部分......我可能不理智,但希望这不会让我失去资格:P

答案 2 :(得分:0)

要重述您的问题,您希望表items中包含某个列表中所有tags的所有列都正确吗?如果是这样,我认为您需要加入tags表,并使用INNER JOIN代替LEFT OUTER JOIN。像这样:

SELECT DISTINCT `items`.* 
FROM   `items` a
JOIN   `linker` b 
ON     b.item_id=a.item_id

JOIN   `tags` c1
ON     c1.tag_id=b.tag_id
   and c1.name = "tag-a"

JOIN   `tags` c2
ON     c2.tag_id=b.tag_id
   and c2.name = "tag-a"

JOIN   `tags` c3
ON     c3.tag_id=b.tag_id
   and c3.name = "tag-c"

使用INNER JOIN将仅选择包含所有三个标记的行。我不确定如何使用可变数量的标签(我认为这是你真正想要的)这样做。

答案 3 :(得分:0)

当然已经问过这个问题:How to filter SQL results in a has-many-through relation

原来我的临时解决方案是最快的(链接问题中的第4位),这里是:

SELECT *
FROM `items`
WHERE (
        `item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-a")
    AND `item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-b")
    AND `item_id` IN (SELECT item_id FROM linker INNER JOIN tags USING (tag_id) WHERE name = "tag-c")
    AND `item_stuff` = "whatever"
)