如何阻止postgres表具有重复记录,而是将重复记录写入另一个表中

时间:2013-11-29 13:41:35

标签: sql postgresql duplicates postgresql-9.1

如何阻止Postgres表重复记录,而是将重复记录写入另一个表中。我想将数据从“table1”复制到“table2”,如果“table1”中有任何重复数据,则应将其复制到“duplicates”而不是“table2”。

问题是:

  1. table2上创建唯一密钥会停止错误插入数据
  2. ERROR:  duplicate key value violates unique constraint
    

    与可以使用IGNORE命令的MySQL不同。

    1. table2上创建规则对每次检查无效。

      CREATE  RULE "copy_dup" AS ON INSERT TO "table2"
      WHERE EXISTS 
      (SELECT 1 FROM table2 WHERE (field1, field2, field3) =
      (NEW.field1, NEW.field2, NEW.field3))
      DO INSTEAD INSERT INTO duplicates 
      (field1, field2, field3)  
      VALUES
      (NEW.field1, NEW.field2, NEW.field3);
      
      INSERT INTO "table2"
      (field1, field2, field3)   
      SELECT 
      field1, field2, field3 FROM table1;
      
    2. 创建触发器时会出现语法错误:

      CREATE FUNCTION data_insert_table2()
        RETURNS void AS
      $$
      BEGIN
      
      INSERT INTO table2(field1, field2, field3)
      SELECT field1, field2, field3
      FROM table1;
      
      END;
      $$ LANGUAGE plpgsql;
      
      CREATE TRIGGER ignore_dup
      BEFORE INSERT ON table2
      FOR EACH ROW
      WHEN (OLD.* IS DISTINCT FROM NEW.*)
      EXECUTE PROCEDURE  data_insert_table2();
      
        

      错误:“WHEN”或附近的语法错误

    3. 触发器是否不支持WHEN子句?

      我与Postgres不是很亲密。有人可以帮忙吗?

      table1table2都有serial_id的主键,而不是从一个表复制到另一个表。

      我不确定此功能是否有效,因为触发器在WHEN失败。

1 个答案:

答案 0 :(得分:4)

问题

您的触发器(至少)因此失败:INSERT触发器未定义special variable OLD。没有“旧”行,例如DELETEUPDATE触发器。

带触发器的永久解决方案

否则触发器解决方案应该可以正常工作。基于您更新的问题:

CREATE FUNCTION data_insert_table2()
  RETURNS trigger AS
$func$
BEGIN
-- 
INSERT INTO dupe(field1, field2, field3)  -- insert into other table
SELECT NEW.field1, NEW.field2, NEW.field3
WHERE EXISTS (                            -- if dupe is already in table2
   SELECT 1 FROM table2
   WHERE (field1, field2, field3) = (NEW.field1, NEW.field2, NEW.field3)
   );

IF FOUND THEN        -- only if the above wrote to the dupe table ..
   RETURN NULL;      -- .. cancel original INSERT
END IF;

RETURN NEW;          -- else proceed normally

END;
$func$ LANGUAGE plpgsql;

CREATE TRIGGER ignore_dup
BEFORE INSERT ON table2
FOR EACH ROW          -- no WHEN condition!
EXECUTE PROCEDURE  data_insert_table2();

一次性操作

对于一次性操作,我会使用data-modifying CTEs进行查询,但是(Postgres 9.1或更高版本):

WITH sel AS (
   SELECT t.field1, t.field2, t.field3
   FROM  (
      SELECT DISTINCT ON (field1, field2, field3)  -- fold duplicates in source
             pk_col, field1, field2, field3
      FROM   table1
      ORDER  BY field1, field2, field3, pk_col     -- take "first" row per set
      ) t
   LEFT   JOIN table2 t2 USING (field1, field2, field3)
   WHERE  t2.field1 IS NULL                        -- except rows in table2
   )
, ins1 AS (
   INSERT INTO table2 (field1, field2, field3) 
   SELECT field1, field2, field3
   FROM   sel
   )
INSERT INTO dupes (field1, field2, field3) 
SELECT t.field1, t.field2, t.field3
FROM   table1 t
LEFT   JOIN sel USING (pk_col)
WHERE  sel.pk_col IS NULL;

将其与UNIQUE上的table2约束相结合,以确保满足您的要求。

请注意,保留NULL的两列不相同。如果您不同意此标准SQL定义,则必须定义所有列NOT NULL才能使其正常工作。

此操作容易出现竞争条件。如果多个客户端可能同时运行此客户端,则需要独占锁。但它看起来像是一个单用户的工作。