2016-10-29 19 views
9

私は非常に頻繁に一意の制約を持つテーブルから行を取得する必要があり、存在しない場合は作成して戻ります。 たとえば、私のテーブルには、次のようになります。更新を必要とせずにINSERTからON CONFLICTを返す

CREATE TABLE names(
    id SERIAL PRIMARY KEY, 
    name TEXT, 
    CONSTRAINT names_name_key UNIQUE (name) 
); 

そして、それは含まれています

id | name 
1 | bob 
2 | alice 

その後、私はしたいと思います:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT DO NOTHING RETURNING id; 

それとも:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT (name) DO NOTHING RETURNING id 

ボブのIDを返すようにする1。ただし、RETURNINGは、挿入または更新された行のみを返します。したがって、上記の例では、何も返されません。それを望むように機能させるためには、実際には次のことが必要です。

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE 
SET name = 'bob' 
RETURNING id; 

これは厄介なようです。私の質問は次の通りです:

  1. (私の)希望の行動を許さない理由は何ですか?

  2. もっとエレガントな方法がありますか?

答えて

9

それに関連し、SELECT or INSERTの定期的な問題だ(しかし異なる)UPSERT。 Postgres 9.5の新しいUPSERT機能は、まだ道具です。

WITH ins AS (
    INSERT INTO names(name) 
    VALUES ('bob') 
    ON  CONFLICT ON CONSTRAINT names_name_key DO UPDATE 
    SET name = NULL 
    WHERE FALSE  -- never executed, but locks the row 
    RETURNING id 
    ) 
SELECT id FROM ins 
UNION ALL 
SELECT id FROM names 
WHERE name = 'bob' -- only executed if no INSERT 
LIMIT 1; 

この方法では、実際には新しい行バージョンを必要なく作成することはありません。私はあなたがPostgresの中のすべてのUPDATEことを知っていると仮定し

は、そのMVCC modelによる行の新しいバージョンを書き込み - nameは前と同じ値に設定されている場合でも。これにより、操作がより高価になり、特定の状況で同時実行性の問題/ロック競合が発生し、さらにテーブルが肥大化する可能性があります。

詳細説明とどのように関数にこれをラップする:

?あなたが行をロックする必要はありませんし、簡素化することができます

(別のセッションから)同時UPDATEまたはDELETEが不可能な場合は、次の

WITH ins AS (
    INSERT INTO names(name) 
    VALUES ('bob') 
    ON  CONFLICT ON CONSTRAINT names_name_key DO NOTHING -- no lock needed 
    RETURNING id 
    ) 
SELECT id FROM ins 
UNION ALL 
SELECT id FROM names 
WHERE name = 'bob' -- only executed if no INSERT 
LIMIT 1; 
+0

をいただき、ありがとうございます回答!私はこれを "より良い"方法のように思えますが、私が記述した方法と実際的な違いは何ですか? – ira

+0

@ira:上記の説明をいくつか追加しました。 –

+0

@ErwinBrandstetter - これは常にIDを返しますか?私は試して、それは動作していないようだ - https://stackoverflow.com/q/46586793/435563 - おそらく私は何か間違っている? – shaunc

関連する問題