【问题标题】:Replace looping with a single query for INSERT / UPDATE用 INSERT / UPDATE 的单个查询替换循环
【发布时间】:2012-11-05 13:44:27
【问题描述】:

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

  1. 从源表中获取记录。
  2. 检查目标表中获取的记录的值,如果在目标表中找到记录,则用获取的记录更新目标表的所有值,否则将获取的记录插入目标表。

如果我为插入/更新编写单个查询,而不是执行此循环,它会比上述方法更快吗?如何通过编写单个查询而不是循环遍历每条记录并进行更新/插入来获得相同的结果。

我目前的做法如下

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;

【问题讨论】:

    标签: sql postgresql plpgsql upsert postgresql-performance


    【解决方案1】:

    更好的 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 / 唯一约束)。因为您没有第二次查询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;
    

    【讨论】:

    • 呸,你说得对UPDATE。晚上教我发帖。
    猜你喜欢
    • 1970-01-01
    • 2013-11-11
    • 1970-01-01
    • 2021-10-21
    • 2014-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-10
    相关资源
    最近更新 更多