我正在使用的数据非常复杂,因此我将提供一个简单的示例,以便希望将其扩展到我正在处理的内容。
注意:我已经找到了一种方法,但是它非常慢且不可扩展。它适用于小型数据集,但是如果我将其应用于需要在其上运行的实际表上,那将花费很多时间。
我需要删除表中数据的全部重复子集。删除重复的行很容易,但是我一直在寻找一种有效的方法来删除重复的子集。
示例:
GroupID Subset Value
------- ---- ----
1 a 1
1 a 2
1 a 3
1 b 1
1 b 3
1 b 5
1 c 1
1 c 3
1 c 5
2 a 1
2 a 2
2 a 3
2 b 4
2 b 5
2 b 6
2 c 1
2 c 3
2 c 6
因此,在此示例中,我将需要从GroupID 1中删除子集“ b”或子集“ c”,因为这两个都包含值1,2,3,所以这无关紧要。对于GroupID 2,没有任何集合可以重复,因此都不会删除。
这是我用来解决此问题的代码。它很好用,但是当应用于10百万条记录时...您可以想象它会非常慢(后来我被告知记录数,我得到的样本数据要小得多)...:>
DECLARE @values TABLE (GroupID INT NOT NULL, SubSet VARCHAR(1) NOT NULL, [Value] INT NOT NULL)
INSERT INTO @values (GroupID, SubSet, [Value])
VALUES (1,'a',1),(1,'a',2),(1,'a',3) ,(1,'b',1),(1,'b',3),(1,'b',5) ,(1,'c',1),(1,'c',3),(1,'c',5),
(2,'a',1),(2,'a',2),(2,'a',3) ,(2,'b',2),(2,'b',4),(2,'b',6) ,(2,'c',1),(2,'c',3),(2,'c',6)
SELECT *
FROM @values v
ORDER BY v.GroupID, v.SubSet, v.[Value]
SELECT x.GroupID, x.NameValues, MIN(x.SubSet)
FROM (
SELECT t1.GroupID, t1.SubSet
, NameValues = (SELECT ',' + CONVERT(VARCHAR(10), t2.[Value]) FROM @values t2 WHERE t1.GroupID = t2.GroupID AND t1.SubSet = t2.SubSet ORDER BY t2.[Value] FOR XML PATH(''))
FROM @values t1
GROUP BY t1.GroupID, t1.SubSet
) x
GROUP BY x.GroupID, x.NameValues
我在这里要做的就是按GroupID和Subset分组,然后将所有值连接到一个逗号分隔的字符串中……然后将其分组到GroupID和Value列表中,并获取MIN子集。
答案 0 :(得分:4)
我会选择这样的东西:
;with cte as
(
select v.GroupID, v.SubSet, checksum_agg(v.Value) h, avg(v.Value) a
from @values v
group by v.GroupID, v.SubSet
)
delete v
from @values v
join
(
select c1.GroupID, case when c1.SubSet > c2.SubSet then c1.SubSet else c2.SubSet end SubSet
from cte c1
join cte c2 on c1.GroupID = c2.GroupID and c1.SubSet <> c2.SubSet and c1.h = c2.h and c1.a = c2.a
)x on v.GroupID = x.GroupID and v.SubSet = x.SubSet
select *
from @values
答案 1 :(得分:2)
来自Checksum_Agg
:
CHECKSUM_AGG结果不取决于行中的顺序 桌子。
这是因为它是以下值的总和:1 + 2 + 3 = 3 + 2 + 1 = 3 + 3 = 6
。
HashBytes
旨在为两个输入产生不同的值,这些输入仅在字节顺序和其他差异上有所不同。 (很有可能两个输入(可能长度截然不同)可能散列为相同的值。您不能采用任意输入并将其压缩为绝对唯一的16字节值。)
以下代码演示了如何使用HashBytes
为每个GroupId
/ Subset
返回。
-- Thanks for the sample data!
DECLARE @values TABLE (GroupID INT NOT NULL, SubSet VARCHAR(1) NOT NULL, [Value] INT NOT NULL)
INSERT INTO @values (GroupID, SubSet, [Value])
VALUES (1,'a',1),(1,'a',2),(1,'a',3) ,(1,'b',1),(1,'b',3),(1,'b',5) ,(1,'c',1),(1,'c',3),(1,'c',5),
(2,'a',1),(2,'a',2),(2,'a',3) ,(2,'b',2),(2,'b',4),(2,'b',6) ,(2,'c',1),(2,'c',3),(2,'c',6);
SELECT *
FROM @values v
ORDER BY v.GroupID, v.SubSet, v.[Value];
with
DistinctGroups as (
select distinct GroupId, Subset
from @Values ),
GroupConcatenatedValues as (
select GroupId, Subset, Convert( VarBinary(256), (
select Convert( VarChar(8000), Cast( Value as Binary(4) ), 2 ) AS [text()]
from @Values as V
where V.GroupId = DG.GroupId and V.SubSet = DG.SubSet
order by Value
for XML Path('') ), 2 ) as GroupedBinary
from DistinctGroups as DG )
-- To see the intermediate results from the CTE you can use one of the
-- following two queries instead of the last select :
-- select * from DistinctGroups;
-- select * from GroupConcatenatedValues;
select GroupId, Subset, GroupedBinary, HashBytes( 'MD4', GroupedBinary ) as Hash
from GroupConcatenatedValues
order by GroupId, Subset;
答案 2 :(得分:1)
您可以在一组行上使用checksum_agg()。如果校验和相同,则有力的证据表明分组字段中的“值”列相等。
在下面的'getChecksums'cte中,我按组和子集分组,并根据“值”列添加了校验和。
在'maybeBadSubsets'cte中,我在每个聚合上放置了一个row_number,目的是在校验和匹配的情况下识别第二+行。
最后,我删除如此确定的所有子组。
with
getChecksums as (
select groupId,
subset,
cs = checksum_agg(value)
from @values v
group by groupId,
subset
),
maybeBadSubsets as (
select groupId,
subset,
cs,
deleteSubset =
case
when row_number() over (
partition by groupId, cs
order by subset
) > 1
then 1
end
from getChecksums
)
delete v
from @values v
where exists (
select 0
from maybeBadSubsets mbs
where v.groupId = mbs.groupId
and v.SubSet = mbs.subset
and mbs.deleteSubset = 1
);
我不知道匹配校验和的确切可能性是多少。如果您对误报率不满意,仍然可以使用它以一种更具算法性的方法消除一些分支,从而大大提高性能。
注意:CTE在性能方面可能会有古怪之处。如果发现查询引擎正在为@values的每一行运行“ maybeBadSubsets”,则可能需要在使用前将其结果放入临时表或表变量中。但是我相信只要存在,您就可以了。
编辑:
我没有抓住它,但是正如OP所注意到的,就错误命中/未命中而言,checksum_agg的表现似乎很差。我怀疑这可能是由于输入的简单性。我改变了
cs = checksum_agg(value)
高于
cs = checksum_agg(convert(int,hashbytes('md5', convert(char(1),value))))
并获得更好的结果。但是我不知道它在更大的数据集上的表现。