他に更新がない場合は挿入しますか?


275

クラシックな「新しいレコードを挿入する、またはレコードが既に存在する場合にそれを更新する方法」のいくつかの「正しい」解決策を見つけましたが、SQLiteでそれらを機能させることができません。

次のように定義されたテーブルがあります。

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

私がしたいことは、一意の名前を持つレコードを追加することです。名前が既に存在する場合、フィールドを変更します。

誰かがこれを行う方法を教えてもらえますか?


3
「挿入または置換」は「挿入または更新」とはまったく異なります
Fattie

回答:


320

見ていhttp://sqlite.org/lang_conflict.htmlを

あなたは次のようなものが必要です:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

行がすでにテーブルに存在する場合、挿入リストにないフィールドはNULLに設定されることに注意してください。これが、ID列に副選択がある理由です。置換の場合、ステートメントはそれをNULLに設定し、新しいIDが割り当てられます。

この方法は、置換の場合に行の特定のフィールド値のみを残し、挿入の場合にはフィールドをNULLに設定する場合にも使用できます。

たとえば、Seen独りのままにしたい場合:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));

109
誤った「挿入または置換」は「挿入または更新」とは異なります。有効な回答については、stackoverflow.com
questions /

12
@rdsいいえ、この質問は「フィールドの変更」と言っており、主キーは列リストの一部ではありませんが、他のすべてのフィールドはそうであるため、間違いありません。すべてのフィールド値を置き換えるわけではないコーナーケースが発生する場合、または主キーをいじくり回している場合は、別のことを行う必要があります。新しいフィールドの完全なセットがある場合、このアプローチは問題ありません。私には見えない特定の問題がありますか?
janm

9
すべてのフィールドのすべての新しい値がわかっている場合に有効です。たとえば、ユーザーがのみを更新する場合Level、このアプローチに従うことはできません。
RDS

4
正解です。他の答えは関連していますが、このアプローチはすべてのフィールド(キーを除く)の更新に有効です。
ЯрославРахматуллин

2
はいこれは完全に間違っています。新しいものから競合の単一の列の値を更新したい場合はどうなりますか?上記の場合、他のすべてのデータは正しくない新しいデータに置き換えられます。
Mrug 2014

85

INSERT OR IGNOREコマンドに続けてコマンドを使用する必要があります。UPDATE次の例nameは主キーです。

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

最初のコマンドはレコードを挿入します。レコードが存在する場合、既存の主キーとの競合が原因のエラーは無視されます。

2番目のコマンドはレコードを更新します(これは確実に存在します)


6
それはいつ無視されますか?名前と年齢が同じである場合
mou

これが解決策になるはずです...挿入時にトリガーを使用している場合、受け入れられた回答が毎回起動されます。これは行わず、アップデートのみを実行します
PodTech.io

1
名前のみに基づいて無視します。「名前」列のみが主キーであることを忘れないでください。
Gabriel Ferrer 2017年

3
レコードが新しい場合、更新は不要ですが、とにかく実行され、パフォーマンスが低下しますか?
MarcG 2017年

これのために準備されたステートメントを実行するにはどうすればよいですか?
2018

65

テーブルに制約を設定して「競合」をトリガーする必要があります。これは、置換を実行して解決します。

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

次に、発行できます:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

「SELECT * FROM data」はあなたに与えるでしょう:

2|2|2|3.0
3|1|2|5.0

REPLACEはUPDATEではなくDELETEおよびINSERTを実行するため、data.idは「1」ではなく「3」であることに注意してください。これは、必要なすべての列を定義することを確認する必要があることも意味します。そうしないと、予期しないNULL値が取得されます。


33

まずそれを更新します。場合は、影響を受けた行数 = 0は、それを挿入します。最も簡単で、すべてのRDBMSに適しています。


11
2つの操作は、データベースに関係なく、適切な分離レベルのトランザクションで問題ありません。
janm

5
Insert or Replace本当に望ましいです。
MPelletier

1
IT文書にもっと多くの例が含まれることを本当に望みます。以下で試してみましたが、機能しません(構文が明らかに間違っています)。それがどうあるべきかについてのアイデアはありますか?INSERT INTO Book(Name、TypeID、Level、Seen)VALUES( 'Superman'、 '2'、 '14'、 '0')ON CONFLICT REPLACE Book(Name、TypeID、Level、Seen)VALUES( 'Superman'、 ' 2 '、' 14 '、' 0 ')
SparkyNZ

6
また、行の値がまったく同じ場合、影響を受ける行数はゼロになり、新しい重複行が作成されます。
12


20

INSERT OR REPLACE 他のフィールドをデフォルト値に置き換えます。

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

他のフィールドを保存したい場合

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

またはUPSERTを使用する(構文はSQLiteにバージョン3.24.0(2018-06-04)で追加されました)

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

excluded.値に等しいプレフィックスVALUES


16

アップサートはあなたが欲しいものです。UPSERT構文は、SQLiteにバージョン3.24.0(2018-06-04)で追加されました。

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

この時点で、「UPSERT」という実際の単語は、アップサート構文の一部ではないことに注意してください。

正しい構文は

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

またINSERT INTO SELECT ...、select を実行する場合は、少なくとも結合構文を使用してWHERE trueトークンに関するパーサーのあいまいさを解決する必要ONがあります。

INSERT OR REPLACE...レコードを置き換える必要がある場合、新しいレコードを挿入する前にレコードを削除することに注意してください。これは、外部キーカスケードまたは他の削除トリガーがある場合は悪い可能性があります。


それはドキュメンテーションに存在し、私は3.25.1である最新バージョンのsqliteを持っていますが、それは私にとっては機能しません
Bentaiba Miled Basma

これら2つのクエリをそのまま実行しましたか?
ComradeJoecool 2018

Ubuntu 18.04には3.25ではなくSQLite3 v3.22が付属しているため、UPSERT構文はサポートされていません。
starbeamrainbowlabs

機能の参照バージョンをありがとう!
Matteo

6

主キーがない場合は、存在しない場合は挿入してから更新できます。これを使用する前に、テーブルに少なくとも1つのエントリが含まれている必要があります。

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';

これは、重複するエントリを作成することなく機能した唯一のソリューションです。おそらく、私が使用しているテーブルには主キーがなく、デフォルト値のない2つの列があるためです。少し長いソリューションですが、適切に機能し、期待どおりに機能します。
インバーローズ

4

UPSERTが欲しいと思います。

「INSERT OR REPLACE」は、その答えに追加のトリックがなければ、指定していないフィールドをNULLまたはその他のデフォルト値にリセットします。(このINSERT OR REPLACEの動作はUPDATEとは異なります。実際にはINSERTであるため、これはINSERTとまったく同じです。ただし、必要なものがUPDATE-existである場合は、おそらくUPDATEセマンティクスが必要であり、実際の結果に不愉快なことに驚きます。)

提案されたUPSERT実装の秘訣は、基本的にINSERT OR REPLACEを使用することですが、埋め込みSELECT句を使用してすべてのフィールドを指定し、変更したくないフィールドの現在の値を取得します。


1

PRIMARY KEYUNIQUEの仕組みを完全に理解していなければ、ここで予期しない動作が発生する可能性があることを指摘しておくとよいでしょう。相互作用。

例として、NAMEフィールドが現在使用されていない場合にのみレコードを挿入したい場合で、その場合、制約例外を発生させて通知したい場合、INSERT OR REPLACEは例外をスローせず、代わりに競合するレコード(同じNAMEを持つ既存のレコード)を置き換えることにより、UNIQUE制約自体を解決します。ガスパール彼の答えでこれを本当によく示しています上記の。

制約例外を発生させる場合は、INSERTステートメントを使用し、名前が取得されていないことがわかったら、別のUPDATEコマンドを使用してレコードを更新する必要があります。

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