如何在 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 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 表示该项目的标签不能存在。

【讨论】:

这非常简洁,我认为按比例分组相当好(与外部应用解决方案相比)。假设,如果我扩展到 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 中基于多个“标签”查询数据?的主要内容,如果未能解决你的问题,请参考以下文章

如何在sql中打印多个数据

sql中如何使一列中的多个重复数据只显示第一条

如何在一个实体类中放入从多个sql语句中查询的数据

sql如何在多个表中查询一个表的全部列

sql 2005数据库 如何操作xml

如何在 Tedious 中运行 sql 查询,为 sql in 运算符提供多个值?