2016-08-15 114 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