117

SQLite データベースに対して UPSERT / INSERT OR UPDATE を実行する必要があります。

多くの場合に役立つ INSERT OR REPLACE コマンドがあります。ただし、外部キーのために自動インクリメントを使用してIDを保持したい場合は、行を削除して新しい行を作成し、その結果、この新しい行に新しいIDがあるため、機能しません。

これはテーブルになります:

プレーヤー - (id の主キー、user_name は一意)

|  id   | user_name |  age   |
------------------------------
|  1982 |   johnny  |  23    |
|  1983 |   steven  |  29    |
|  1984 |   pepee   |  40    |
4

8 に答える 8

112

質疑応答スタイル

何時間にもわたって問題を調査し、格闘した結果、テーブルの構造と、整合性を維持するために外部キー制限を有効にしている場合に応じて、これを達成する方法が 2 つあることを発見しました。私の状況にあるかもしれない人々に時間を節約するために、これをきれいな形式で共有したいと思います.


オプション 1: 行を削除する余裕がある

つまり、外部キーを持っていないか、持っている場合は、整合性の例外がないように SQLite エンジンが構成されています。行く方法はINSERT OR REPLACEです。ID が既に存在するプレイヤーを挿入/更新しようとすると、SQLite エンジンはその行を削除し、提供するデータを挿入します。問題は、古い ID を関連付けたままにするにはどうすればよいかということです。

データ user_name='steven' および age=32 でUPSERTしたいとしましょう。

このコードを見てください:

INSERT INTO players (id, name, age)

VALUES (
    coalesce((select id from players where user_name='steven'),
             (select max(id) from drawings) + 1),
    32)

コツは合体です。ユーザー「steven」の ID があればそれを返し、それ以外の場合は新しい新しい ID を返します。


オプション 2: 行を削除する余裕がない

前の解決策を試してみたところ、私の場合、この ID は他のテーブルの外部キーとして機能するため、データが破壊される可能性があることに気付きました。さらに、 ON DELETE CASCADE句を使用してテーブルを作成しました。これは、データをサイレントに削除することを意味します。危険。

ということで、最初は IF 句を考えましたが、SQLite にはCASEしかありません。そして、このCASEは、存在する場合 (user_name='steven' のプレイヤーから ID を選択) の場合は 1 つのUPDATEクエリを実行し、そうでない場合はINSERTを実行するために使用することはできません (または、少なくとも私はそれを管理しませんでした) 。立ち入り禁止。

そして最後に、私は力ずくで成功しました。ロジックは、実行するUPSERTごとに、最初にINSERT OR IGNOREを実行してユーザーの行があることを確認してから、挿入しようとしたデータとまったく同じデータでUPDATEクエリを実行します。

前と同じデータ: user_name='steven' および age=32。

-- make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 

-- make sure it has the right data
UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; 

そしてそれだけです!

編集

Andy がコメントしたように、最初に挿入してから更新しようとすると、予想よりも頻繁にトリガーが発生する可能性があります。私の意見では、これはデータの安全性の問題ではありませんが、不必要なイベントを発生させることはほとんど意味がありません。したがって、改善されたソリューションは次のようになります。

-- Try to update any existing row
UPDATE players SET age=32 WHERE user_name='steven';

-- Make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32); 
于 2013-03-07T17:06:52.673 に答える
76

これは、キー違反があった場合にのみ機能するブルートフォース「無視」を必要としないアプローチです。この方法は、更新で指定した条件に基づいて機能します。

これを試して...

-- Try to update any existing row
UPDATE players
SET age=32
WHERE user_name='steven';

-- If no update happened (i.e. the row didn't exist) then insert one
INSERT INTO players (user_name, age)
SELECT 'steven', 32
WHERE (Select Changes() = 0);

使い方

ここでの「魔法のソース」は節 で使用Changes()しています。最後の操作 (この場合は更新) によって影響を受けた行の数を表します。WhereChanges()

上記の例では、更新による変更がない場合 (つまり、レコードが存在しない場合) Changes()= 0 となるためWhere、ステートメントの句はInserttrue と評価され、指定されたデータで新しい行が挿入されます。

が既存の行を更新した場合はUpdate =Changes() 1 (より正確には、複数の行が更新された場合はゼロではない) であるため、Insertnow の 'Where' 句は false と評価され、挿入は行われません。

これの利点は、外部キー関係で下流のキーを台無しにする可能性のあるデータを不必要に削除したり、再挿入したりする必要がないことです。

さらに、これは単なる標準Where句であるため、キー違反だけでなく、定義したあらゆるものに基づくことができます。同様に、Changes()式が許可されている場所ならどこでも、必要/必要なものと組み合わせて使用​​できます。

于 2016-07-19T15:41:05.477 に答える
26

提示されたすべての回答の問題は、トリガー(およびおそらく他の副作用)を完全に考慮していないことです。のようなソリューション

INSERT OR IGNORE ...
UPDATE ...

行が存在しない場合、両方のトリガーが実行されます (挿入と更新の場合)。

適切な解決策は

UPDATE OR IGNORE ...
INSERT OR IGNORE ...

その場合、1 つのステートメントのみが実行されます (行が存在するかどうかに関係なく)。

于 2015-01-13T15:35:59.447 に答える
6

一意のキーやその他のキーを中継しない (プログラマー向けの) 穴のない純粋な UPSERT を作成するには:

UPDATE players SET user_name="gil", age=32 WHERE user_name='george'; 
SELECT changes();

SELECT changes() は、最後の照会で行われた更新の数を返します。次に、changes() からの戻り値が 0 かどうかを確認し、0 の場合は次を実行します。

INSERT INTO players (user_name, age) VALUES ('gil', 32); 
于 2015-09-01T04:17:55.790 に答える