質問

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

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

SQLiteでこれを行うための賢明な方法はありますか?

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

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

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

提案?


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

もしそうなら、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 更新される各送信行には、次のものが必要です。Prepare / Bind / Step / Finalizeステートメント リセット機能の使用を許可するINSERTとは異なり

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

  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列の新しいデータをブレンドした新しいレコードを書き込む必要がありますか?

役に立ちましたか?

解決

テーブルの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');

良い:これにより、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')
          );

他のヒント

INSERT OR REPLACEは、<!> quot; UPSERT <!> quot;と同等の NOT です。

フィールド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の期待される出力は、役割を変更し、名前を保持することです。

Eric B <!>#8217; sの回答を1つだけ保持する場合はOKですまたは、既存の行の2列かもしれません。多数の列を保持したい場合、面倒になりすぎます。

Here <!>#8217;は、いずれかの側の任意の量の列に適切にスケーリングするアプローチです。それを説明するために、次のスキーマを想定します。

 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は行<!>#8211の自然キーであることに注意してください。 idは外部キーにのみ使用されるため、ポイントは新しい行を挿入するときにSQLiteがID値自体を選択することです。しかし、UPSERTに基づいて既存の行を更新する場合、古いID値を保持し続けたい(明らかに!)。

次の構成で真のINSERT SELECTを達成します:

 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;

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

ここで、行が以前に存在しなかった場合、NULLtsになり、SQLiteは自動的にIDを割り当てますが、すでにそのような行があった場合、DEFAULTには実際の値があり、これは再利用されます。まさに私が望んでいたものです。

実際、これは非常に柔軟です。 new列がすべての側で完全に欠落していることに注意してください<!>#8211;値がoldであるため、SQLiteはどのような場合でも正しいことを行います。そのため、私は<!>#8217;自分で処理する必要はありません。

COALESCE(new.content, old.content)SELECTの両方の側に列を含めることもできます。 <=>外側の<=>に<!>#8220;と言うと、新しいコンテンツがあれば挿入し、そうでなければ古いコンテンツを保持します<!>#8221; <!>#8211;例えば固定クエリを使用していて、新しい値をプレースホルダーにバインドしている場合。

一般的に更新を行う場合は、..

  1. トランザクションを開始する
  2. 更新を行う
  3. 行数を確認する
  4. 0の場合、挿入を行います
  5. コミット

一般的に挿入を行う場合は、

  1. トランザクションを開始する
  2. 挿入を試してください
  3. 主キー違反エラーの確認
  4. エラーが発生した場合、更新を行います
  5. コミット

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

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

2018-05-18 STOP PRESS。

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

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

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

別の方法:

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

これは古いスレッドであることに気づきましたが、私は最近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()の呼び出しが0より大きい場合、sqliteはupdateステートメントを完全に最適化できるというこのビジョンを頭の中に持っています。それが実際にそれを行うかどうかは私の知識を超えていますが、男は夢を見ることができませんか? ;)

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

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

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

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

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

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

言及すべきことの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;

SQLiteでアップサートを実際に実行できます。見た目が以前とは少し異なります。次のようになります:

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

Aristotle <!>#8217; sの回答を展開すると、ダミーの「シングルトン」テーブル(単一の行を持つ独自の作成)。これにより、重複が回避されます。

また、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";

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

ドキュメントから:

  

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

     

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

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

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

私が知っている最善の方法は、更新を実行してから挿入を行うことです。 <!> quot; select <!> quotのオーバーヘッド;が必要ですが、主キーで検索しているため、ひどい負担にはなりません。高速です。

テーブル<!> ampを使用して、以下のステートメントを変更できるはずです。必要なことを行うためのフィールド名。

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

誰かが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);
    });
});
}

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

<!> quot;値<!> quot;は、前に構築する必要がある配列であり、テーブルに挿入または更新する行を表します。

<!> quot; remoteid <!> quot;リモートサーバーと同期しているため、参照として使用したIDです。

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

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

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

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

現在、既に存在するIDでINSERTを実行すると、SQLiteはINSERTではなくUPDATEを自動的に実行します。

Hth ...

このメソッドは、この質問に対する回答からの他のメソッドのいくつかをリミックスし、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を値に置き換えて、使用するデフォルトを決定できます。

Aristotle Pagaltzis およびCOALESCEのアイデアをフォロー/ questions / 418898 /-/ 4330694#4330694 ">エリックB <!>#8217; sの回答、ここではいくつかの列のみを更新するか、存在しない場合は行全体を挿入するためのアップサートオプションです。

この場合、タイトルとコンテンツを更新し、存在する場合は他の古い値を保持し、名前が見つからない場合は提供された値を挿入することを想像してください:

idINSERTの場合、自動インクリメントであると想定されるため、強制的にNULLになります。生成された主キーだけの場合は、new.fieldnameも使用できます(アリストテレス・パガルツィスのコメント)。

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;

したがって、一般的なルールは、古い値を保持する場合は<=>を使用し、値を更新する場合は<=>

を使用することです。

このスレッドを読んだだけで、この<!> quot; UPSERT <!> quot;だけでは簡単ではないことに失望し、さらに調査しました...

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

使用する代わりに:INSERT INTO

使用:INSERT OR REPLACE INTO

これは、あなたがやりたいことを正確に行います!

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

if COUNT(*) = 0

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

else if COUNT(*) > 0

UPDATE table1 SET col1 = var4, col2 = var5, col3 = var6 WHERE id = 1;
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top