Postgresql

忽略重複插入的最佳方法?

  • December 2, 2016

背景

此問題與使用 PostgreSQL 9.2 或更高版本忽略重複插入有關。我問的原因是因為這段程式碼:

 -- Ignores duplicates.
 INSERT INTO
   db_table (tbl_column_1, tbl_column_2)
 VALUES (
   SELECT
     unnseted_column,
     param_association
   FROM
     unnest( param_array_ids ) AS unnested_column
 );

程式碼不受現有值檢查的阻礙。(在這種特殊情況下,使用者並不關心插入重複項的錯誤——插入應該“正常工作”。)在這種情況下添加程式碼來顯式測試重複項會帶來複雜性。

問題

在 PostgreSQL 中,我發現了一些忽略重複插入的方法。

忽略重複 #1

創建一個擷取唯一約束違規的事務,不採取任何措施:

 BEGIN
   INSERT INTO db_table (tbl_column) VALUES (v_tbl_column);
 EXCEPTION WHEN unique_violation THEN
   -- Ignore duplicate inserts.
 END;

忽略重複 #2

創建一個規則以忽略給定表上的重複項:

CREATE OR REPLACE RULE db_table_ignore_duplicate_inserts AS
   ON INSERT TO db_table
  WHERE (EXISTS ( SELECT 1
          FROM db_table
         WHERE db_table.tbl_column = NEW.tbl_column)) DO INSTEAD NOTHING;

問題

我的問題主要是學術性的:

  • 什麼方法最有效?
  • 哪種方法最可維護,為什麼?
  • 使用 PostgreSQL 忽略插入重複錯誤的標準方法是什麼?
  • 是否有一種技術上更有效的方法來忽略重複插入;如果有,那是什麼?

謝謝!

正如另一個問題的答案(這個問題被認為是重複的)提到的,有(從版本 9.5 開始)一個本機UPSERT功能。對於舊版本,請繼續閱讀 :)

我已經設置了一個測試來檢查選項。我將包含下面的程式碼,它可以在psqllinux/Unix 機器上執行(只是因為為了結果清晰起見,我將設置命令的輸出通過管道傳輸到/dev/null- 在 Windows 機器上,可以選擇一個日誌文件代替)。

我試圖通過對每種類型使用多個(即 100 個)從儲存過程中INSERT的循環執行來使不同的結果具有可比性。plpgsql此外,在每次執行之前,都會通過截斷並重新插入原始數據來重置表。

檢查一些測試執行,看起來使用規則並顯式添加WHERE NOT EXISTS語句INSERT花費相似的時間,而擷取異常需要更多時間才能完成。

後者並不令人驚訝

提示:包含 EXCEPTION 子句的塊的進入和退出比沒有的塊要昂貴得多。因此,不要在沒有必要的情況下使用 EXCEPTION。

就個人而言,由於可讀性和可維護性,我更喜歡將WHERE NOT EXISTS位添加到INSERTs 本身。就像觸發器(也可以在這裡測試)一樣,調試(或簡單地跟踪INSERT行為)在存在規則的情況下更加複雜。

以及我使用的程式碼(請隨意指出誤解或其他問題):

\o /dev/null
\timing off

-- set up data
DROP TABLE IF EXISTS insert_test;

CREATE TABLE insert_test_base_data (
   id integer PRIMARY KEY,
   col1 double precision,
   col2 text
);

CREATE TABLE insert_test (
   id integer PRIMARY KEY,
   col1 double precision,
   col2 text
);

INSERT INTO insert_test_base_data
SELECT i, (SELECT random() AS r WHERE s.i = s.i)
FROM 
   generate_series(2, 200, 2) s(i)
;

UPDATE insert_test_base_data
SET col2 = md5(col1::text)
;

INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;



-- function with exception block to be called later
CREATE OR REPLACE FUNCTION f_insert_test_insert(
   id integer,
   col1 double precision,
   col2 text
)
RETURNS void AS
$body$
BEGIN
   INSERT INTO insert_test
   VALUES ($1, $2, $3)
   ;
EXCEPTION
   WHEN unique_violation
   THEN NULL;
END;
$body$
LANGUAGE plpgsql;



-- function running plain SQL ... WHERE NOT EXISTS ...
CREATE OR REPLACE FUNCTION insert_test_where_not_exists()
RETURNS void AS
$body$
BEGIN
   FOR i IN 1 .. 100
   LOOP
       INSERT INTO insert_test
       SELECT i, rnd, md5(rnd::text)
       FROM (SELECT random() AS rnd) r
       WHERE NOT EXISTS (
           SELECT 1
           FROM insert_test
           WHERE id = i
       )
       ;
   END LOOP;
END;
$body$
LANGUAGE plpgsql;



-- call a function with exception block
CREATE OR REPLACE FUNCTION insert_test_function_with_exception_block()
RETURNS void AS
$body$
BEGIN
   FOR i IN 1 .. 100
   LOOP
       PERFORM f_insert_test_insert(i, rnd, md5(rnd::text))
       FROM (SELECT random() AS rnd) r
       ;
   END LOOP;
END;
$body$
LANGUAGE plpgsql;



-- leave checking existence to a rule
CREATE OR REPLACE FUNCTION insert_test_rule()
RETURNS void AS
$body$
BEGIN
   FOR i IN 1 .. 100
   LOOP
       INSERT INTO insert_test
       SELECT i, rnd, md5(rnd::text)
       FROM (SELECT random() AS rnd) r
       ;
   END LOOP;
END;
$body$
LANGUAGE plpgsql;



\o
\timing on


\echo 
\echo 'check before INSERT'

SELECT insert_test_where_not_exists();

\echo 



\o /dev/null

\timing off

TRUNCATE insert_test;

INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;

\timing on

\o

\echo 'catch unique-violation'

SELECT insert_test_function_with_exception_block();

\echo 
\echo 'implementing a RULE'

\o /dev/null
\timing off

TRUNCATE insert_test;

INSERT INTO insert_test
SELECT *
FROM insert_test_base_data
;

CREATE OR REPLACE RULE db_table_ignore_duplicate_inserts AS
   ON INSERT TO insert_test
   WHERE EXISTS ( 
       SELECT 1
       FROM insert_test
       WHERE id = NEW.id
   ) 
   DO INSTEAD NOTHING;

\o 
\timing on

SELECT insert_test_rule();

引用自:https://dba.stackexchange.com/questions/30499