如何在 SQL 中基于多个“标签”查询数据?
Posted
技术标签:
【中文标题】如何在 SQL 中基于多个“标签”查询数据?【英文标题】:How to query data based on multiple 'tags' in SQL? 【发布时间】:2016-12-21 15:36:11 【问题描述】:我有三个简单的表格:
物品
ItemID (int, PK)
ItemName (nvarchar50)
ItemCost (int)
标签
TagID (int, PK)
TagName (nvarchar50)
物品标签
ItemID (int, FK->Items)
TagID (int, FK->Tags)
如何编写查询以“查找所有标记有 tag1 和 tag2,但不包括 tag3 的项目”?
我需要使用完全不同的架构吗?
【问题讨论】:
【参考方案1】:我喜欢用GROUP BY
和HAVING
来做这件事:
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
表示该项目的标签不能存在。
【讨论】:
这非常简洁,我认为按比例分组相当好(与外部应用解决方案相比)。假设,如果我扩展到 30 个条件,你还会使用这种方法来处理 30 个 sum(case...) 语句还是其他什么? @Vok 。 . .绝对地。无论条件数量如何,GROUP BY
本质上都具有恒定的性能(最大的开销是聚合,而不是另一个聚合列的计算)。 JOIN
方法通常在一两个条件下效果更好,但复杂的查询可能难以维护并且会使优化器感到困惑。【参考方案2】:
您可以使用外部应用:
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
's 和计数TagsID
's。然后加入 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。
【讨论】:
感谢您的建议 - 能够使用 IN 非常适合扩展条件列表。我尝试添加 AND t.TagName NOT IN ('tag3') 但这似乎不起作用。【参考方案3】:这里有一个更简单的解决方案
let filteredTags = Set<Tag.ID>()
CREATE TABLE sketch (
id int
)
CREATE TABLE tag (
id int
)
CREATE TABLE sketchTag (
sketchId int,
tagId int
)
SELECT *
FROM sketch
INNER JOIN (
SELECT sketchId
FROM sketchTag
WHERE tagId IN \(filteredTags)
GROUP BY sketchId
HAVING COUNT(tagId) = \(filteredTags.count)
) AS sketchTag
ON sketch.id = sketchTag.sketchId
【讨论】:
以上是关于如何在 SQL 中基于多个“标签”查询数据?的主要内容,如果未能解决你的问题,请参考以下文章