2016-08-15 95 views
2

我有三個簡單的表格:如何根據SQL中的多個「標籤」查詢數據?

項目

ItemID (int, PK) 
ItemName (nvarchar50) 
ItemCost (int) 

標籤

TagID (int, PK) 
TagName (nvarchar50) 

ItemTags

ItemID (int, FK->Items) 
TagID (int, FK->Tags) 

我如何編寫一個查詢到的「發現的所有項目的影響用tag1和tag2標記,但不tag3'?

我需要使用完全不同的模式嗎?

回答

5

我喜歡GROUP BYHAVING做到這一點:

SELECT it.ItemId 
FROM ItemTags it JOIN 
    Tags t 
    ON t.TagId = it.TagId 
GROUP BY it.ItemId 
HAVING SUM(CASE WHEN t.TagName = 'Tag1' THEN 1 ELSE 0 END) > 0 AND 
     SUM(CASE WHEN t.TagName = 'Tag2' THEN 1 ELSE 0 END) > 0 AND 
     SUM(CASE WHEN t.TagName = 'Tag3' THEN 1 ELSE 0 END) = 0; 

HAVING條款檢查一個標籤每個條件。前兩個使用> 0來表示該項目的標籤必須存在。第三個使用= 0來表示該項目的標籤不能存在。

+0

這非常整齊,我認爲這個小組相當好(相對於外部應用解決方案)。 假設,如果我得到30個條件,你是否仍然使用這種方法30總和(case ...)語句或其他東西? – Vok

+0

@Vok。 。 。絕對。無論條件數量多少,「GROUP BY」基本上都具有恆定的性能(大的開銷是聚合,而不是另一個聚合列的計算)。 'JOIN'方法在一兩種情況下通常效果更好,但是複雜的查詢可能很難維護並且可能會混淆優化器。 –

1

您可以使用OUTER APPLY:

SELECT i.* 
FROM Items i 
OUTER APPLY (
    SELECT COUNT(*) as TagsCount 
    FROM ItemTags it 
    INNER JOIN Tags t 
     ON t.TagID = it.TagID 
    WHERE i.ItemID = it.ItemID 
     AND t.TagName IN ('tag1','tag2') 
) as tt 
WHERE TagsCount = 2 

起初,我們得到了所有ItemID的與計算TagsID的。然後用Items表的連接與過濾只有那些誰擁有TagsCount = 2

編輯#1

添加一個樣本:

;WITH Items AS (
    SELECT * 
    FROM (VALUES 
    (1,'Item1',100),(2,'Item2',50),(3,'Item3',90),(4,'Item4',63),(5,'Item5',75) 
    )as t(ItemID,ItemName,ItemCost) 
) 
, Tags AS (
    SELECT * 
    FROM (VALUES 
    (1,'tag1'),(2,'tag2'),(3,'tag3'),(4,'tag4'),(5,'tag5') 
    ) as t(TagID, TagName) 
) 
, ItemTags AS (
    SELECT * 
    FROM (VALUES 
    (1,1),(1,2),   --This 
    (2,1),(2,2),(2,3),  --and that records we need to get 
    (3,1),  (3,3),(3,4), 
      (4,2),   (4,5), 
    (5,1) 
    ) as t(ItemID, TagID) 
) 

SELECT i.* 
FROM Items i 
CROSS APPLY (
    SELECT COUNT(*) as TagsCount 
    FROM ItemTags it 
    INNER JOIN Tags t 
     ON t.TagID = it.TagID 
    WHERE i.ItemID = it.ItemID 
     AND t.TagName IN ('tag1','tag2') 
    HAVING COUNT(*) = 2 
) as tt 

輸出:

ItemID ItemName ItemCost 
1  Item1  100 
2  Item2  50 

編輯#2

如果要過濾沒有tag3標記的項目,可以添加左連接。

SELECT i.* 
FROM Items i 
CROSS APPLY (
    SELECT COUNT(*) as TagsCount 
    FROM ItemTags it 
    INNER JOIN Tags t 
     ON t.TagID = it.TagID 
    WHERE i.ItemID = it.ItemID 
     AND t.TagName IN ('tag1','tag2') 
    HAVING COUNT(*) = 2 
) as tin 
LEFT JOIN (
    SELECT it.Itemid 
    FROM ItemTags it 
    INNER JOIN Tags t 
     ON t.TagID = it.TagID 
    WHERE t.TagName IN ('tag3') 
    ) tnot 
    ON tnot.Itemid = i.itemid 
WHERE tnot.ItemId is NULL 

如果喲想通過一些標記過濾您可以使用臨時表項有一個標籤,並讓您不需要標籤,然後加入他們的行列。動態SQL也可能是一個選項。

+0

感謝這個建議 - 能夠使用IN很適合擴展條件列表。我試着添加AND t.TagName NOT IN('tag3'),但這似乎不起作用。 – Vok

+0

添加一些編輯,也許這會幫助你。 – gofr1