demo1: дБ <> скрипка , demo2: дБ <> скрипка
WITH combined AS (
SELECT
a.email as a_email,
b.email as b_email,
array_remove(ARRAY[a.id, b.id], NULL) as ids
FROM
a
FULL OUTER JOIN b ON (a.email = b.email)
), clustered AS (
SELECT DISTINCT
ids
FROM (
SELECT DISTINCT ON (unnest_ids)
*,
unnest(ids) as unnest_ids
FROM combined
ORDER BY unnest_ids, array_length(ids, 1) DESC
) s
)
SELECT DISTINCT
new_id,
unnest(array_cat) as email
FROM (
SELECT
array_cat(
array_agg(a_email) FILTER (WHERE a_email IS NOT NULL),
array_agg(b_email) FILTER (WHERE b_email IS NOT NULL)
),
row_number() OVER () as new_id
FROM combined co
JOIN clustered cl
ON co.ids <@ cl.ids
GROUP BY cl.ids
) s
Пошаговое объяснение:
Для объяснения я возьму этот набор данных. Это немного сложнее, чем у вас. Это может лучше проиллюстрировать мои шаги. Некоторые проблемы не возникают в вашем меньшем наборе. Думайте о символах как о переменных для адресов электронной почты.
Таблица A:
| id | email |
|----|-------|
| 1 | a |
| 1 | b |
| 2 | c |
| 5 | e |
Таблица B
| id | email |
|----|-------|
| 3 | a |
| 3 | d |
| 4 | e |
| 4 | f |
| 3 | b |
CTE combined
:
СОЕДИНЕНИЕ обеих таблиц на одних и тех же адресах электронной почты для получения точки соприкосновения. Идентификаторы с одинаковыми идентификаторами будут объединены в один массив:
| a_email | b_email | ids |
|-----------|-----------|-----|
| (null) | a@bar.com | 3 |
| a@foo.com | a@foo.com | 1,3 |
| b@foo.com | (null) | 1 |
| c@foo.com | (null) | 2 |
| (null) | d@foo.com | 4 |
CTE clustered
(простите за имена ...):
Цель состоит в том, чтобы получить все элементы точно в одном массиве. В combined
вы можете видеть, например, в настоящее время есть больше массивов с элементом 4
: {5,4}
и {4}
.
Сначала упорядочьте строки по длине их ids
массивов, потому что DISTINCT
позже должен взять самый длинный массив (потому что удерживает точку касания {5,4}
вместо {4}
).
Затем unnest
массивы ids
, чтобы получить основу для фильтрации. Это заканчивается в:
| a_email | b_email | ids | unnest_ids |
|---------|---------|-----|------------|
| b | b | 1,3 | 1 |
| a | a | 1,3 | 1 |
| c | (null) | 2 | 2 |
| b | b | 1,3 | 3 |
| a | a | 1,3 | 3 |
| (null) | d | 3 | 3 |
| e | e | 5,4 | 4 |
| (null) | f | 4 | 4 |
| e | e | 5,4 | 5 |
После фильтрации с DISTINCT ON
| a_email | b_email | ids | unnest_ids |
|---------|---------|-----|------------|
| b | b | 1,3 | 1 |
| c | (null) | 2 | 2 |
| b | b | 1,3 | 3 |
| e | e | 5,4 | 4 |
| e | e | 5,4 | 5 |
Нас интересует только столбец ids
с сгенерированными кластерами уникальных идентификаторов. Так что нам нужны все они только один раз. Это работа последнего DISTINCT
. Итак, CTE clustered
приводит к
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
Теперь мы знаем, какие идентификаторы объединены, и должны поделиться своими данными. Теперь мы объединяем кластеризованные ids
с исходными таблицами. Поскольку мы сделали это в CTE combined
, мы можем повторно использовать эту часть (вот почему, кстати, она передается на аутсорсинг в один CTE: нам больше не нужно еще одно соединение обеих таблиц на этом этапе). Оператор JOIN <@
говорит: JOIN, если массив «точки соприкосновения» combined
является подгруппой кластера идентификаторов clustered
. Это дает в:
| a_email | b_email | ids | ids |
|---------|---------|-----|-----|
| c | (null) | 2 | 2 |
| a | a | 1,3 | 1,3 |
| b | b | 1,3 | 1,3 |
| (null) | d | 3 | 1,3 |
| e | e | 5,4 | 5,4 |
| (null) | f | 4 | 5,4 |
Теперь мы можем группировать адреса электронной почты, используя кластерные идентификаторы (крайний правый столбец).
array_agg
агрегирует письма одного столбца, array_cat
объединяет почтовые массивы обоих столбцов в один большой почтовый массив.
Поскольку существуют столбцы, в которых адрес электронной почты равен NULL
, мы можем отфильтровать эти значения перед кластеризацией с помощью предложения FILTER (WHERE...)
.
Результат на данный момент:
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
Теперь мы группируем все адреса электронной почты для одного идентификатора. Мы должны генерировать новые уникальные идентификаторы. Для этого предназначена оконная функция row_number
. Он просто добавляет количество строк в таблицу:
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
Последний шаг - unnest
массив, чтобы получить строку для каждого адреса электронной почты. Поскольку в массиве все еще есть дубликаты, мы можем устранить их на этом шаге также с помощью DISTINCT
:
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |