postgresqlを使用して「挿入無視」および「重複キー更新時」(SQLマージ)をエミュレートする方法


140

一部のSQLサーバーには、INSERT主キー/一意キーの制約に違反する場合にスキップされる機能があります。たとえば、MySQLにはがありINSERT IGNOREます。

エミュレートするための最良の方法は何だINSERT IGNOREON DUPLICATE KEY UPDATEPostgreSQLのでは?




6
9.5のように、それはネイティブ可能です:stackoverflow.com/a/34639631/4418
ウォーレン

MySQLのON DUPLICATE KEY UPDATEエミュレーション:PgSQL 9.5では、PgSQLでON CLAUSE同等の制約名を指定する必要があるため、MySQLが制約を定義せずにキャプチャできるので、依然としていくらか不可能です。これにより、クエリを書き直さずにこの機能を「エミュレート」できなくなります。
NeverEndingQueue 2018年

回答:


35

更新してみてください。行を変更しない場合は、その行が存在しなかったことを意味するため、挿入を実行します。明らかに、これはトランザクション内で行います。

もちろん、追加のコードをクライアント側に配置したくない場合は、これを関数でラップできます。また、その考えでは非常にまれな競合状態のループも必要です。

ドキュメントにこれの例があります:http : //www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html、例40-2右下。

通常は、これが最も簡単な方法です。ルールを使って魔法をかけることはできますが、かなり面倒になるでしょう。私はいつでも関数をラップするアプローチをお勧めします。

これは、単一行または少数行の値に対して機能します。たとえばサブクエリからの大量の行を処理する場合は、INSERTとUPDATEの2つのクエリに分割するのが最善です(もちろん、適切な結合/副選択として-メインを記述する必要はありません) 2回フィルター)


4
「大量の行を処理する場合」それはまさに私の場合です。行を一括で更新/挿入したいのですが、mysqlを使用すると、ループせずに1つのクエリだけでこれを実行できます。今私はこれがpostgresqlでも可能かどうかを疑問視します:1つのクエリだけを使用して一括更新または挿入します。あなたは「あなたはそれを2つのクエリに分割するのが最善です、1つはINSERT用、もう1つはUPDATE用」ですが、重複キーでエラーをスローしない挿入をどのように実行できますか?(例:「INSERT IGNORE」)
gpilotino '06 / 06/18

4
Magnusは次のようなクエリを使用することを意味しました:「トランザクションを開始する;一時テーブルtemp_tableをselect * from test where falseとして作成する; temp_tableを 'data_file.csv'からコピーする;テーブルをロックするテスト; update test set data = temporary_table.data from temporary_table where test.id = temporary_table.id;テストに挿入select * from temporary_table where id not in(select id from test)as a "
Tometzky

25
更新: PostgreSQL 9.5では、これはと同じくらい簡単になりましたINSERT ... ON CONFLICT DO NOTHING;。回答stackoverflow.com/a/34639631/2091700も参照してください。
Alphaaa

重要なのは、SQL標準ではMERGEありませんあなたが取る場合を除き、同時性、安全アップサートLOCK TABLE最初。人々はそれをそのように使用しますが、それは間違っています。
クレイグリンガー

1
v9.5では、これが「ネイティブ」機能になりました。@ Alphaaaのコメントをチェックしてください(回答を宣伝するコメントを宣伝しているだけです)
Camilo Delvasto

178

PostgreSQL 9.5では、これはネイティブ機能ですMySQLが数年前から持っていたように):

挿入...競合時に何もしない/更新しない(「アップサート」)

9.5では、「UPSERT」操作がサポートされています。INSERTは、ON CONFLICT DO UPDATE / IGNORE句を受け入れるように拡張されています。この句は、重複違反の可能性がある場合に実行する代替アクションを指定します。

...

新しい構文のさらなる例:

INSERT INTO user_logins (username, logins)
VALUES ('Naomi',1),('James',1) 
ON CONFLICT (username)
DO UPDATE SET logins = user_logins.logins + EXCLUDED.logins;

100

編集:ウォーレンの答えを見逃した場合に備えて、PG9.5はこれをネイティブで備えています。アップグレードする時間です!


ビルカーウィンの答えに基づいて、ルールベースのアプローチがどのように見えるかを詳しく説明します(同じDB内の別のスキーマから転送し、複数列の主キーを使用):

CREATE RULE "my_table_on_duplicate_ignore" AS ON INSERT TO "my_table"
  WHERE EXISTS(SELECT 1 FROM my_table 
                WHERE (pk_col_1, pk_col_2)=(NEW.pk_col_1, NEW.pk_col_2))
  DO INSTEAD NOTHING;
INSERT INTO my_table SELECT * FROM another_schema.my_table WHERE some_cond;
DROP RULE "my_table_on_duplicate_ignore" ON "my_table";

注:INSERTルールは、ルールが削除されるまですべての操作に適用されるため、アドホックではありません。


@sema another_schema.my_tableは、my_table
EoghanM 14

2
@EoghanM私はpostgresql 9.3でルールをテストしましたが、たとえばINSERT INTO "my_table"(a、b)、(a、b);のような複数の行挿入ステートメントで重複を挿入することもできました。(その行(a、b)が「my_table」にまだ存在していないと仮定します。)
sema

@sema、gotcha-つまり、ルールは最初に挿入されるすべてのデータに対して実行され、各行が挿入された後に再実行されないことを意味します。1つのアプローチは、最初に制約のない別の一時テーブルにデータを挿入し、次にそれを実行することですINSERT INTO "my_table" SELECT DISTINCT ON (pk_col_1, pk_col_2) * FROM the_tmp_table;
EoghanM

@EoghanM別のアプローチは、重複制約を一時的に緩和し、挿入時に重複を受け入れるが、後で重複を削除することですDELETE FROM my_table WHERE ctid IN (SELECT ctid FROM (SELECT ctid,ROW_NUMBER() OVER (PARTITION BY pk_col_1,pk_col_2) AS rn FROM my_table) AS dups WHERE dups.rn > 1);
sema

@semaで説明されている問題が発生しています。(a、b)、(a、b)を挿入すると、エラーがスローされます。この場合も、エラーを抑制する方法はありますか?
ディオゴメロ2014

35

Postgres 9.5以降を使用している場合は、新しいON CONFLICT DO NOTHING構文が機能するはずです。

INSERT INTO target_table (field_one, field_two, field_three ) 
SELECT field_one, field_two, field_three
FROM source_table
ON CONFLICT (field_one) DO NOTHING;

以前のバージョンを持っている私たちにとっては、この正しい結合が代わりに機能します:

INSERT INTO target_table (field_one, field_two, field_three )
SELECT source_table.field_one, source_table.field_two, source_table.field_three
FROM source_table 
LEFT JOIN target_table ON source_table.field_one = target_table.field_one
WHERE target_table.field_one IS NULL;

並行環境で大きな挿入を行う場合、2番目のアプローチは機能しません。このクエリの実行中に別の行が挿入されたUnique violation: 7 ERROR: duplicate key value violates unique constraintときに、キーが実際に互いに重複している場合に取得します。ロックは役立つと思いますが、並行性は明らかに低下します。target_tabletarget_table
G.カシュタノフ2018年

1
ON CONFLICT (field_one) DO NOTHING答えの最良の部分です。
Abel Callejo

24

挿入無視ロジックを取得するには、以下のようにします。リテラル値の選択ステートメントから単に挿入するのが最も効果的であることがわかりました。次に、NOT EXISTS句を使用して重複するキーをマスクできます。重複ロジックの更新を取得するには、pl / pgsqlループが必要だと思います。

INSERT INTO manager.vin_manufacturer
(SELECT * FROM( VALUES
  ('935',' Citroën Brazil','Citroën'),
  ('ABC', 'Toyota', 'Toyota'),
  ('ZOM',' OM','OM')
  ) as tmp (vin_manufacturer_id, manufacturer_desc, make_desc)
  WHERE NOT EXISTS (
    --ignore anything that has already been inserted
    SELECT 1 FROM manager.vin_manufacturer m where m.vin_manufacturer_id = tmp.vin_manufacturer_id)
)

tmpに重複する行が含まれている場合はどうなりますか?
Henley Chiu

いつでも個別のキーワードで選択できます。
Keyo 2013

5
参考までに、「WHERE NOT EXISTS」トリックは複数のトランザクションにわたって機能しません。これは、異なるトランザクションが他のトランザクションから新しく追加されたデータを認識できないためです。
Dave Johansen

21
INSERT INTO mytable(col1,col2) 
    SELECT 'val1','val2' 
    WHERE NOT EXISTS (SELECT 1 FROM mytable WHERE col1='val1')

同じことをすべて実行しようとする複数のトランザクションの影響は何ですか?存在しないwhere実行と他のトランザクションを実行する挿入の間に行が挿入される可能性はありますか?そして、もしPostgresがそれを防ぐことができるなら、postgresがこれにぶつかったときに、それらすべてのトランザクション間で同期のポイントを導入していませんか?
Καrτhικ

新しく追加されたデータは他のトランザクションから見えないため、これは複数のトランザクションでは機能しません。
Dave Johansen、2015年

12

PostgreSQLは、ルールと呼ばれるスキーマオブジェクトをサポートしているようです。

http://www.postgresql.org/docs/current/static/rules-update.html

ON INSERT特定のテーブルにルールを作成NOTHINGして、特定の主キー値を持つ行が存在する場合にルールを作成するか、特定の主キー値を持つ行が存在する場合にUPDATE代わりにルールを作成することができますINSERT

私はこれを自分で試したことがないので、経験から話すことも、例を示すこともできません。


1
私がよく理解していれば、これらのルールはステートメントが呼び出されるたびに実行されるトリガーです。1つのクエリのみにルールを適用する場合はどうなりますか?ルールを作成してすぐに破棄する必要がありますか?(レース状況はどうですか?)
gpilotino

3
はい、同じ質問があります。ルールメカニズムは、PostgreSQLでMySQLのINSERT IGNOREまたはON DUPLICATE KEY UPDATEに最も近いものです。「重複キー更新時のpostgresql」をGoogleで検索すると、ルールはアドホックベースだけでなく任意のINSERTに適用されるとしても、他の人々がルールメカニズムを推奨していることがわかります。
ビルカーウィン、

4
PostgreSQLはトランザクションDDLをサポートしています。つまり、ルールを作成してそれを単一のトランザクション内にドロップした場合、ルールはそのトランザクションの外部で表示されることはありません(そのため、そのトランザクションの外部では影響がありません)。
cdhowie 2015年

6

@hanmariがコメントで述べたように。postgresテーブルに挿入する場合、on conflict(..)は何も実行せず、重複データを挿入しないために使用するのに最適なコードです。

query = "INSERT INTO db_table_name(column_name)
         VALUES(%s) ON CONFLICT (column_name) DO NOTHING;"

コードのON CONFLICT行を使用すると、挿入ステートメントで引き続きデータの行を挿入できます。クエリと値のコードは、Excelからpostgres dbテーブルに挿入された日付の例です。IDフィールドが一意であることを確認するために使用するpostgresテーブルに制約を追加しました。同じデータ行で削除を実行する代わりに、1から始まるID列の番号を付け直すSQLコードの行を追加します。例:

q = 'ALTER id_column serial RESTART WITH 1'

データにIDフィールドがある場合、これをプライマリID /シリアルIDとして使用せず、ID列を作成してシリアルに設定します。この情報が皆様のお役に立てば幸いです。*ソフトウェア開発/コーディングの学位はありません。コーディングで知っていることはすべて自分で勉強します。


これは複合一意インデックスでは機能しません!
Nulik

4

このソリューションでは、ルールの使用を回避します。

BEGIN
   INSERT INTO tableA (unique_column,c2,c3) VALUES (1,2,3);
EXCEPTION 
   WHEN unique_violation THEN
     UPDATE tableA SET c2 = 2, c3 = 3 WHERE unique_column = 1;
END;

ただし、パフォーマンス上の欠点があります(PostgreSQL.orgを参照)。

EXCEPTION句を含むブロックは、EXCEPTION句を含まないブロックに比べて、出入りにかなりの費用がかかります。したがって、必要がない場合はEXCEPTIONを使用しないでください。


1

一括で、挿入の前にいつでも行を削除できます。存在しない行を削除してもエラーは発生しないため、安全にスキップされます。


2
このアプローチは奇妙な競合状態になりやすいので、お勧めしません...
Steven Schlansker

1
+1これは簡単で汎用的です。注意して使用すると、これは実際には簡単な解決策になります。
Wouter van Nifterick 2012年

1
また、既存のデータが挿入後に変更されている(ただし、重複キーではない)場合は機能せず、更新を保持します。これは、実稼働、QA、開発、およびテストシステムで実行されるdb更新など、わずかに異なる複数のシステム用に記述されたSQLスクリプトがある場合のシナリオです。
Hanno Fietz

1
DEFERRABLE INITIALLY DEFERREDフラグ付きで作成した場合、外部キーは問題にならない可能性があります。
手本

-1

データインポートスクリプトの場合、ある方法で「IF NOT EXISTS」を置き換えるには、それでも機能するわずかに扱いにくい定式があります。

DO
$do$
BEGIN
PERFORM id
FROM whatever_table;

IF NOT FOUND THEN
-- INSERT stuff
END IF;
END
$do$;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.