2011-10-03 75 views
4

我一直在寻找所有的网,要求人们为指导,但似乎没有人知道正确的(相对较快的)问题的解决方案:MySQL的许多一对多的补集

我有三个表,经典多到许多解决方案:

  • entries:ID(INT),标题(VARCHAR [255]),内容(文字)
  • tags:ID(INT),名称(VARCHAR [255]) ,slug(varchar [255])
  • entries_tags:id(int),entry_id (int),tag_id(int)

到目前为止没有什么不寻常的。现在,让我们说我有标签的测试数据(我保持了蛞蝓,因为它们并不重要):

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

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

编辑我想我应该提到我需要一个能够处理数据集而不是单个标签的解决方案,所以用'One','Two'和'Two'替换我的问题中的'One' 'Three','Four'

edit2提供的答案是正确的,但实际上它太慢了。我想让它工作的唯一方法是使用像Lucene或ElasticSearch这样的第三方搜索引擎。

回答

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

在之前的三分之一时间内(约5秒)做到了这一点,我想这就是在不缓存结果并做出某种魔术巫术技巧的情况下所能获得的最多。非常感谢! – d4rky

+0

随时欢迎您!其实,还有一个想法,我已经更新了我的答案及其实施。你可以试试看吗? –

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 

通过左侧的接合部的entries_tags表ET2(你不想要的数据),你只能选择et2.name IS NULL(其中et2记录不存在)的记录。

+0

我不确定你在这里做了什么,但是这个查询完全被破坏了。或者,也许我正在修复它错误(你的表和字段命名似乎有点随机) – d4rky

+0

我已经修改了我的问题一点,请记住:) – d4rky

+0

好的我已经为你更新了答案 –

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字段指数,这不应该是像你说的是一样慢。它将取决于数据集,但对于索引应该没问题(并且省略了字符串比较)。

+0

更改后在几个地方断开):'SELECT count(entries.id)FROM entries LEFT JOIN entries_tags ON entries.id = entries_tags.entry_id LEFT JOIN标签ON entries_tags.tag_id = tags.id WHERE tags.id IN(1,2)和entries .id NOT IN(SELECT entries.id FROM entries LEFT JOIN entries_tags ON entries.id = entries_tags.entry_id LEFT JOIN tags ON entries_tags.tag_id = tags.id WHERE tags.id IN(3,4));'。在我的测试数据库上花了13秒钟。 – d4rky

+0

你有ID的索引?您可以使用SHOW CREATE TABLE [Table]进行检查。另外,有多少物品有标签3或4? –

+0

[表结构转储](http://pastebin.com/B9L680wb)。我们正在谈论1百万条目和1.172亿条entries_tags关系。 – d4rky