Postgresql批量插入或忽略

时间:2012-08-09 19:28:58

标签: sql postgresql plpgsql postgresql-9.1 duplicate-removal

我有责任将代码从sqlite切换到postgres。我遇到问题的其中一个问题将在下面复制。

INSERT INTO group_phones(group_id, phone_name)
SELECT g.id, p.name 
FROM phones AS p, groups as g
WHERE g.id IN ($add_groups) AND p.name IN ($phones);

当存在重复记录时会出现问题。在此表中,两个值的组合必须是唯一的。我在其他地方使用了一些plpgsql函数来执行更新或插入操作,但在这种情况下,我可以一次执行多个插入操作。我不知道如何为此编写存储例程。感谢所有sql专家的帮助!

2 个答案:

答案 0 :(得分:11)

3 挑战。

  1. 您的查询在表JOINphones之间没有 groups条件,这实际上是有限的CROSS JOIN - 其中你很可能不打算。即每个符合条件的手机都会与符合条件的每个组合在一起。如果您有100部手机和100组已经有10,000种组合。

  2. 插入(group_id, phone_name)

  3. 不同组合
  4. 避免在表格group_phones中插入已存在的行。

  5. 所有事情都认为如下:

    INSERT INTO group_phones(group_id, phone_name)
    SELECT i.id, i.name
    FROM  (
        SELECT DISTINCT g.id, p.name -- get distinct combinations
        FROM   phones p
        JOIN   groups g ON ??how are p & g connected??
        WHERE  g.id IN ($add_groups)
        AND    p.name IN ($phones)
        ) i
    LEFT   JOIN group_phones gp ON (gp.group_id, gp.phone_name) = (i.id, i.name)
    WHERE  gp.group_id IS NULL  -- avoid duping existing rows
    

    并发

    此表单最大限度地减少了并发写操作的竞争条件。 如果您的表格有重度并发写入加载,您可能需要lock the table exclusively或使用serializable transaction isolation,这可以防止出现极不可能的情况在约束验证(行不存在)和查询中的写操作之间的微小时隙中由并发事务更改。

    BEGIN ISOLATION LEVEL SERIALIZABLE;
    INSERT ...
    COMMIT;
    

    如果事务以序列化错误回滚,请准备重复该事务。 有关该主题的更多信息,可以选择blog post by @depesz或此related question on SO

    但是,通常情况下,你甚至不用担心这一点。

    性能

    LEFT JOIN tbl ON right_col = left_col WHERE right_col IS NULL
    

    通常是右表中具有不同列的最快方法。如果列中有欺骗(特别是有很多),

    WHERE NOT EXISTS (SELECT 1 FROM tbl WHERE right_col = left_col)
    

    可能会更快,因为一旦找到第一行就可以停止扫描。

    你也可以使用IN,就像@dezso演示一样,但它在PostgreSQL中通常会更慢。

答案 1 :(得分:2)

尝试以下方法:

INSERT INTO group_phones(group_id, phone_name)
SELECT DISTINCT g.id, p.name 
FROM phones AS p, groups as g
WHERE 
    g.id IN ($add_groups) 
    AND p.name IN ($phones)
    AND (g.id, p.name) NOT IN (
        SELECT group_id, phone_name
        FROM group_phones
    )
;

使用DISTINCT可以确保插入唯一的行,并使用NOT IN子句排除已存在的行。

注意虽然这个解决方案可能更容易理解,但在大多数情况下,Erwin的表现会更好。