一种在大型 PostgreSQL 表中处理/合并“后继记录”的方法?

Posted

技术标签:

【中文标题】一种在大型 PostgreSQL 表中处理/合并“后继记录”的方法?【英文标题】:A way to handle/merge 'successor records' in a large PostgreSQL table? 【发布时间】:2018-05-07 04:39:56 【问题描述】:

我有一个包含 415,000 多行的大表,我需要以非常具体的方式对其进行操作:

这最初是关于人们在网上签署请愿书并将他们的信息保存在 PostgreSQL 数据库中。

原来的 'members' 表有一种非常奇怪的方式来处理重复签名者:如果一个人在网站上签署了一份请愿书,它会注册他们的姓名(和其他东西,例如电子邮件地址)并分配他们一个 member_id(主键)。如果此人后来签署了另一份请愿书,但提供了稍微不同的信息,例如他们的姓名拼写错误但使用相同的电子邮件,则数据库将创建一个新成员并使用新的 member_id

最终,手动合并脚本会将两个成员之一标记为 state='deleted' 并为其分配一个 'successor_id' - 保持为 state='active' 的另一个成员的 member_id

问题是:数据库中的其他表,例如特定请愿书的签名者列表,仍然引用旧记录的 member_id,现在已删除。通常,这可以通过简单地加入成员表并使用successor_id 字段来解决,但是......更糟糕的是,一些成员已经合并了3、4 甚至5 次。本质上意味着被删除的成员可以有一个successor_id,它有一个successor_id,它有一个successor_id……等等。

现在我需要将所有已签署特定请愿书的 member_id 导出到 .csv,并且我需要将所有这些已删除的成员解析为他们当前的实际 member_id,无论它是 1 个还是 5 个合并的成员。

实现这一目标的最佳方法是什么?要创建名为 'successor_id_2'、'successor_id_3' 等的新列,然后只使用非空白的最远的successor_id 字段?还是有其他一些聪明的方法来生成特定成员的所有后续successor_id 的列表?我真的很茫然。

更新:我尝试使用递归查询来获取一列规范 id(即一个活跃成员的id),这样做:

WITH RECURSIVE canonicalCTE AS (
 SELECT
 id,
name,
successor_id, canonical_id
 FROM
members

 UNION ALL

 SELECT
 members.id,
 members.name,
 members.successor_id,
 members.canonical_id
 FROM
 members
 JOIN canonicalCTE ON members.id = canonicalCTE.successor_id
) 
UPDATE members
SET canonical_id = m1.id
FROM canonicalCTE m0
LEFT JOIN canonicalCTE m1
ON m0.successor_id = m1.id;

但是除了由于缺乏资源而使我的计算机完全崩溃之外,我不确定这是找到它的正确方法吗? (它仍在运行)

【问题讨论】:

你拥有的是本质上一个链表。你可能想要的是:为每个真实的人分配一个唯一的canonical id。对此的自然选择是该人发生的最低值。您最终会得到一个表 canonical_it, this_id 将当前 id 映射到第一个。这可以通过将 canon_id 添加到原始表中来完成,或者将其放入单独的映射表中。要填充它,您要么必须迭代几次,要么使用递归查询来获取每个人的第一条记录。 我不确定你想要达到什么目的。您能否请edit 提出您的问题并为members 表和一些sample data 添加create table 语句以及基于该示例数据的预期输出。 【参考方案1】:
-- \i tmp.sql

CREATE TABLE linkedlist
        ( id serial PRIMARY KEY
        , hops integer not null default 0
        , canon_id integer references linkedlist(id)
        , link_id integer  references linkedlist(id)
        , name text
        );

        -- create some data
INSERT INTO linkedlist(name)
SELECT'name'|| gs::text
FROM generate_series(1,101) gs;

-- SELECT * FROM linkedlist ORDER BY id;

        -- now:shake it up.
UPDATE linkedlist
SET link_id= id + 11
WHERE id <= 101-11
        ;

UPDATE linkedlist
SET link_id= id + 17
WHERE id <= 101-17
AND id % 7 = 0
        ;

        -- for the (recursive) self join, you *really* need these index(es)
CREATE INDEX ON linkedlist(link_id);
CREATE INDEX ON linkedlist(canon_id);
VACUUM ANALYZE linkedlist;
-- SELECT * FROM linkedlist ORDER BY id;
-- EXPLAIN  -- find the *final* successor for* any* id
WITH RECURSIVE tree AS (
        SELECT id AS this
        , id AS nxt
        , 0 AS hops
        FROM linkedlist l
        WHERE l.link_id IS NULL --final in chain (this is the canonical id)
        UNION ALL
        SELECT l.id AS this
                , t.nxt AS nxt
                , 1+t.hops AS hops
        FROM tree t
        JOIN linkedlist l ON l.link_id = t.this -- any id that points to the canonical one
        )
-- SELECT t.this ,t.hops ,t.nxt FROM tree t ORDER BY t.this, t.hops DESC ; \q

UPDATE linkedlist dst
SET canon_id = src.nxt
,  hops = src.hops
FROM tree src
WHERE dst.id=src.this
AND src.hops > 0
        ;

SELECT l.id, l.link_id, l.canon_id, l.name
        ,COALESCE(c.name,l.name) AS canon_name
FROM linkedlist l
LEFT JOIN linkedlist c ON c.id = l.canon_id
ORDER BY id;

【讨论】:

太棒了!这实际上正是我想要的!我也很欣赏示例代码:-D 我忽略了布尔标志(我认为它们是多余的:NULL 链接可以携带相同的信息)但是,您当然可以在 逻辑框架 中添加更多条件。

以上是关于一种在大型 PostgreSQL 表中处理/合并“后继记录”的方法?的主要内容,如果未能解决你的问题,请参考以下文章

使用 PostgreSQL 从许多表中选择并合并相同的属性

一种在大型 Xcode 项目中查找孤立图像的方法

更新两个表中的数据。一种在一列中包含多个数据

一种在python中表示语料库句子的热编码

为大型 Postgresql 表优化嵌套连接窗口函数

如何在 PostgreSQL 中进行大型非阻塞更新?