この問題は、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では、重複した挿入を無視する方法をいくつか見つけました。
アクションを実行せずに、一意の制約違反をキャッチするトランザクションを作成します。
BEGIN
INSERT INTO db_table (tbl_column) VALUES (v_tbl_column);
EXCEPTION WHEN unique_violation THEN
-- Ignore duplicate inserts.
END;
特定のテーブルの重複を無視するルールを作成します。
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;
私の質問はほとんどが学術的です:
ありがとうございました!
他の質問(これは重複と見なされます)に対する回答として、(バージョン9.5以降)ネイティブ UPSERT
機能があります。古いバージョンについては、読み続けてください:)
オプションを確認するためのテストを設定しました。以下のコードを含めます。これは、Linux/Unixボックスのpsql
で実行できます(結果を明確にするために、セットアップコマンドの出力をパイプラインで/dev/null
にパイプしました。 Windowsボックスでは、代わりにログファイルを選択できます)。
INSERT
ストアドプロシージャ内のループから実行する、タイプごとに複数(つまり100)のplpgsql
を使用して、異なる結果を比較できるようにしました。さらに、各実行前に、元のデータを切り捨てて再挿入することにより、テーブルがリセットされます。
いくつかのテストの実行を確認すると、ルールを使用してWHERE NOT EXISTS
にINSERT
ステートメントを明示的に追加すると、同様の時間が費やされますが、例外のキャッチにはかなり長い時間がかかります。
後者はそうではありません 意外 :
ヒント:EXCEPTION句を含むブロックは、EXCEPTION句を含まないブロックに比べて、出入りにかなりの費用がかかります。したがって、必要がない場合はEXCEPTIONを使用しないでください。
個人的には、読みやすさと保守性のため、INSERT
s自体にWHERE NOT EXISTS
ビットを追加することを好みます。トリガー(ここでもテストできます)と同様に、デバッグ(または単に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();