一种在大型 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 表中处理/合并“后继记录”的方法?的主要内容,如果未能解决你的问题,请参考以下文章