忽略重複插入的最佳方法?
背景
此問題與使用 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
功能。對於舊版本,請繼續閱讀 :)我已經設置了一個測試來檢查選項。我將包含下面的程式碼,它可以在
psql
linux/Unix 機器上執行(只是因為為了結果清晰起見,我將設置命令的輸出通過管道傳輸到/dev/null
- 在 Windows 機器上,可以選擇一個日誌文件代替)。我試圖通過對每種類型使用多個(即 100 個)從儲存過程中
INSERT
的循環執行來使不同的結果具有可比性。plpgsql
此外,在每次執行之前,都會通過截斷並重新插入原始數據來重置表。檢查一些測試執行,看起來使用規則並顯式添加
WHERE NOT EXISTS
語句INSERT
花費相似的時間,而擷取異常需要更多時間才能完成。後者並不令人驚訝:
提示:包含 EXCEPTION 子句的塊的進入和退出比沒有的塊要昂貴得多。因此,不要在沒有必要的情況下使用 EXCEPTION。
就個人而言,由於可讀性和可維護性,我更喜歡將
WHERE NOT EXISTS
位添加到INSERT
s 本身。就像觸發器(也可以在這裡測試)一樣,調試(或簡單地跟踪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();