2013-10-18 58 views
3

我在查找一種有效的方法來根據標籤選擇數據庫中的一行並返回與該行關聯的所有其他標籤時遇到了一些麻煩。 當我使用不返回行的所有標籤的查詢時,大約需要0.001秒。我的初始方案更加規範化,併爲標籤的標籤添加了另一個表格,但最終需要花費幾秒鐘的時間才能完成一個查詢,因此我最終刪除了該表格並使其不太規範化,但即使這樣的解決方案似乎也很慢。高效的MySQL多對多標籤查詢

SELECT c.* 
FROM collections c, 
    tags t 
WHERE t.collection_id=c.id 
    AND (t.name IN ("foo", 
        "bar")) 
GROUP BY c.id HAVING COUNT(t.id)=2 LIMIT 10 

現在我不能想出一個有效的方式來獲得該元素的所有其他標籤而不會變慢。我目前的解決方案慢10倍左右,需要0.01秒才能完成,我也覺得它不能很好地擴展(我覺得它很醜陋)。

SELECT c.*, 
     GROUP_CONCAT(t1.name) AS tags 
FROM collections c, 
    tags t, 
    tags t1 
WHERE t1.collection_id = c.id 
    AND t.collection_id=c.id 
    AND (t.name IN ("foo", 
        "bar")) 
GROUP BY c.id HAVING COUNT(t.id)=2 LIMIT 10 

實際上是否有效或至少更有效的方法來實現這一目標?將真正感謝任何意見或暗示在這一個!

+0

您定義了哪些索引?什麼是執行計劃('EXPLAIN SELECT ...')? – eggyal

+0

我不知道這是否仍然是真實的(或曾經是......),但如果您將連接條件置於「ON」,mysql可能會更容易加入。 atm你在語義上要求''集合'中的每一行加入'tags'中的每一行,然後_then_過濾掉其中的一些。 – Eevee

+0

@eggyal,所有表都將自己的id設置爲主索引。 – GoldenerAal

回答

0

好的。考慮以下...

DROP TABLE IF EXISTS ingredients; 

CREATE TABLE ingredients 
(ingredient_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY 
,ingredient VARCHAR(30) NOT NULL UNIQUE 
); 

INSERT INTO ingredients (ingredient_id, ingredient) VALUES 
(1, 'Macaroni'), 
(2, 'Cheese'), 
(3, 'Beans'), 
(4, 'Toast'), 
(5, 'Jam'), 
(6, 'Jacket Potato'), 
(7, 'Peanut Butter'); 


DROP TABLE IF EXISTS recipes; 

CREATE TABLE recipes 
(recipe_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY 
,recipe VARCHAR(50) NOT NULL UNIQUE 
); 

INSERT INTO recipes (recipe_id, recipe) VALUES 
(1, 'Macaroni & Cheese'), 
(2, 'Cheese on Toast'), 
(3, 'Beans on Toast'), 
(4, 'Cheese & Beans on Toast'), 
(5, 'Toast & Jam'), 
(6, 'Beans & Macaroni'), 
(9, 'Beans on Jacket Potato'), 
(10, 'Cheese & Beans on Jacket Potato'), 
(12, 'Peanut Butter on Toast'); 

DROP TABLE IF EXISTS recipe_ingredient; 

CREATE TABLE recipe_ingredient 
(recipe_id INT NOT NULL 
,ingredient_id INT NOT NULL 
,PRIMARY KEY (recipe_id,ingredient_id) 
); 

INSERT INTO recipe_ingredient (recipe_id, ingredient_id) VALUES 
(1, 1), 
(1, 2), 
(2, 2), 
(2, 4), 
(3, 3), 
(3, 4), 
(4, 2), 
(4, 3), 
(4, 4), 
(5, 4), 
(5, 5), 
(6, 1), 
(6, 3), 
(9, 3), 
(9, 6), 
(10, 2), 
(10, 3), 
(10, 6), 
(12, 4), 
(12, 7); 

SELECT r.* 
     , GROUP_CONCAT(CASE WHEN i.ingredient IN ('Cheese','Beans') THEN i.ingredient END) i 
     , GROUP_CONCAT(CASE WHEN i.ingredient NOT IN('Cheese','Beans') THEN i.ingredient END) o 
    FROM recipes r 
    LEFT 
    JOIN recipe_ingredient ri 
    ON ri.recipe_id = r.recipe_id 
    LEFT 
    JOIN ingredients i 
    ON i.ingredient_id = ri.ingredient_id 
    GROUP 
    BY recipe_id; 

+-----------+---------------------------------+--------------+---------------------+ 
| recipe_id | recipe       | i   | o     | 
+-----------+---------------------------------+--------------+---------------------+ 
|   1 | Macaroni & Cheese    | Cheese  | Macaroni   | 
|   2 | Cheese on Toast     | Cheese  | Toast    | 
|   3 | Beans on Toast     | Beans  | Toast    | 
|   4 | Cheese & Beans on Toast   | Cheese,Beans | Toast    | 
|   5 | Toast & Jam      | NULL   | Toast,Jam   | 
|   6 | Beans & Macaroni    | Beans  | Macaroni   | 
|   9 | Beans on Jacket Potato   | Beans  | Jacket Potato  | 
|  10 | Cheese & Beans on Jacket Potato | Cheese,Beans | Jacket Potato  | 
|  12 | Peanut Butter on Toast   | NULL   | Toast,Peanut Butter | 
+-----------+---------------------------------+--------------+---------------------+ 

小提琴的相同:http://www.sqlfiddle.com/#!2/45aa0/1

0

將其投入使用顯式連接語法(這不應該做出的性能不同,像MySQL應該設法優化它拿走)

SELECT c.*, 
     GROUP_CONCAT(t1.name) AS tags 
FROM collections c 
INNER JOIN tags t ON t.collection_id = c.id 
INNER JOIN tags t1 ON t1.collection_id = c.id 
WHERE t.name IN ("foo", "bar") 
GROUP BY c.id 
HAVING COUNT(t.id) = 2 
LIMIT 10 

可能是值得做一個單獨的INNER JOIN爲您檢查每個標籤,從而消除了具有必要性: -

SELECT c.*, 
     GROUP_CONCAT(t1.name) AS tags 
FROM collections c 
INNER JOIN tags t ON t.collection_id = c.id AND t.name = "foo" 
INNER JOIN tags t0 ON t.collection_id = c.id AND t0.name = "bar" 
INNER JOIN tags t1 ON t1.collection_id = c.id 
GROUP BY c.id 
LIMIT 10 

但是,您的原始查詢看起來不錯,所以它可能是一個索引問題。