テーブルcol1
、col2
には2つの列があり、両方とも一意のインデックスが付けられています(col1は一意であり、col2も一意です)。
このテーブルへの挿入時にON CONFLICT
構文を使用して他の列を更新する必要がありますが、conflict_target
clauseで両方の列を使用することはできません。
できます:
INSERT INTO table
...
ON CONFLICT ( col1 )
DO UPDATE
SET
-- update needed columns here
しかし、次のようないくつかの列に対してこれを行う方法:
...
ON CONFLICT ( col1, col2 )
DO UPDATE
SET
....
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');
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_time
がtsrange
である)の列があり、重複するid
sを許可したいが、期間が重複していないとします。一意の制約は役に立ちませんが、除外制約を使用すると、「id
が古いid
と等しく、valid_time
がvalid_time
と重複する場合、新しいレコードを除外できます」と言うことができます。
または
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;
Vladは正しいアイデアを思いついた。
最初に、列col1, col2
にテーブル一意制約を作成する必要があります。その後、次の操作を実行できます。
INSERT INTO dupes values(3,2,'c')
ON CONFLICT ON CONSTRAINT dupes_pkey
DO UPDATE SET col3 = 'c', col2 = 2
一種のハックですが、col1とcol2の2つの値を新しい列col3(2つのインデックスのようなもの)に連結して比較し、これを解決しました。これは、col1とcol2の両方に一致させる必要がある場合にのみ機能します。
INSERT INTO table
...
ON CONFLICT ( col3 )
DO UPDATE
SET
-- update needed columns here
ここで、col3 = col1とcol2の値の連結。
通常、挿入するものに関連する唯一の制約を指定する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を知っているので、どの単一のものを知っているのですか多くのユニークな制約のうち、違反する可能性があるものです。
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、および他のすべてのデータベースで動作します