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


83

私は次のカウンターの表を持っています:

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

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

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

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

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

  • UPDATEINSERT行。行が変更されていない場合は、新しい行。

  • MySQLにはON DUPLICATE KEY UPDATE句があります。

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


6
これらすべてのRDBMSで機能する単一のソリューションを見つけることはできません。ごめんなさい。
Paul Tomblin


回答:


137

MySQL(およびその後のSQLite)もREPLACEINTO構文をサポートしています。

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

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


13
実際には、具体的には、MySQLのREPLACEは常に挿入を行いますが、行がすでに存在する場合は、最初に行を削除します。dev.mysql.com/doc/refman/4.1/en/replace.html
Evan

90
これは挿入+削除であり、決して更新されないことを理解することが重要です。この結果、置換を行うときは常に、すべてのフィールドのデータを含める必要があることを確認する必要があります。
Zoredache 2009年

2
@Zoredache技術的には、これは '削除してから挿入'です。a(n) '挿入+削除'は事実上削除と同じですが、それは髪の毛を分割します。
Agi Hammerthief 2014

2
@Agihammerthiefには非常に現実的な違いがあります。つまり、新しく挿入された行には、削除された行と同じ主キーがありません。ON DUPLICATEを使用すると、同じ主キーになります(特に変更しない限り)。
Tim Strijdhorst 2016

32

SQLiteは、行がすでに存在する場合、それを置き換えることをサポートしています。

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

これを次のように短縮できます

REPLACE INTO [...blah...]

このショートカットは、MySQLREPLACE INTO式と互換性があるように追加されました。


PRAMARY KEY値にaを定義する必要があります。
DawnSong 2016年

24

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

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

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


1
この答えは正直に選択された答えでなければなりません。すべてのフィールドをエントリに送信しない場合、これは非破壊編集です。
ヨルダン

9

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)
;

4
REPLACEはアトミックであるため、行が存在しない期間はありません。
ブリリアン


5

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

最良の(可能な限りの条件での作業のように)アプローチは、マニュアル(merge_db)に示されているような関数を使用することです。

関数を使用したくない場合は、通常、次の方法で回避できます。

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

これは障害防止でなく、最終的に失敗すること覚えておいてください。


4

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


0

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


0

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


コマンドが機能したときに、トリガー(少なくともPostgreSQLでは)が実行されています。つまり、基本コマンドが失敗したときに実行されるトリガーを持つことはできません。

0

SQLを作成するライブラリを使用しても問題がない場合は、Upsert(現在はRubyとPythonのみ)を使用できます。

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

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

MySQLとPostgresでストアドプロシージャまたはユーザー定義関数(UDF)を記述します。INSERT OR REPLACESQLite3で使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.