sql - Postgres:通过通用电子邮件地址将帐户聚合为一个身份
问题描述
我正在建立一个用户目录,其中:
- 每个用户都可以拥有一个或多个外部服务的帐户,并且
- 这些帐户中的每一个都可以有一个或多个电子邮件地址。
我想知道的是,如何通过常用的电子邮件地址将这些帐户聚合为单个身份?
例如,假设我有两个服务,A
并且B
. 对于每项服务,我都有一个将帐户与一个或多个电子邮件地址相关联的表格。
因此,如果服务A
有这些帐户电子邮件地址:
account_id | email_address
-----------|--------------
1 | a@foo.com
1 | b@foo.com
2 | c@foo.com
并且服务B
具有以下帐户电子邮件地址:
account_id | email_address
-----------|--------------
3 | a@foo.com
3 | a@bar.com
4 | d@foo.com
我想创建一个表格,将这些帐户的电子邮件地址聚合成一个用户身份:
user_id | email_address
--------|--------------
X | a@foo.com
X | b@foo.com
X | a@bar.com
Y | c@foo.com
Z | d@foo.com
如您所见, account 1
from serviceA
和 account 2
from serviceB
已合并为一个 common user X
,基于 common email address a@foo.com
。这是一个动画视觉效果:
我能找到的最接近的答案是这个,我怀疑该解决方案是递归 CTE,但鉴于输入和引擎不同,我无法实现它。
澄清:我正在寻找一种可以处理任意数量的服务的解决方案,因此输入表可能会更好:
service_id | account_id | email_address
-----------|------------|--------------
A | 1 | a@foo.com
A | 1 | b@foo.com
A | 2 | c@foo.com
B | 3 | a@foo.com
B | 3 | a@bar.com
B | 4 | d@foo.com
解决方案
demo1:db<>fiddle , demo2:db <>fiddle
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 |
热电偶combined
:
加入同一电子邮件地址上的两个表以获得接触点。相同 ID 的 ID 将连接到一个数组中:
| 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
具有生成的唯一 id 集群的列感兴趣。所以我们只需要一次。这是最后的工作DISTINCT
。所以CTEclustered
导致
| ids |
|-----|
| 2 |
| 1,3 |
| 5,4 |
现在我们知道哪些 id 被组合并应该共享它们的数据。ids
现在我们根据原始表加入集群。由于我们在 CTE 中完成了此操作,因此combined
我们可以重用这部分(这就是它被外包到单个 CTE 中的原因:在此步骤中我们不再需要两个表的另一个连接)。JOIN 运算符<@
表示:如果 的“接触点”数组combined
是 的 id 集群的子组,则加入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 |
现在我们可以使用集群 ID(最右边的列)对电子邮件地址进行分组。
array_agg
聚合一列array_cat
的邮件,将两列的电子邮件数组连接成一个大电子邮件数组。
由于存在电子邮件所在的列,因此我们可以在使用该子句NULL
进行聚类之前过滤掉这些值。FILTER (WHERE...)
到目前为止的结果:
| array_cat |
|-----------|
| c |
| a,b,a,b,d |
| e,e,f |
现在我们将所有电子邮件地址分组为一个 ID。我们必须生成新的唯一 ID。这就是窗口函数 row_number
的用途。它只是将行数添加到表中:
| array_cat | new_id |
|-----------|--------|
| c | 1 |
| a,b,a,b,d | 2 |
| e,e,f | 3 |
最后一步是到unnest
数组中获取每个电子邮件地址的一行。由于数组中仍有一些重复项,我们也可以在此步骤中使用 a 消除它们DISTINCT
:
| new_id | email |
|--------|-------|
| 1 | c |
| 2 | a |
| 2 | b |
| 2 | d |
| 3 | e |
| 3 | f |
推荐阅读
- python - 在使用 Sublime Text 时,我遇到了这个错误
- javascript - 使用 jsfuck 约定以流方式调用数组的方法
- django - 我如何覆盖用户保存 Django allauth.socialaccounts
- c++ - opreport 似乎报告了错误的模板实例化
- java - Android 尝试使用资源未找到方法 close()
- tomcat - Lucee IIS10 - Tomcat 8.5 BonCodeAJP 连接器 - 通用连接器通信错误
- php - 为什么我在循环中的帖子占据了整个屏幕的宽度?
- java - 迭代器如何在 LinkedList 上工作?
- reactjs - 试图通过数组做出反应
- html - 添加边距时 div 超出父 div