MySQL多对多补充集

时间:2011-10-03 08:11:13

标签: mysql sql join many-to-many

我一直在寻找网络并向人们寻求指导,但似乎没有人知道解决问题的正确(相对快速)的解决方案:

我有三张桌子,经典的多对多解决方案:

  • entries:id(int),title(varchar [255]),content(text)
  • tags:id(int),name(varchar [255]),slug(varchar [255])
  • entries_tags:id(int),entry_id(int),tag_id(int)

到目前为止没有什么不寻常的事。现在让我们说我在标签中有测试数据(因为它们并不重要,所以我要保留slu ::)

ID | name
1. | one 
2. | two 
3. | three
4. | four
5. | five 

我还有三个条目:

ID | title
1. | Something
2. | Blah blah blah
3. | Yay!

和关系:

ID | entry_id | tag_id
1. | 1        | 1
2. | 1        | 2
3. | 2        | 1
4. | 2        | 3
5. | 3        | 1
6. | 3        | 2
7. | 3        | 3
8. | 4        | 1
9. | 4        | 4

好的,我们有测试数据。我想知道如何获得所有标记为One的条目,但没有标记Three(即条目1和条目4)。

我知道如何用子查询来做,问题是,它需要花费很多时间(100k条目需要大约10-15秒)。有没有办法用JOIN做到这一点?或者我错过了什么?

编辑我想我应该提到我需要一个适用于数据集而不是单个标签的解决方案,所以在我的问题中将'One'替换为'One','Two'和“两个”,“三个”,“四个”

edit2 提供的答案是对的,但实际使用起来太慢了。我认为使其成功的唯一方法是使用像Lucene或ElasticSearch这样的第三方搜索引擎。

3 个答案:

答案 0 :(得分:3)

以下脚本选择包含标记OneTwo但没有标记ThreeFour的条目:

SELECT DISTINCT
  et.entry_id
FROM entries_tags et
  INNER JOIN tags t1 ON et.tag_id = t1.id AND t1.name IN ('One', 'Two')
  LEFT JOIN  tags t2 ON et.tag_id = t2.id AND t2.name IN ('Three', 'Four')
WHERE t2.id IS NULL

替代方案:INNER JOIN替换为WHERE EXISTS,这样我们就可以摆脱(相当昂贵的)DISTINCT

SELECT
  et.entry_id
FROM entries_tags et
  LEFT JOIN  tags t2 ON et.tag_id = t2.id AND t2.name IN ('Three', 'Four')
WHERE t2.id IS NULL
  AND EXISTS (
    SELECT *
    FROM tags t1
    WHERE t1.id = et.tag_id
      AND t1.name IN ('One', 'Two')
  )

答案 1 :(得分:1)

这应该做你想要的。

(它可能会或可能不会比子查询解决方案更快,我建议您比较查询计划)

SELECT DISTINCT e.* 
FROM tags t1 
INNER JOIN entries_tags et1 ON t1.id=et1.tag_id 
INNER JOIN entries e ON e.entry_id=et1.entry_id 
INNER JOIN tags t2 on t2.name='three'
INNER JOIN tags t3 on t3.name='four'
LEFT JOIN entries_tags et2 ON (et1.entryid=et2.entryid AND t2.id = et2.tag_id ) 
       OR (et1.entryid=et2.entryid AND t3.id = et2.tag_id )
WHERE t1.name IN ('one','two') AND et2.name is NULL 

通过LEFT加入entries_tags表et2(您不想要的数据),然后您只能选择et2.name IS NULL(其中et2记录不存在)的记录。

答案 2 :(得分:0)

你提到过尝试子查询。这是你试过的吗?

SELECT entries.id, entries.content
FROM entries
  LEFT JOIN entries_tags ON entries.id=entries_tags.entries_id
  LEFT JOIN tags ON entries_tags.tag_id=tags.id
WHERE tag.id=XX
  and entries.id NOT IN (
    SELECT entries.id
    FROM entries
      LEFT JOIN entries_tags ON entries.id=entries_tags.entries_id
      LEFT JOIN tags ON entries_tags.tag_id=tags.id
    WHERE tag.id=YY
  )

(其中XX是您想要的标签,YY是您不想要的标签)

对于ID字段的索引,这不应该像你说的那么慢。它将取决于数据集,但它应该没有索引(并省略字符串比较)。