“合并” Postgres 表中的两行,带有外键

Ste*_*e D 7 postgresql foreign-key referential-integrity postgresql-9.4

我使用 PostgreSQL 中的以下两个表来保存我读过的书籍的数据库:

CREATE TABLE authors (
    id SERIAL PRIMARY KEY,
    name text
);

CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title text,
    author_id integer REFERENCES authors(id) ON UPDATE CASCADE ON DELETE CASCADE,
    UNIQUE(title, author_id)
);
Run Code Online (Sandbox Code Playgroud)

现在,在浏览我的作者列表时,我发现了以下两个条目:

id | name
----------
 1 | Mark Twain
 2 | Samuel Clemens
Run Code Online (Sandbox Code Playgroud)

我想要做的是删除“马克吐温”条目,并有效地更新所有引用“马克吐温”的书籍以引用“塞缪尔克莱门斯”。我知道我可以手动执行此操作,但我想要一个有效的解决方案,无论哪些表正在引用authors(id)

我想过这样做(在交易中):

  1. 将 Mark Twain 更改id为 2,让我们UPDATE CASCADE负责更改引用。
  2. 删除马克吐温条目

但这会遇到一些问题,主要是:

  1. 第一步创建一个重复的主键
  2. 一旦它们都具有相同的 ID,我不确定如何引用要删除的正确行!
  3. DELETE CASCADE让我担心的第二个步骤

还有一个更微妙的问题,可以用我(精心策划的)books表格的一部分来说明:

id | title              | author_id
------------------------------------
 1 | "Huckleberry Finn" | 1
 2 | "Huckleberry Finn" | 2
Run Code Online (Sandbox Code Playgroud)

在这里,即使我的两个步骤成功了,我就违反了UNIQUE上contstraint books

有没有办法做到这一点,并解决大部分/所有这些问题?使用 Postgres 9.4。

Erw*_*ter 6

假设您只想books在合并重复作者后删除重复项。

BEGIN;
LOCK books, authors;

CREATE TEMP TABLE dupes ON COMMIT DROP AS (SELECT 2 AS dupe, 1 AS org);

DELETE FROM books b  -- delete duplicate books
USING  dupes d
WHERE  b.author_id = d.dupe
AND    EXISTS (
   SELECT 1
   FROM   books
   WHERE  title = b.title
   AND    author_id = d.org
   );

UPDATE books b  -- now we relink all remaining books
SET    author_id = d.org
FROM   dupes d   
WHERE  b.author_id = d.dupe;

DELETE FROM authors a  -- now we can delete all dupes
USING  dupes d
WHERE  a.id = d.dupe;

COMMIT;
Run Code Online (Sandbox Code Playgroud)

临时表可以容纳许多行以一次删除许多重复项。

对每个引用的表重复前两个步骤authors.id。如果有很多我会动态创建和执行语句......

我明确锁定表以避免并发干扰。

自动化

一个基本函数可能如下所示:

CREATE OR REPLACE FUNCTION f_remove_dupe(_tbl text, _col text, _dupe int, _org int)
  RETURNS void  AS
$func$
DECLARE
   _ftbl text;
   _fcol text;
BEGIN
   FOR _ftbl, _fcol IN
      -- table and column name behind all referencing FKs
      SELECT c.conrelid::regclass::text, f.attname
      FROM   pg_attribute  a 
      JOIN   pg_constraint c ON a.attrelid = c.confrelid AND a.attnum = c.confkey[1]
      JOIN   pg_attribute  f ON f.attrelid = c.conrelid AND f.attnum = c.conkey[1]
      WHERE  a.attrelid = _tbl::regclass
      AND    a.attname  = _col
      AND    c.contype  = 'f'
   LOOP
      EXIT WHEN _ftbl IS NULL;  -- skip if not found
      EXECUTE format('
         UPDATE %1$s
         SET    %2$I = $2
         WHERE  %2$I = $1'
       , _ftbl, _fcol)
      USING _dupe, _org;
   END LOOP;

   EXECUTE format('
      DELETE FROM %I WHERE %I = $1'
    , _tbl, _col)
   USING _dupe;
END
$func$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)

称呼:

SELECT f_remove_dupe('authors', 'id', 2, 1);
Run Code Online (Sandbox Code Playgroud)

这个简单的版本...

  • ... 仅适用于单个受骗者。
  • ... 忽略UNIQUE引用表中的约束。
  • ...假设所有 FK 约束仅使用一列,忽略多列 FK
  • ...忽略来自并发事务的可能干扰。

适应您的要求。

有关的: