SQLite-INSERTまたはREPLACEではなくUPSERT


535

http://en.wikipedia.org/wiki/Upsert

SQL Serverに更新ストアドプロシージャを挿入する

SQLiteでこれを行うために私が考えていなかった賢い方法はありますか?

基本的に、レコードが存在する場合は4つのカラムのうち3つを更新します。レコードが存在しない場合は、4番目のカラムのデフォルト(NUL)値でレコードを挿入します。

IDは主キーであるため、UPSERTへのレコードは1つしかありません。

(明らかに更新または挿入する必要があるかどうかを判断するために、SELECTのオーバーヘッドを回避しようとしています)

提案?


SQLiteサイトでTABLE CREATEの構文を確認できません。テスト用のデモは作成していませんが、サポートされていないようです。

もしそうなら、私は3つの列を持っているので、実際には次のようになります:

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    Blob1 BLOB ON CONFLICT REPLACE, 
    Blob2 BLOB ON CONFLICT REPLACE, 
    Blob3 BLOB 
);

ただし、最初の2つのblobは競合を引き起こさず、IDのみが発生するので、(必要に応じて)Blob1とBlob2は置き換えられません。


バインドデータが完全なトランザクションである場合のSQLiteでのUPDATEは、更新される送信された各行に以下が必要であることを意味します。

ステートメントオブジェクトの寿命は次のようになります。

  1. sqlite3_prepare_v2()を使用してオブジェクトを作成します
  2. sqlite3_bind_インターフェイスを使用して値をホストパラメータにバインドします。
  3. sqlite3_step()を呼び出してSQLを実行します
  4. sqlite3_reset()を使用してステートメントをリセットし、手順2に戻って繰り返します。
  5. sqlite3_finalize()を使用してステートメントオブジェクトを破棄します。

UPDATE INSERTに比べて遅いと思いますが、主キーを使用したSELECTと比較するとどうですか?

おそらく、selectを使用して4番目の列(Blob3)を読み取り、REPLACEを使用して、元の4番目の列と最初の3列の新しいデータをブレンドした新しいレコードを書き込む必要がありますか?


5
SQLiteの-プレリリースで利用可能なUPSERT参照してください。sqlite.1065341.n5.nabble.com/...
のGaurav

3
UPSERTはSQLiteのバージョン3.24.0で利用可能
pablo_worker '

回答:


854

テーブルの3つの列を想定:ID、NAME、ROLE


悪い例:これにより、ID = 1のすべての列が新しい値で挿入または置換されます。

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (1, 'John Foo', 'CEO');

悪い例:これは2つの列を挿入または置換します... NAME列はNULLまたはデフォルト値に設定されます。

INSERT OR REPLACE INTO Employee (id, role) 
  VALUES (1, 'code monkey');

良い:SQLite On競合句を使用するSQLiteで UPSERTサポート!UPSERT構文がバージョン3.24.0でSQLiteに追加されました。

UPSERTはINSERTに特別な構文を追加したもので、INSERTが一意性の制約に違反した場合、INSERTはUPDATEまたはno-opとして動作します。UPSERTは標準SQLではありません。SQLiteのUPSERTは、PostgreSQLによって確立された構文に従います。

ここに画像の説明を入力してください

良いが傾向がある:これは2つの列を更新します。ID = 1が存在する場合、NAMEは影響を受けません。ID = 1が存在しない場合、名前はデフォルト(NULL)になります。

INSERT OR REPLACE INTO Employee (id, role, name) 
  VALUES (  1, 
            'code monkey',
            (SELECT name FROM Employee WHERE id = 1)
          );

これにより、2つの列が更新されます。ID = 1が存在する場合、ROLEは影響を受けません。ID = 1が存在しない場合、ロールはデフォルト値ではなく「ベンチウォーマー」に設定されます。

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1素晴らしい!組み込みのselect句を使用すると、フィールドの古い値と新しい値を組み合わせる/比較する必要がある場合に、デフォルトのON CONFLICT REPLACE機能をオーバーライドする柔軟性が得られます。
G__ 2011

24
従業員がカスケード削除で他の行によって参照されている場合、他の行は置換によって削除されます。
Don Reba

246
これは痛いです。SQliteにはUPSERTが必要です。
andig

21
これがすべての列を挿入するか、ID = 1の新しい値で置き換える理由を説明できますか?最初の例ではBADと見なされますか?そこで提示するコマンドは、ID 1の新しいレコードを作成し、名前がJohn Fooで役割CEOであるか、IDが1のレコードが既に存在する場合は、そのデータで上書きします(idが主キーであると想定)。 。それで、それが正確に起こるのはなぜ悪いのですか?
またはMapper

10
@Cornelius:それは明らかですが、最初の例ではそうではありません。最初の例は、すべての列を強制的に設定することを目的としています。これは、レコードが挿入または置換されているかどうかに関係なく、実際に行われることです。では、なぜそれが悪いと考えられるのでしょうか?リンクされた回答は、2番目の例のように、列のサブセットを指定したときに問題が発生する理由も指摘しています。すべての列の値を指定する一方で、最初の例で何が起こるかについての悪影響については詳しく説明していないようです。INSERT OR REPLACE
またはMapper 2014年

134

INSERT OR REPLACEは「UPSERT」と同等ではありません

id、name、およびroleフィールドを持つテーブルEmployeeがあるとします。

INSERT OR REPLACE INTO Employee ("id", "name", "role") VALUES (1, "John Foo", "CEO")
INSERT OR REPLACE INTO Employee ("id", "role") VALUES (1, "code monkey")

ブーム、あなたは従業員番号1の名前を失いました。SQLiteはそれをデフォルト値に置き換えました。

UPSERTの予想される出力は、役割を変更し、名前を維持することです。


21
-1私からの恐れがあります。はい、受け入れられた答えは間違っていますが、あなたの答えは問題を指摘していますが、それも答えではありません。実際の答えについては、Embedded 句を使用したEric Bの巧妙なソリューションを参照してくださいcoalesce((select ..), 'new value')。エリックの答えはここにもっと投票が必要だと思います。

22
確かに。エリックは間違いなく最高の答えであり、より多くの投票に値します。そうは言っても、問題を指摘することで、良い答えを見つけるのに少し貢献したと思います(エリックの答えは後で来て、私の答えのサンプルSQLテーブルに基づいています)。したがって、私が-1に値するかどうかは
わかり

6
上記の-1をオフセットするには+1。笑。このスレッドの興味深いタイムライン。明らかに、あなたの答えはエリックの1週間以上前でしたが、質問されてからほぼ2年後、あなたはどちらもこの質問に答えました。エリックも+1。
リッチ


2
@QEDいいえ、削除+挿入(置換)は2つのdmlステートメントであり、たとえば独自のトリガーがあるためです。これは、1つの更新ステートメントだけとは異なります。
2016

109

エリックBの答えは、既存の行から1列または2列だけを保持する場合は問題ありません。たくさんの列を保持したい場合は、非常に扱いにくくなります。

これは、両側の任意の数の列に適切にスケーリングするアプローチです。それを説明するために、次のスキーマを想定します。

 CREATE TABLE page (
     id      INTEGER PRIMARY KEY,
     name    TEXT UNIQUE,
     title   TEXT,
     content TEXT,
     author  INTEGER NOT NULL REFERENCES user (id),
     ts      TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

特に、これnameは行の自然キーidです。外部キーにのみ使用されるため、SQLiteが新しい行を挿入するときにID値自体を選択することが重要です。しかし、そのnameに基づいて既存の行を更新する場合、古いID値を保持し続けたいです(明らかに!)。

UPSERT次の構成でtrueを達成します。

 WITH new (name, title, author) AS ( VALUES('about', 'About this site', 42) )
 INSERT OR REPLACE INTO page (id, name, title, content, author)
 SELECT old.id, new.name, new.title, old.content, new.author
 FROM new LEFT JOIN page AS old ON new.name = old.name;

このクエリの正確な形式は少し異なる場合があります。重要なのは、INSERT SELECT既存の行を新しい値に結合するための、左外部結合の使用です。

行が以前に存在しなかった場合、ここで、old.idあろうNULLとSQLiteは、自動的にIDを割り当てますが、既にそのような行があった場合、old.id実際の値を持ち、これが再利用されます。まさに私が欲しかったものです。

実際、これは非常に柔軟です。ts列がすべての面で完全に欠落していることに注意してください– DEFAULT値があるため、SQLiteはいずれにしても正しいことを行うだけなので、自分で処理する必要はありません。

また、両方の列含むことができ、newおよびold側面をして、例えばを使用しCOALESCE(new.content, old.content)、外側にSELECTあなたは固定クエリを使用していて、新しい結合している場合など- 「それ以外の場合は、古いコンテンツを保持し、いずれかがあった場合、新たなコンテンツを挿入」と言ってプレースホルダー付きの値。


13
+1はうまく機能しWHERE name = "about"ますが、SELECT ... AS old速度を上げるためにに制約を追加します。1m以上の行がある場合、これは非常に遅くなります。

良い点、あなたのコメントに+1してください。ただし、そのようなWHERE句を追加するには、このアプローチを思いついたときにそもそも回避しようとしていたクエリに、一種の冗長性が必要なためです。いつものように:パフォーマンスが必要な場合は、正規化しない-この場合はクエリの構造。
アリストテレスPagaltzis 2013

4
:あなたがしたい場合は、これにアリストテレスの例のダウンを簡素化することができます INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
jcox

4
ON DELETEこれは、置換(つまり、更新)を実行するときにトリガーを不必要にトリガーしませんか?
からくり

3
それは確かにトリガーをON DELETEトリガーします。Dunnoについて不必要に。ほとんどのユーザーにとって、それはおそらく不要であり、不要でさえありますが、すべてのユーザーにとってはそうではないかもしれません。同様に、外部キーを持つ行を問題の行にカスケード削除するという事実についても–おそらく多くのユーザーにとって問題です。SQLiteには、残念ながら実際のUPSERTに近いものはありません。(INSTEAD OF UPDATEトリガーでそれを偽造するために保存すると思います。)
アリストテレスPagaltzis '19

86

あなたが一般的にアップデートをしているなら、私は..

  1. 取引を開始する
  2. 更新を行う
  3. 行数を確認する
  4. 0の場合、挿入を行います
  5. コミット

あなたが一般的に挿入をしているなら、私は

  1. 取引を開始する
  2. 挿入してみてください
  3. 主キー違反エラーを確認する
  4. エラーが発生した場合は、更新を行います
  5. コミット

このようにして、選択を回避し、Sqliteでトランザクション的に健全です。


おかげで、この@ stackoverflow.com / questions / 2717590 / …に質問しました。私から+1!=)
Alix Axel

4
3番目のステップでsqlite3_changes()を使用して行数を確認する場合は、変更のために複数のスレッドからのDBハンドルを使用しないようにしてください。
リヌリン

3
以下は、同じ効果でまだそれほど簡潔ではありませんか?1)id = 'x'でidフォームテーブルを選択2)if(ResultSet.rows.length == 0)id = 'x'でテーブルを更新;
フローリン

20
私はINSERT OR UPDATEが言語の一部であったことを本当に望んでいます
Florin

これは最終的に私にとって最も効果的に機能し、REPLACE INTOを実行しようとするか、常に挿入/更新しようとすると、挿入ではなく更新を主に行ったときにパフォーマンスが低下しました。
PherricOxide 2013

83

この回答は更新されたため、以下のコメントは適用されません。

2018-05-18 STOP PRESS。

SQLiteでのUPSERTのサポート!UPSERT構文がバージョン3.24.0でSQLiteに追加されました(保留中)!

UPSERTはINSERTに特別な構文を追加したもので、INSERTが一意性の制約に違反した場合、INSERTはUPDATEまたはno-opとして動作します。UPSERTは標準SQLではありません。SQLiteのUPSERTは、PostgreSQLによって確立された構文に従います。

ここに画像の説明を入力してください

あるいは:

これを行う別の完全に異なる方法は、次のとおりです。私のアプリケーションでは、メモリ内に行を作成するときに、メモリ内のrowIDをlong.MaxValueに設定しました。(MaxValueをIDとして使用することはありません。十分に長く存続することはできません。...次に、rowIDがその値でない場合は、データベースに既に存在している必要があるため、UPDATEが必要です。MaxValueの場合は、挿入が必要です。これは、アプリでrowIDを追跡できる場合にのみ役立ちます。


5
アーメン。シンプルは複雑よりも優れています。これは、受け入れられた回答よりも少し単純に感じられます。
kkurian

4
INSERT INTO ... WHEREをSQLiteで実行できないと思いましたか?これは私にとってsqlite3の構文エラーです
チャーリーマーティン

5
@CharlieMartinは正しいです。この構文は、OPが要求したSQLiteでは無効です。WHERE句をに追加することができませんINSERT:文 のsqlite-挿入 ...
colm.anseo

4
この答えは私に多くの時間をゆったりさせました、質問はSQLITEについてであり、INSERT WHEREがsqiteでサポートされていないことを知りませんでした。答えでsqliteには無効であることをこのメモに追加してください
Miquel

3
@colminatorなど:あなたの提案を使用して彼のSQLステートメントを修正しました。
F Lekschas

62

私はこれが古いスレッドであることを理解していますが、私は最近sqlite3で作業しており、パラメーター化されたクエリを動的に生成するという私のニーズにより適したこのメソッドを考え出しました:

insert or ignore into <table>(<primaryKey>, <column1>, <column2>, ...) values(<primaryKeyValue>, <value1>, <value2>, ...); 
update <table> set <column1>=<value1>, <column2>=<value2>, ... where changes()=0 and <primaryKey>=<primaryKeyValue>; 

それは更新のwhere句を含む2つのクエリですが、トリックを行うようです。また、changes()への呼び出しがゼロより大きい場合、sqliteがupdateステートメントを完全に最適化できるというこのビジョンも頭の中にあります。それが実際にそれができるかどうかは私の知識を超えていますが、男は夢を見ることはできませんか?;)

ボーナスポイントの場合は、この行を追加して、新しく挿入された行でも既存の行でも、行のIDを返します。

select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;

+ 1.一部のSQL Server TSQLコードからの変換を実行して置き換えようとしていたこと。ありがとう。私が置き換えようとしたSQLは次のようなものでしたUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB '30 / 11/14

14

これは、実際にはINSERT OR REPLACEではなくUPSERT(UPDATEまたはINSERT)であるソリューションです(多くの状況で動作が異なります)。

これは次のように機能し
ます。1.同じIDのレコードが存在する場合は更新を試みます。
2.更新によって行が変更されなかった場合(NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0))、レコードを挿入します。

そのため、既存のレコードが更新されたか、挿入が実行されます。

重要な詳細は、changes()SQL関数を使用して、更新ステートメントが既存のレコードにヒットしたかどうかを確認し、レコードにヒットしなかった場合にのみ挿入ステートメントを実行することです。

言及すべきことの1つは、changes()関数は低レベルのトリガー(http://sqlite.org/lang_corefunc.html#changesを参照)によって実行された変更を返さないことです。そのため、必ず考慮してください。

ここにSQLがあります...

テスト更新:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 2;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 2, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

テスト挿入:

--Create sample table and records (and drop the table if it already exists)
DROP TABLE IF EXISTS Contact;
CREATE TABLE [Contact] (
  [Id] INTEGER PRIMARY KEY, 
  [Name] TEXT
);
INSERT INTO Contact (Id, Name) VALUES (1, 'Mike');
INSERT INTO Contact (Id, Name) VALUES (2, 'John');

-- Try to update an existing record
UPDATE Contact
SET Name = 'Bob'
WHERE Id = 3;

-- If no record was changed by the update (meaning no record with the same Id existed), insert the record
INSERT INTO Contact (Id, Name)
SELECT 3, 'Bob'
WHERE NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0);

--See the result
SELECT * FROM Contact;

2
これは私にとってエリックのものよりも良い解決策のようです。ただし、動作するINSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;はずです。
bkausbk

おかげで、それはWebSQL(CordovaおよびSQLiteプラグインを使用)でも動作します
Zappescu 2017年

11

バージョン3.24.0以降、UPSERTはSQLiteでサポートされています。

ドキュメントから:

UPSERTはINSERTに特別な構文を追加したもので、INSERTが一意性の制約に違反した場合、INSERTはUPDATEまたはno-opとして動作します。UPSERTは標準SQLではありません。SQLiteのUPSERTは、PostgreSQLによって確立された構文に従います。UPSERT構文がバージョン3.24.0でSQLiteに追加されました(保留中)。

UPSERTは、特別なON CONFLICT句が後に続く通常のINSERTステートメントです。

ここに画像の説明を入力してください

画像ソース:https : //www.sqlite.org/images/syntax/upsert-clause.gif


8

SQLiteで実際にupsertを実行できます。見慣れたものとは少し違って見えるだけです。それは次のようになります:

INSERT INTO table name (column1, column2) 
VALUES ("value12", "value2") WHERE id = 123 
ON CONFLICT DO UPDATE 
SET column1 = "value1", column2 = "value2" WHERE id = 123

5

アリストテレスの答えを拡張すると、ダミーの「シングルトン」テーブル(1行の独自の作成のテーブル)からSELECTできます。これにより、一部の重複が回避されます。

また、MySQLとSQLite間で移植可能な例を保持し、最初にのみ列を設定する方法の例として「date_added」列を使用しました。

 REPLACE INTO page (
   id,
   name,
   title,
   content,
   author,
   date_added)
 SELECT
   old.id,
   "about",
   "About this site",
   old.content,
   42,
   IFNULL(old.date_added,"21/05/2013")
 FROM singleton
 LEFT JOIN page AS old ON old.name = "about";

4

私が知っている最善の方法は、更新を行ってから挿入を行うことです。「selectのオーバーヘッド」は必要ですが、高速な主キーを検索しているので、それほど負担にはなりません。

あなたはあなたが望むことをするためにあなたのテーブルとフィールド名で以下のステートメントを修正することができるはずです。

--first, update any matches
UPDATE DESTINATION_TABLE DT
SET
  MY_FIELD1 = (
              SELECT MY_FIELD1
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
 ,MY_FIELD2 = (
              SELECT MY_FIELD2
              FROM SOURCE_TABLE ST
              WHERE ST.PRIMARY_KEY = DT.PRIMARY_KEY
              )
WHERE EXISTS(
            SELECT ST2.PRIMARY_KEY
            FROM
              SOURCE_TABLE ST2
             ,DESTINATION_TABLE DT2
            WHERE ST2.PRIMARY_KEY = DT2.PRIMARY_KEY
            );

--second, insert any non-matches
INSERT INTO DESTINATION_TABLE(
  MY_FIELD1
 ,MY_FIELD2
)
SELECT
  ST.MY_FIELD1
 ,NULL AS MY_FIELD2  --insert NULL into this field
FROM
  SOURCE_TABLE ST
WHERE NOT EXISTS(
                SELECT DT2.PRIMARY_KEY
                FROM DESTINATION_TABLE DT2
                WHERE DT2.PRIMARY_KEY = ST.PRIMARY_KEY
                );

7
複雑化する方法。
10

データベースエンジンへのリクエストを2回行う必要があるため、これは良い考えではないと思います。
Ricardo da Rocha Vitor

3

誰かがCordovaでSQLiteの私のソリューションを読みたい場合は、上記の@davidの回答のおかげで、この汎用jsメソッドを取得しました。

function    addOrUpdateRecords(tableName, values, callback) {
get_columnNames(tableName, function (data) {
    var columnNames = data;
    myDb.transaction(function (transaction) {
        var query_update = "";
        var query_insert = "";
        var update_string = "UPDATE " + tableName + " SET ";
        var insert_string = "INSERT INTO " + tableName + " SELECT ";
        myDb.transaction(function (transaction) {
            // Data from the array [[data1, ... datan],[()],[()]...]:
            $.each(values, function (index1, value1) {
                var sel_str = "";
                var upd_str = "";
                var remoteid = "";
                $.each(value1, function (index2, value2) {
                    if (index2 == 0) remoteid = value2;
                    upd_str = upd_str + columnNames[index2] + "='" + value2 + "', ";
                    sel_str = sel_str + "'" + value2 + "', ";
                });
                sel_str = sel_str.substr(0, sel_str.length - 2);
                sel_str = sel_str + " WHERE NOT EXISTS(SELECT changes() AS change FROM "+tableName+" WHERE change <> 0);";
                upd_str = upd_str.substr(0, upd_str.length - 2);
                upd_str = upd_str + " WHERE remoteid = '" + remoteid + "';";                    
                query_update = update_string + upd_str;
                query_insert = insert_string + sel_str;  
                // Start transaction:
                transaction.executeSql(query_update);
                transaction.executeSql(query_insert);                    
            });
        }, function (error) {
            callback("Error: " + error);
        }, function () {
            callback("Success");
        });
    });
});
}

したがって、最初にこの関数で列名を取得します。

function get_columnNames(tableName, callback) {
myDb.transaction(function (transaction) {
    var query_exec = "SELECT name, sql FROM sqlite_master WHERE type='table' AND name ='" + tableName + "'";
    transaction.executeSql(query_exec, [], function (tx, results) {
        var columnParts = results.rows.item(0).sql.replace(/^[^\(]+\(([^\)]+)\)/g, '$1').split(','); ///// RegEx
        var columnNames = [];
        for (i in columnParts) {
            if (typeof columnParts[i] === 'string')
                columnNames.push(columnParts[i].split(" ")[0]);
        };
        callback(columnNames);
    });
});
}

次に、プログラムでトランザクションを構築します。

「値」は、前に作成する必要がある配列であり、テーブルに挿入または更新する行を表します。

「remoteid」は、リモートサーバーと同期しているため、参照として使用したIDです。

SQLite Cordovaプラグインの使用については、公式リンクを参照してください


2

この方法は、この質問に対する回答からの他の方法のいくつかをリミックスし、CTE(Common Table Expressions)の使用を組み込んでいます。クエリを紹介し、なぜ自分がやったのかを説明します。

従業員300がいる場合は、従業員300の姓をDAVISに変更します。それ以外の場合は、新しい従業員を追加します。

テーブル名:従業員列:id、first_name、last_name

クエリは次のとおりです。

INSERT OR REPLACE INTO employees (employee_id, first_name, last_name)
WITH registered_employees AS ( --CTE for checking if the row exists or not
    SELECT --this is needed to ensure that the null row comes second
        *
    FROM (
        SELECT --an existing row
            *
        FROM
            employees
        WHERE
            employee_id = '300'

        UNION

        SELECT --a dummy row if the original cannot be found
            NULL AS employee_id,
            NULL AS first_name,
            NULL AS last_name
    )
    ORDER BY
        employee_id IS NULL --we want nulls to be last
    LIMIT 1 --we only want one row from this statement
)
SELECT --this is where you provide defaults for what you would like to insert
    registered_employees.employee_id, --if this is null the SQLite default will be used
    COALESCE(registered_employees.first_name, 'SALLY'),
    'DAVIS'
FROM
    registered_employees
;

基本的に、CTEを使用して、selectステートメントを使用してデフォルト値を決定する必要がある回数を減らしました。これはCTEなので、テーブルから必要な列を選択するだけで、INSERTステートメントはこれを使用します。

これで、COALESCE関数のnullを値と同じにすることで、使用するデフォルトを決定できます。


2

アリストテレス・パガルツィスエリックBの答えCOALESCEからのアイデアに続き、ここでは、いくつかの列のみを更新するか、存在しない場合は行全体を挿入するというアップサートオプションがあります。

この場合、タイトルとコンテンツを更新し、既存の場合は他の古い値を維持し、名前が見つからない場合は指定された値を挿入するとします。

オートインクリメントが想定idされているため、NOTEは強制的にNULLになりますINSERT。生成された主キーだけの場合COALESCEも使用できます(Aristotle Pagaltzisコメントを参照)。

WITH new (id, name, title, content, author)
     AS ( VALUES(100, 'about', 'About this site', 'Whatever new content here', 42) )
INSERT OR REPLACE INTO page (id, name, title, content, author)
SELECT
     old.id, COALESCE(old.name, new.name),
     new.title, new.content,
     COALESCE(old.author, new.author)
FROM new LEFT JOIN page AS old ON new.name = old.name;

したがって、一般的なルールは、古い値を保持する場合はを使用しCOALESCE、値を更新する場合はnew.fieldname


COALESCE(old.id, new.id)自動インクリメントキーは間違いなく間違っています。そして、「値が欠落している場合を除いて、行のほとんどを変更しないでください」というのは実際に誰かが持っているユースケースのように聞こえますが、UPSERTを実行する方法を探しているときに、それが求められているとは思いません。
アリストテレスPagaltzis 2017

@AristotlePagaltzis私が間違っている場合は謝罪します。自動インクリメントを使用していません。このクエリで探しているのは、数列のみ更新するか、存在しない場合に備えて、指定された値で行全体を挿入することです。私はあなたのクエリで遊んでいて、挿入時にそれを達成できませんでした:oldで指定さNULLれた値ではなく、に割り当てられているテーブルから選択された列new。これが使用する理由COALESCEです。私はsqliteの専門家ではありません。このクエリをテストしてきましたが、このケースではうまくいくようです。自動インクリメントを使用した解決策を教えていただければ幸いです
Miquel

2
自動インクリメントキーの場合は、あなたが欲しい挿入するNULLことではなく、次の使用可能な値を挿入するのSQLiteを伝えるために、キーとして。
アリストテレスPagaltzis

私はあなたがあなたがしていることをしているべきではないと言っているわけではありません(あなたがそれを必要とするならあなたはそれを必要とします)。UPSERTは通常、テーブルに格納する行があり、値を入れるための一致する行がすでにあるかどうか、またはそれらを新しい行として挿入する必要があるかどうかがわからないことを意味します。ユースケースは、一致する行がすでにある場合、新しい行のほとんどの値無視することです。それは結構です、それは尋ねられた質問だけではありません。
アリストテレスPagaltzis

1

これはあなたが探しているものかもしれないと思います:ON CONFLICT句

次のようにテーブルを定義すると、

CREATE TABLE table1( 
    id INTEGER PRIMARY KEY ON CONFLICT REPLACE, 
    field1 TEXT 
); 

さて、すでに存在するIDでINSERTを実行すると、SQLiteは自動的にINSERTの代わりにUPDATEを実行します。

うーん...


6
私はこれがうまくいくとは思わない、それは挿入ステートメントから欠落している列を一掃するだろう
Sam Saffron

3
@Mosor:-1、ごめんなさい。これはREPLACEステートメントを発行するのと同じです。
Alix Axel

7
-1。これは、主キーがすでに存在する場合、削除してから挿入するためです。
10

1

2つの操作でこれを行うことを気にしない場合。

手順:

1)「INSERT OR IGNORE」で新しいアイテムを追加します

2)「UPDATE」で既存のアイテムを更新します

両方のステップへの入力は、新しいアイテムまたは更新可能なアイテムの同じコレクションです。変更を必要としない既存のアイテムで正常に動作します。それらは更新されますが、データは同じであるため、最終結果は変更されません。

確かに遅いなど。非効率的。うん。

SQLを記述して保守および理解するのは簡単ですか?絶対に。

これは考慮すべきトレードオフです。小さなアップサートに最適です。コードの保守性の効率を犠牲にすることを気にしない人に最適です。


全員への注意:INSERT OR REPLACEはこの投稿の目的ではありません。INSERT OR REPLACEは、テーブルに新しいIDで新しい行を作成します。それは更新ではありません。
sapbucket

-2

このスレッドを読んだばかりで、この「UPSERT」するだけでは簡単ではないことに失望したので、さらに調査しました...

これはSQLITEで直接かつ簡単に行うことができます。

代わりに: INSERT INTO

使用する: INSERT OR REPLACE INTO

これはまさにあなたがしたいことをします!


21
-1 INSERT OR REPLACEはではありませんUPSERT。理由については、gregschlomの「回答」を参照してください。エリックBのソリューションは実際に機能し、いくつかの賛成票が必要です。

-4
SELECT COUNT(*) FROM table1 WHERE id = 1;

もし COUNT(*) = 0

INSERT INTO table1(col1, col2, cole) VALUES(var1,var2,var3);

そうでなければ COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;

これは非常に複雑で、SQLはこれを1つのクエリでうまく処理できます
Robin Kanters
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.