【问题标题】:How to query data based on multiple 'tags' in SQL?如何在 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 的项目”?

我需要使用完全不同的架构吗?

【问题讨论】:

    标签: sql sql-server schema


    【解决方案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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-04
      • 1970-01-01
      • 1970-01-01
      • 2011-11-24
      • 2017-11-16
      • 1970-01-01
      • 2021-04-22
      相关资源
      最近更新 更多