元组的 SQL 去重列表
Posted
技术标签:
【中文标题】元组的 SQL 去重列表【英文标题】:SQL Deduplicate List of Tuples 【发布时间】:2017-11-14 19:27:34 【问题描述】:我有一个包含两列 ID 的表,如下所示:
╔════════╦══════╗
║ Master ║ Dupe ║
╠════════╬══════╣
║ 2 ║ 7 ║
║ 3 ║ 6 ║
║ 6 ║ 7 ║
║ 20 ║ 25 ║
║ 75 ║ 25 ║
╚════════╩══════╝
每一行代表一个sql表中两行的ID,它们被认为是彼此重复的。
此表可以包含数千个条目,除了Master
列以外的数据不保证按升序排序,如图所示。任一列可能包含与另一列相同的 ID,可能针对不同或相同的合作伙伴 ID。再次 - 没有保证。
从这张表中,我想获得 Master 的索引及其所有可能的欺骗。如下图所示。
期望的结果:
-
最低 ID 应保留为主
受骗者的所有后续受骗者都应映射回同一(最低 ID)主人
对于上述情况,所需的输出看起来像这样(但不必对列进行排序):
╔════════╦══════╗
║ Master ║ Dupe ║
╠════════╬══════╣
║ 2 ║ 3 ║
║ 2 ║ 6 ║
║ 2 ║ 7 ║
║ 20 ║ 25 ║
║ 20 ║ 75 ║
╚════════╩══════╝
我发现很难解释这个问题,所以我的谷歌搜索没有返回太多。我在想一定有一个算法可以遍历这样的元组列表并发现重复。
任何帮助表示赞赏!
编辑:我已经修改了示例表以更好地解释它们的内容可能是什么样子。
需要考虑的一些注意事项,
不保证链。它可以是一个大链,也可以是很多小链,或者根本没有。 不能保证所有对在表中的其他位置都以相反的顺序出现据我所见,问题看起来是递归的,我认为 LukStorms 是在正确的轨道上,但我无法完全弄清楚
回答:虽然@artm 和@LukStorms 下面的两种解决方案似乎都有效,但我发现后者更简洁易读。谢谢你们俩!在一个棘手的问题上提供了极好的帮助。我只希望我能将答案奖励给你们两个
【问题讨论】:
你能更好地解释你的逻辑吗?原始表中的结果集中显示的 2 和 3 之间的关系的性质是什么? 当然,3和6是骗子,6和7是骗子,7和2是骗子。保持集合 (2) 的最低 id,ID 的 3、6 和 7 都是 2 的骗子。 【参考方案1】:试试这个。使用 CTE 从表中获取 master 的最小值,并交叉连接到表中的所有其他值。
;WITH minmaster as (select MIN(MASTER) master
FROM myTable)
select distinct m.master
, i.dupe
from minmaster m
cross join (select dupe dupe from myTable union all select master from myTable) i
WHERE i.dupe <> m.master
更新:
在您对更多行进行编辑后,尽管我不确定这是否是最佳解决方案,但下面的内容仍然有效。逻辑从第一个 master 副本开始(因为数据按 master 排序),如果副本存在于第二列且第一列不等于当前 master,则取相同的 master,否则取下一个 master。这很难解释,其他人可能会找到更简单的解决方案。
;WITH myTable AS
(SELECT 2 MASTER, 7 dupe
UNION all SELECT 3, 6
UNION all SELECT 6, 7
UNION all SELECT 20, 25
UNION all SELECT 75, 25
UNION all SELECT 100, 125
UNION all SELECT 150, 300
UNION all SELECT 180, 300
)
, cte AS
(
SELECT m.master L, m.dupe R, ROW_NUMBER() OVER (ORDER BY master) rnkC
FROM myTable m
)
, cte2 AS
(
SELECT m.master L, m.dupe R, ROW_NUMBER() OVER (ORDER BY master) rnkC2
FROM myTable m
)
, cteCur AS
(
SELECT TOP 1 cte.l, cte.R, cte.rnkC
FROM cte
UNION ALL
SELECT
CASE WHEN cteCur.r IN (SELECT dupe
FROM myTable
WHERE MASTER <> cteCur.L AND dupe = cteCur.R)
THEN cteCur.L
ELSE (SELECT cte2.L
FROM cte2
WHERE cte2.rnkC2 = cteCur.rnkC + 1)
END
, CASE WHEN cteCur.r IN (SELECT dupe
FROM myTable
WHERE MASTER <> cteCur.L AND dupe = cteCur.R)
THEN (SELECT cte2.L
FROM cte2
WHERE cte2.R = cteCur.R AND cte2.L <> cteCur.L)
ELSE (SELECT cte2.R
FROM cte2
WHERE cte2.rnkC2 = cteCur.rnkC + 1)
END
, cteCur.rnkC + 1
FROM cteCur
WHERE cteCur.L IS NOT NULL
)
SELECT cteCur.L Master
, cteCur.R Dupe
FROM cteCur
WHERE L IS NOT NULL
ORDER BY L, R
【讨论】:
这里假设它是一个链,请看我的编辑 @artm 您可能想查看这些 row_number() 的顺序。在第一个 CTE 中使用表时,(select 1)
不会给出正确的顺序,因此结果可能会有所不同。
@LukStorms 你说得对,谢谢。我把它改成由主人订购。
谢谢你们!!我已经编辑了原始帖子并标记了答案`
@SeanMissingham 不用担心,很乐意提供帮助。【参考方案2】:
这是一个使用递归 CTE 连接这些重复项的示例。
但为了确保重复项都是双向的,使用了 DUPES CTE。
declare @DuplicateTest table (Master int, Dupe int);
insert into @DuplicateTest (Master, Dupe) values
(3,6),(6,7),(2,7),
(20,25),(75,25);
;with DUPES as
(
select distinct Master as Dupe1, Dupe as Dupe2 from @DuplicateTest
union
select distinct Dupe, Master from @DuplicateTest
)
,RCTE as
(
select Dupe1 as Base, 0 as Level, Dupe1, Dupe2
from DUPES
union all
select r.Base, (r.Level + 1), d.Dupe1, d.Dupe2
from RCTE r
join DUPES d on (r.Dupe2 = d.Dupe1
and r.Dupe1 != d.Dupe2 -- don't loop on the reverse
and r.Base != d.Dupe2 -- don't repeat what we started from
and r.Level < 100) -- if the level gets to big it's most likely a loop
)
select min(Dupe2) as Master, Base as Dupe
from RCTE
group by Base
having Base > min(Dupe2)
order by Base;
【讨论】:
我喜欢你用 RCTE 去哪里,但这个过程似乎假设整个事情是一个链条,并且它是循环的,因为它在原始对反转的情况下关闭。如果您取出底部的7,2
对,它不再起作用,但结果应该仍然相同。如果您愿意,请查看修改后的示例:)
@SeanMissingham 确实,存在基于先前测试数据的假设。不过,不会将其视为“一条链”,它更像是获得分支。答案已更新。【参考方案3】:
聚会迟到了,但你似乎想要找到不连贯的场景。 如果您关心效率,有一个非常快的算法,它涉及称为UnionFind 的数据结构。似乎比排序还要快……
谷歌搜索 SQL 实现,我是领导 there
【讨论】:
以上是关于元组的 SQL 去重列表的主要内容,如果未能解决你的问题,请参考以下文章