web-dev-qa-db-ja.com

テーブル内の行を更新または存在しない場合に挿入するにはどうすればよいですか?

次のカウンターの表があります。

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

カウンターの1つをインクリメントするか、対応する行がまだ存在しない場合はゼロに設定します。標準SQLで並行性の問題なしにこれを行う方法はありますか?操作はトランザクションの一部である場合があり、場合によっては別個です。

可能であれば、SQLite、PostgreSQL、MySQLでSQLを変更せずに実行する必要があります。

検索により、並行性の問題があるか、データベースに固有のいくつかのアイデアが得られました。

  • 新しい行をINSERT、エラーが発生した場合はUPDATEを試してください。残念ながら、INSERTのエラーは現在のトランザクションを中止します。

  • UPDATE行。変更された行がない場合は、INSERT新しい行。

  • MySQLにはON DUPLICATE KEY UPDATE句。

編集:すべての偉大な返信いただきありがとうございます。ポールは正しいように見えますが、これを行うための単一のポータブルな方法はありません。それは非常に基本的な操作のように聞こえるので、それは非常に驚くべきことです。

80
Remy Blank

MySQL(およびその後のSQLite)は、REPLACE INTO構文もサポートします。

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

これにより、主キーが自動的に識別され、更新する一致する行が検索され、見つからない場合は新しい行が挿入されます。

133
andygeers

SQLiteは 行の置換 が既に存在する場合をサポートします:

INSERT OR REPLACE INTO [...blah...]

これを短くすることができます

REPLACE INTO [...blah...]

このショートカットは、MySQL REPLACE INTO式。

31
Kyle Cronin

私は次のようなことをします:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

コードまたはSQLで世代値を0に設定しますが、ON DUP ...を使用して値をインクリメントします。とにかくそれが構文だと思います。

20
jmoz

oN DUPLICATE KEY UPDATE句は最良の解決策です。REPLACEはDELETEに続いてINSERTを実行するため、非常にわずかな期間レコードが削除され、ページがREPLACEクエリ中に表示されます。

そのため、INSERT ... ON DUPLICATE UPDATE ...を使用します。

jmozのソリューションが最適です。ただし、括弧よりもSET構文の方が好きですが

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
9
Fire Crow

プラットフォームに中立なソリューションを見つけることになるのかわかりません。

これは一般に「UPSERT」と呼ばれます。

関連する議論をご覧ください。

8
BradC

PostgreSQLにはマージコマンドはなく、実際にそれを記述することは簡単ではありません。実際、タスクを「興味深い」ものにするEdgeの奇妙なケースがあります。

最善の(最も可能性の高い条件での作業など)アプローチは、 manual (merge_db)に示されているような関数を使用することです。

関数を使用したくない場合は、通常はを回避できます:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

それはフォールトプルーフではなく、最終的に失敗することを忘れないでください。

5
user80168

標準SQLは、このタスクにMERGEステートメントを提供します。すべてのDBMSがMERGEステートメントをサポートしているわけではありません。

4

SQLを作成するライブラリを使用しても問題ない場合は、 psert (現在Ruby and Python =のみ):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

これは、MySQL、Postgres、およびSQLite3で機能します。

ストアドプロシージャまたはユーザー定義関数(UDF)をMySQLおよびPostgresに書き込みます。それは使用しています INSERT OR REPLACE SQLite3で。

0
Seamus Abshere

アトミックに更新または挿入する一般的な方法がない場合(たとえば、トランザクションを介して)、別のロックスキームにフォールバックできます。 0バイトのファイル、システムミューテックス、名前付きパイプなど...

0
Arnshea

挿入トリガーを使用できますか?失敗した場合は、更新を行います。

0
Michael Todd