web-dev-qa-db-ja.com

ON CONFLICT句で複数のconflict_targetを使用します

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

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

できます:

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

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

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

サンプル表とデータ

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.

ドキュメント の内容

conflict_targetは、一意のインデックス推論を実行できます。推論を実行する場合、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番目の列で競合が発生したときに何が起こるかが明確に定義されていないためです。多くの可能性があります。たとえば、上記の第1四半期のクエリでは、col1に競合がある場合にcol2を更新する必要がありますか?しかし、それがcol1で別の競合につながる場合はどうでしょうか? postgresqlはどのようにそれを処理することが期待されていますか?

解決策

解決策は、ON CONFLICTと 旧式の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');
31
e4c5

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

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

44

この頃は不可能です。 ON CONFLICTsyntax の最後のバージョンでは、句を繰り返すことも、 CTE を使用することもできません。ONCONFLICTからINSERTを破壊することはできません。さらに競合ターゲットを追加します。

4
Peter Krauss
  1. 制約(外部インデックスなど)を作成します。

または

  1. 既存の制約(psqの\ d)を見てください。
  2. INSERT句でON CONSTRAINT(constraint_name)を使用します。

Postgres 9.5を使用している場合、EXCLUDEDスペースを使用できます。

PostgreSQL 9.5の新機能 からの例:

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

Vladは正しいアイデアを思いついた。

最初に、列col1, col2にテーブル一意制約を作成する必要があります。その後、次の操作を実行できます。

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

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

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

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

0
Niko Dunk

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

通常、一度に「関連する」制約は1つだけです。 (もし多くの場合、私は何かが奇妙な/奇妙に設計されているかどうか疑問に思う、うーん。)

例:
(ライセンス:NotCC0、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 )を生成できます。私は何をすべきかwantを知っているので、どの単一のものを知っているのですか多くのユニークな制約のうち、違反する可能性があるものです。

0
KajMagnus

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、および他のすべてのデータベースで動作します

0
user2625834