查找包含多个长格式条件的 ID
Finding the id's which include multiple criteria in long format
假设我有一个这样的table,
id
tagId
1
1
1
2
1
5
2
1
2
5
3
2
3
4
3
5
3
8
我想 select id 的 tagId 包括 2 和 5。对于这个假数据集,它应该 return 1 和 3.
我试过了,
select id from [dbo].[mytable] where tagId IN(2,5)
但它分别考虑了2和5。我也不想让我的 table 保持宽格式,因为 tagId 是动态的。它可以达到任意数量的列。我还考虑过使用两个不同的查询进行过滤以找到(以某种方式)交集。然而,由于我在现实生活中可能会在 tagId 中搜索两个以上的值,所以这对我来说效率不高。
我确定这是以前在标签搜索时遇到的问题。你有什么建议?更改 table 格式?
一个选项是计算每个 id
中不同 tagId
的数量(来自您正在寻找的):
SELECT id
FROM [dbo].[mytable]
WHERE tagId IN (2,5)
GROUP BY id
HAVING COUNT(DISTINCT tagId) = 2
这实际上是一个Relational Division With Remainder问题。
首先,您必须将您的输入设置为正确的 table 格式。如果从客户端代码执行,我建议您使用 Table 值参数。您还可以使用临时 table 或 table 变量。
DECLARE @ids TABLE (tagId int PRIMARY KEY);
INSERT @ids VALUES (2), (5);
这类问题有多种不同的解决方案。
经典双否定EXISTS
SELECT DISTINCT
mt.Id
FROM mytable mt
WHERE NOT EXISTS (SELECT 1
FROM @ids i
WHERE NOT EXISTS (SELECT 1
FROM mytable mt2
WHERE mt2.id = mt.id
AND mt2.tagId = i.tagId)
);
尽管这通常效率不高
与要匹配的 ID 总数相比
SELECT mt.id
FROM mytable mt
JOIN @ids i ON i.tagId = mt.tagId
GROUP BY mt.id
HAVING COUNT(*) = (SELECT COUNT(*) FROM @ids);
这样效率更高。您也可以使用 window 函数执行此操作,它的效率可能或多或少,YMMV。
SELECT mt.Id
FROM mytable mt
JOIN (
SELECT *,
total = COUNT(*) OVER ()
FROM @ids i
) i ON i.tagId = mt.tagId
GROUP BY mt.id
HAVING COUNT(*) = MIN(i.total);
另一种解决方案涉及交叉连接所有内容并使用条件聚合检查有多少匹配项
SELECT mt.id
FROM (
SELECT
mt.id,
mt.tagId,
matches = SUM(CASE WHEN i.tagId = mt.tagId THEN 1 END),
total = COUNT(*)
FROM mytable mt
CROSS JOIN @ids i
GROUP BY
mt.id,
mt.tagId
) mt
GROUP BY mt.id
HAVING SUM(matches) = MIN(total)
AND MIN(matches) >= 0;
还有其他解决方案,参见High Performance Relational Division in SQL Server
假设我有一个这样的table,
id | tagId |
---|---|
1 | 1 |
1 | 2 |
1 | 5 |
2 | 1 |
2 | 5 |
3 | 2 |
3 | 4 |
3 | 5 |
3 | 8 |
我想 select id 的 tagId 包括 2 和 5。对于这个假数据集,它应该 return 1 和 3.
我试过了,
select id from [dbo].[mytable] where tagId IN(2,5)
但它分别考虑了2和5。我也不想让我的 table 保持宽格式,因为 tagId 是动态的。它可以达到任意数量的列。我还考虑过使用两个不同的查询进行过滤以找到(以某种方式)交集。然而,由于我在现实生活中可能会在 tagId 中搜索两个以上的值,所以这对我来说效率不高。
我确定这是以前在标签搜索时遇到的问题。你有什么建议?更改 table 格式?
一个选项是计算每个 id
中不同 tagId
的数量(来自您正在寻找的):
SELECT id
FROM [dbo].[mytable]
WHERE tagId IN (2,5)
GROUP BY id
HAVING COUNT(DISTINCT tagId) = 2
这实际上是一个Relational Division With Remainder问题。
首先,您必须将您的输入设置为正确的 table 格式。如果从客户端代码执行,我建议您使用 Table 值参数。您还可以使用临时 table 或 table 变量。
DECLARE @ids TABLE (tagId int PRIMARY KEY);
INSERT @ids VALUES (2), (5);
这类问题有多种不同的解决方案。
经典双否定
EXISTS
SELECT DISTINCT mt.Id FROM mytable mt WHERE NOT EXISTS (SELECT 1 FROM @ids i WHERE NOT EXISTS (SELECT 1 FROM mytable mt2 WHERE mt2.id = mt.id AND mt2.tagId = i.tagId) );
尽管这通常效率不高
与要匹配的 ID 总数相比
SELECT mt.id FROM mytable mt JOIN @ids i ON i.tagId = mt.tagId GROUP BY mt.id HAVING COUNT(*) = (SELECT COUNT(*) FROM @ids);
这样效率更高。您也可以使用 window 函数执行此操作,它的效率可能或多或少,YMMV。
SELECT mt.Id FROM mytable mt JOIN ( SELECT *, total = COUNT(*) OVER () FROM @ids i ) i ON i.tagId = mt.tagId GROUP BY mt.id HAVING COUNT(*) = MIN(i.total);
另一种解决方案涉及交叉连接所有内容并使用条件聚合检查有多少匹配项
SELECT mt.id FROM ( SELECT mt.id, mt.tagId, matches = SUM(CASE WHEN i.tagId = mt.tagId THEN 1 END), total = COUNT(*) FROM mytable mt CROSS JOIN @ids i GROUP BY mt.id, mt.tagId ) mt GROUP BY mt.id HAVING SUM(matches) = MIN(total) AND MIN(matches) >= 0;
还有其他解决方案,参见High Performance Relational Division in SQL Server