2011-10-03 174 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