使用INSERT / UPDATE的单个查询替换循环

时间:2012-11-05 13:44:27

标签: sql postgresql plpgsql upsert postgresql-performance

我正在PostgreSQL中编写一个函数。它基本上做了3个步骤:

  1. 从源表中获取记录。
  2. 检查目标表中获取记录的值,如果在目标表中找到记录,则用提取的记录更新目标表的所有值,否则将获取的记录插入目标表。
  3. 如果我为插入/更新写单个查询,而不是执行此循环,它会比上面提到的方法更快吗?如何通过编写单个查询而不是循环遍历每个记录并进行更新/插入来实现相同的结果。

    我目前的方法如下

    CREATE OR REPLACE FUNCTION fun1()
      RETURNS void AS
    $BODY$DECLARE
       source_tab_row RECORD;
    
       v_col1 TEXT;
       v_col2 TEXT;
       v_col3 TEXT;
       v_col4 double precision ;
       cnt integer;
    
    BEGIN
        FOR source_tab_row IN (SELECT * FROM source_tab where col5='abc')
    LOOP
        v_col1=source_tab_row.col1;   
        v_col2=source_tab_row.col2;
        v_col3=source_tab_row.col3;
        v_col4=source_tab_row.col4;
    
        select count(*) INTO cnt from dest_tab where col1=v_col1;
    
         if (cnt =0) then
         -- If records is not found
           INSERT INTO dest_tab(col1, col2, col3,col4)
           VALUES( v_col1, v_col2, v_col3,v_col4)   ;
        else
         --if records found then update it
           update dest_tab set col1=v_col1, col2=v_col2, col3=v_col3,col4=v_col4
           where col1=v_col1;
    
         end if;         
    END LOOP;
    END;
    $BODY$ LANGUAGE plpgsql;
    

1 个答案:

答案 0 :(得分:2)

更好的SQL

如果你有PostgreSQL 9.1或更高版本,你肯定应该使用data-modifying CTE

WITH x AS (
   UPDATE dest_tab d
   SET    col2 = s.col2
        , col3 = s.col3
   --   , ...
   FROM   source_tab s
   WHERE  s.col5 = 'abc'
   AND    s.col1 = d.col1

   RETURNING col1
   )
INSERT INTO dest_tab(col1, col2, col3, col4)
SELECT s.col1, s.col2, s.col3, s.col4
FROM   source_tab s
WHERE  s.col5 = 'abc'
LEFT   JOIN x USING (col1)
WHERE  x.col1 IS NULL;

正如@Craig已发布的那样,这些操作通常比基于集合的SQL快得多,而不是通过迭代单个行。

但是,这种形式更快更简单。它还在很大程度上避免了固有的(微小的!)竞争条件。首先,由于这是一个SQL命令,因此时隙更短。此外,如果并发事务应在UPDATEINSERT之间输入竞争行,则会出现重复的密钥违规(前提是您有一个pk / unique约束)。因为您不再次查询dest_tab并重新使用INSERT的原始集。更快,更好。

如果你看到重复的密钥违规:没有发生任何不好的事情,只需重试查询。

覆盖了相反的情况,在此情况下并发事务会DELETE行。这实际上是不那么重要/频繁的情况,IMO。

正确的plpgsql

如果 您使用plpgsql进行此操作,请简化:

CREATE OR REPLACE FUNCTION fun1()
  RETURNS void AS
$BODY$
DECLARE
   _source source_tab;  -- name of table = type
BEGIN
   FOR _source IN
      SELECT * FROM source_tab where col5 = 'abc'
   LOOP
        UPDATE dest_tab
        SET    col2 = _source.col2  -- don't update col1, it doesn't change
               ,col3 = _source.col3
               ,col4 = _source.col4
        WHERE  col1 = _source.col1;

      IF NOT FOUND THEN  -- no row found
         INSERT INTO dest_tab(col1, col2, col3,col4)
         VALUES (_source.col1, _source.col2, _source.col3, _source.col4);
      END IF;

   END LOOP;
END
$BODY$ LANGUAGE plpgsql;