589

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

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

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

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

ID は主キーであるため、UPSERT の対象となるレコードは 1 つだけです。

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

提案?


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

もしそうなら、私は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 列の新しいデータをブレンドする新しいレコードを書き込む必要がありますか?

4

19 に答える 19

915

テーブルに ID、NAME、ROLE の 3 つの列があるとします。


悪い:これにより、すべての列が 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');

GOOD : SQLite で競合句 の UPSERT サポートに SQLite を使用してください! バージョン 3.24.0 で UPSERT 構文が SQLite に追加されました!

UPSERT は、INSERT が一意性制約に違反する場合に、INSERT が UPDATE またはノーオペレーションとして動作するようにする INSERT への特別な構文の追加です。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 が存在しない場合、ロールはデフォルト値ではなく「Benchwarmer」に設定されます。

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );
于 2010-12-02T00:55:15.833 に答える
144

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 の予想される出力は、役割を変更し、名前を保持することです。

于 2010-11-23T07:46:00.187 に答える
88

あなたが一般的に更新をしているなら、私は..

  1. 取引を開始する
  2. 更新を行う
  3. 行数を確認する
  4. 0 の場合は挿入を行います
  5. 専念

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

  1. 取引を開始する
  2. 挿入してみる
  3. 主キー違反エラーのチェック
  4. エラーが発生した場合は、更新を行います
  5. 専念

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

于 2009-01-07T02:29:13.023 に答える
86

この回答は更新されたため、以下のコメントは適用されなくなりました。

2018-05-18 ストッププレス。

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

UPSERT は、INSERT が一意性制約に違反する場合に INSERT を UPDATE またはノーオペレーションとして動作させる INSERT への特別な構文の追加です。UPSERT は標準 SQL ではありません。SQLite の UPSERT は、PostgreSQL によって確立された構文に従います。

ここに画像の説明を入力

または:

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

于 2015-08-28T12:00:13.723 に答える
65

私はこれが古いスレッドであることを認識していますが、私は最近 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;
于 2011-09-08T19:12:04.013 に答える
15

これは、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;
于 2014-02-27T22:47:54.313 に答える
6

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

また、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";
于 2013-05-21T02:07:41.337 に答える
5

The best approach I know is to do an update, followed by an insert. The "overhead of a select" is necessary, but it is not a terrible burden since you are searching on the primary key, which is fast.

You should be able to modify the below statements with your table & field names to do what you want.

--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
                );
于 2009-01-07T02:01:10.773 に答える
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 関数のヌルを値に置き換えて、使用するデフォルトを決定できます。

于 2016-01-29T06:20:46.947 に答える
2

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

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

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

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

ひ...

于 2009-01-07T02:20:46.240 に答える
-3

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

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

使用する代わりに:INSERT INTO

使用する:INSERT OR REPLACE INTO

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

于 2010-10-07T11:00:32.337 に答える