Список SQL дубликатов кортежей

у меня есть таблица с двумя столбцами идентификаторов, например, так:

╔════════╦══════╗
║ Master ║ Dupe ║
╠════════╬══════╣
║ 2      ║ 7    ║
║ 3      ║ 6    ║
║ 6      ║ 7    ║
║ 20     ║ 25   ║
║ 75     ║ 25   ║
╚════════╩══════╝

каждая строка представляет идентификаторы двух строк в таблице sql, которые считаются дубликатами друг друга.

эта таблица может содержать много тысяч записей, без каких-либо гарантий для данных, кроме Master столбец сортируется в порядке возрастания, как показано на рисунке. Любой столбец может содержать тот же идентификатор, что и другой столбец, потенциально против другого или того же идентификатора партнера. И снова-нет. гарантированное обеспечение.

из этой таблицы я хотел бы получить индекс Master и всех его возможных дураков. Как показано ниже.

желаемых результатов:

  1. самый низкий идентификатор должен храниться как master
  2. все последующие дураки дурака должны сопоставляться с тем же (самым низким идентификатором) master

Для выше, желаемый результат будет выглядеть так (но столбцы не должны быть отсортированы):

╔════════╦══════╗
║ Master ║ Dupe ║
╠════════╬══════╣
║ 2      ║ 3    ║
║ 2      ║ 6    ║
║ 2      ║ 7    ║
║ 20     ║ 25   ║
║ 20     ║ 75   ║
╚════════╩══════╝

Я нахожу его трудно объяснить эту проблему, поэтому мой googling не вернулся много. Я думаю, что где-то должен быть алгоритм для итерации по списку таких кортежей и обнаружения дублирования.

любая помощь приветствуется!

EDIT: я изменил таблицы примеров, чтобы лучше объяснить, как может выглядеть их содержимое.

некоторые заметки для рассмотрения,

  • нет никакой гарантии цепи. Это может быть одна большая цепь, много маленьких. или вообще ни одной.
  • нет никакой гарантии, что все пары появляются в обратном порядке где-то еще в таблице

из того, что я вижу, проблема выглядит рекурсивной, я думаю, что LukStorms находится на правильном пути, но я не могу понять это

ответ: в то время как оба решения ниже от @artm и @LukStorms, похоже, работают, я нашел, что последний был немного более кратким и читаемым. Спасибо вам обоим! Фантастическая помощь на жестком вопрос. Я только хотел бы, чтобы я мог наградить ответ вам обоим

3 ответов


вот пример, который использует рекурсивный 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;

попробуйте это. Получите min master из своей таблицы с CTE и перекрестным соединением со всеми другими значениями в таблице.

;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 dupe (так как данные сортируются по master), если dupe существует на 2-м столбце, где первый столбец не равен текущему 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

приходит поздно на вечеринку, но то, что вы, кажется, хотите, чтобы найти несвязанные наборы. Если вы заботитесь об эффективности, для этого существует чрезвычайно быстрый алгоритм, и он включает в себя структуру данных под названием UnionFind. Кажется, это даже быстрее, чем сортировка...

Googling для реализаций SQL, я был ведущим здесь