ONCONFLICT句で複数のconflict_targetを使用する


93

テーブルcol1に2つの列がありcol2、どちらも一意のインデックスが付けられています(col1は一意であり、col2も一意です)。

このテーブルに挿入し、ON CONFLICT構文を使用して他の列を更新する必要がありますが、両方の列をconflict_target句で使用することはできません。

できます:

INSERT INTO table
...
ON CONFLICT ( col1 ) 
DO UPDATE 
SET 
-- update needed columns here

しかし、次のようないくつかの列に対してこれを行う方法:

...
ON CONFLICT ( col1, col2 )
DO UPDATE 
SET 
....

4
「col1、col2、どちらも一意のインデックスが付けられています。」それは、col1が一意で、col2が一意であることを意味しますか、それともcol1、col2の組み合わせが一意であることを意味しますか?
e4c5 2016年

1
つまり、col1は一意であり、col2は個別に一意であるということですか
Oto Shavadze 2016年

回答:


48

サンプルテーブルとデータ

CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
   CONSTRAINT col2_unique UNIQUE (col2)
);

INSERT INTO dupes values(1,1,'a'),(2,2,'b');

問題の再現

INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2

これをQ1と呼びましょう。結果は

ERROR:  duplicate key value violates unique constraint "col2_unique"
DETAIL:  Key (col2)=(2) already exists.

どのようなドキュメントが語ります

コンフリクトターゲットは、一意のインデックス推論を実行できます。推論を実行する場合、1つ以上のindex_column_name列やindex_expression式、およびオプションのindex_predicateで構成されます。順序に関係なく、conflict_targetで指定された列/式を正確に含むすべてのtable_name一意のインデックスは、アービターインデックスとして推測(選択)されます。index_predicateが指定されている場合、推論の追加要件として、アービターインデックスを満たす必要があります。

これは、次のクエリが機能するはずであるという印象を与えますが、実際にはcol1とcol2に一緒に一意のインデックスが必要になるため、機能しません。ただし、このようなインデックスは、col1とcol2が個別に一意になることを保証するものではありません。これはOPの要件の1つです。

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2

このクエリをQ2と呼びましょう(これは構文エラーで失敗します)

どうして?

Postgresqlがこのように動作するのは、2番目の列で競合が発生したときに何が起こるかが明確に定義されていないためです。いくつかの可能性があります。たとえば、上記のQ1クエリでcol1は、col2?で競合が発生した場合、postgresqlを更新する必要があります。しかし、それが別の紛争につながる場合はどうなりcol1ますか?postgresqlはそれをどのように処理することが期待されていますか?

解決策

解決策は、ONCONFLICTを昔ながらのUPSERTと組み合わせることです

CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
        IF found THEN
            RETURN;
        END IF;

        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently, or key2
        -- already exists in col2,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            BEGIN
                INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
                RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- Do nothing, and loop to try the UPDATE again.
            END;
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

このストアド関数のロジックを変更して、列を希望どおりに正確に更新する必要があります。次のように呼び出します

SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');

3
これは方法ですが、必要以上に作業/ロジックがあります。実際に行う必要があるのは、2つの列に一意の制約を作成することだけです。以下の私の答えを参照してください。
ジュベイル2016

一度に複数のVALUESセットを挿入する場合にも、merge_dbソリューションを使用できますか?
daniyel 2016

@daniyelストアド関数を書き直す必要があります
e4c5 2016

3
昔ながらのアップサートの使用を提案することがどのように役立つかは私にはわかりません。この質問は「postgresupsert9.5」でよく参照されており、すべてのconstraint_namesオプションで使用する方法を説明することでより良いかもしれません。
パック2016

3
@Pak質問をはっきりと読んでいないので、はっきりしません。オペレーションは、これらのフィールドで複合キーを探していません。他の答えは複合キーで機能します
e4c5 2016

65

ON CONFLICT競合検出を行うには、一意のインデックス*が必要です。したがって、両方の列に一意のインデックスを作成する必要があります。

t=# create table t (id integer, a text, b text);
CREATE TABLE
t=# create unique index idx_t_id_a on t (id, a);
CREATE INDEX
t=# insert into t values (1, 'a', 'foo');
INSERT 0 1
t=# insert into t values (1, 'a', 'bar') on conflict (id, a) do update set b = 'bar';
INSERT 0 1
t=# select * from t;
 id | a |  b  
----+---+-----
  1 | a | bar

*一意のインデックスに加えて、除外制約を使用することもできます。これらは、一意の制約よりも少し一般的です。テーブルにidvalid_time(およびvalid_timetsrange)の列があり、重複するidsを許可したいが、重複する期間は許可しないとします。一意の制約は役に立ちませんが、除外制約を使用すると、「新しいレコードidが古いレコードidと等しく、valid_time重複している場合は新しいレコードを除外する」と言うことができますvalid_time


4
これが作成するのは、一緒に一意のインデックスを作成し、t(id、a)に一意のインデックスidx_t_id_aを作成することです。もちろん、OPは、2つの列が個別に一意であるか一緒に一意であるかを明確に示していません。
e4c5 2016年

なぜpostgresはインデックスにちなんで名付けられた列がないと言って使用に失敗するのON CONFLICTですか?
パック2016

@Pak使用している特定のコマンドと、受け取ったエラーメッセージを使用して、独自の質問を作成する必要があるようです。
Paul A Jungwirth 2016

@PaulAJungwirthわかりませんが、あなたの答えは的を射ています-on conflictコマンドの制約としての一意のインデックスです。エラーは「列my_index_nameが存在しません」です。
パック

とにかく、OPが要求したときに、各列に個別の一意の制約を設定してこれを試しましたが、機能しませんでした。期待していたわけではありませんが、期待していました。
sudo

5

今日では不可能です(そうです)。ON CONFLICT 構文の最後のバージョンでは、句を繰り返すことも、CTEを使用することもできません。ONCONFLICTからINSERTを破って、競合ターゲットを追加することはできません。




1

ウラドは正しい考えを持った。

まず、列にテーブルの一意の制約を作成する必要があります。col1, col2 次に、作成すると、次のことができます。

INSERT INTO dupes values(3,2,'c') 
ON CONFLICT ON CONSTRAINT dupes_pkey 
DO UPDATE SET col3 = 'c', col2 = 2

4
申し訳ありませんが、あなたは質問を誤解しています。OPは、一緒に一意の制約を望んでいません。
e4c5 2016

1

ちょっとハッキーですが、col1とcol2の2つの値を新しい列col3(2つのインデックスのようなもの)に連結して比較することで、これを解決しました。これは、col1とcol2の両方に一致させる必要がある場合にのみ機能します。

INSERT INTO table
...
ON CONFLICT ( col3 ) 
DO UPDATE 
SET 
-- update needed columns here

ここで、col3 = col1とcol2の値の連結。


3
これらの2つの列に一意のインデックスを作成し、でその制約を指定できon conflictます。
KishoreRelangi19年

0

通常、(私が思うに)1つだけでステートメントを生成できます on conflict挿入するものに対して、関連性のある唯一の制約を指定できます。

通常、一度に1つの制約のみが「関連する」制約であるためです。(多ければ、何かが奇妙な/奇妙なデザインになっているのではないかと思います、うーん。)

例:
(ライセンス:CC0、唯一のCC-BY)

// there're these unique constraints:
//   unique (site_id, people_id, page_id)
//   unique (site_id, people_id, pages_in_whole_site)
//   unique (site_id, people_id, pages_in_category_id)
// and only *one* of page-id, category-id, whole-site-true/false
// can be specified. So only one constraint is "active", at a time.

val thingColumnName = thingColumnName(notfificationPreference)

val insertStatement = s"""
  insert into page_notf_prefs (
    site_id,
    people_id,
    notf_level,
    page_id,
    pages_in_whole_site,
    pages_in_category_id)
  values (?, ?, ?, ?, ?, ?)
  -- There can be only one on-conflict clause.
  on conflict (site_id, people_id, $thingColumnName)   <—— look
  do update set
    notf_level = excluded.notf_level
  """

val values = List(
  siteId.asAnyRef,
  notfPref.peopleId.asAnyRef,
  notfPref.notfLevel.toInt.asAnyRef,
  // Only one of these is non-null:
  notfPref.pageId.orNullVarchar,
  if (notfPref.wholeSite) true.asAnyRef else NullBoolean,
  notfPref.pagesInCategoryId.orNullInt)

runUpdateSingleRow(insertStatement, values)

そして:

private def thingColumnName(notfPref: PageNotfPref): String =
  if (notfPref.pageId.isDefined)
    "page_id"
  else if (notfPref.pagesInCategoryId.isDefined)
    "pages_in_category_id"
  else if (notfPref.wholeSite)
    "pages_in_whole_site"
  else
    die("TyE2ABK057")

on conflict句は、動的に私がやろうとしているものに応じて、生成されます。ページに通知設定を挿入する場合、site_id, people_id, page_id制約に固有の競合が発生する可能性があります。また、カテゴリに対して通知設定を構成している場合、代わりに、違反する可能性のある制約は次のようになります。site_id, people_id, category_idます。

ですから、あなたの場合、on conflict (... columns )私は正しいものを生成することができます。私が何をしたいのかがわかっているので、多くの固有の制約のどれが違反される可能性があるのか​​がわかります。


-4

ON CONFLICTは非常に不器用なソリューションであり、実行します

UPDATE dupes SET key1=$1, key2=$2 where key3=$3    
if rowcount > 0    
  INSERT dupes (key1, key2, key3) values ($1,$2,$3);

Oracle、Postgres、その他すべてのデータベースで動作します


これはアトミックではないため、同時に複数の接続がある場合、失敗して誤った結果を生成する可能性があります。
ボグダンマート
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.