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

在SQL Server上插入更新存储过程

在SQLite中是否有一些我没有想到的聪明方法?

基本上,如果记录存在,我想更新四列中的三列, 如果它不存在,我想用第四列的默认(NUL)值插入记录。

ID是主键,因此UPSERT只会有一条记录。

(我试图避免SELECT的开销,以便确定我是否需要更新或插入显然)

建议?


我无法在SQLite网站上确认SQL CREATE的语法。 我没有构建一个演示来测试它,但似乎没有得到支持..

如果是,我有三列,所以实际上看起来像:

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

但前两个blob不会导致冲突,只有ID会 所以我asusme Blob1和Blob2不会被替换(根据需要)


绑定数据时SQLite中的UPDATE是一个完整的事务,意思是 每个要更新的发送行都需要:准备/绑定/步骤/完成语句 不像INSERT允许使用重置功能

语句对象的生命周期如下:

  1. 使用sqlite3_prepare_v2()
  2. 创建对象
  3. 使用sqlite3_bind_ interfaces将值绑定到主机参数。
  4. 通过调用sqlite3_step()
  5. 运行SQL
  6. 使用sqlite3_reset()重置语句,然后返回步骤2并重复。
  7. 使用sqlite3_finalize()销毁语句对象。
  8. 更新我猜测与INSERT相比很慢,但是如何使用主键与SELECT相比呢?

    也许我应该使用select来读取第4列(Blob3),然后使用REPLACE编写一条新记录,将原始的第4列与前3列的新数据混合在一起?

有帮助吗?

解决方案

假设表中有3列.. ID,NAME,ROLE


BAD:这将使用ID = 1的新值插入或替换所有列:

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

BAD:这将插入或替换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 NOT 等同于<!>“UPSERT <!>”;

假设我有表Employee,其中包含字段id,name和role:

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

Boom,您丢失了员工编号1的名称.SQLite已将其替换为默认值。

UPSERT的预期输出是改变角色并保留名称。

Eric B <!>#8217;答案如果你想只保留一个就行了或者可能是现有行中的两列。如果你想保留很多列,那就太快了。

这里<!>#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. 检查rowcount
  4. 如果为0则执行插入
  5. 提交
  6. 如果你一般都在做插页,我会

    1. 开始交易
    2. 尝试插入
    3. 检查主键违规错误
    4. 如果我们收到错误,请执行更新
    5. 提交
    6. 通过这种方式,您可以避免选择,并且您在Sqlite上具有事务性。

此答案已更新,因此以下评论不再适用。

2018-05-18 STOP PRESS。

在SQLite中支持UPSERT! UPSTER语法已添加到SQLite版本3.24。 0(待定)!

UPSERT是INSERT的一种特殊语法添加,如果INSERT违反唯一性约束,则会导致INSERT表现为UPDATE或no-op。 UPSERT不是标准SQL。 SQLite中的UPSERT遵循PostgreSQL建立的语法。

或者:

另一种完全不同的方法是:在我的应用程序中,当我在内存中创建行时,我将内存rowID设置为long.MaxValue。 (MaxValue将永远不会被用作ID,你将不会活得足够长....然后如果rowID不是那个值,那么它必须已经在数据库中,所以如果它是MaxValue需要更新,那么它需要插入。只有在您可以跟踪应用中的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>; 

在更新中仍然有2个带有where子句的查询,但似乎可以解决问题。我也有这样的想法,如果对changes()的调用大于零,sqlite可以完全优化更新语句。它是否真的这样做是我所不知道的,但是一个人可以梦想不能吗? ;)

对于奖励积分,您可以附加此行,该行会返回行的ID,无论是新插入的行还是现有行。

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

这是一个真正的UPSERT(UPDATE或INSERT)而不是INSERT OR REPLACE(在许多情况下工作方式不同)的解决方案。

它的工作原理如下:
1.尝试更新具有相同Id的记录。
2.如果更新未更改任何行(NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)),则插入记录。

因此,现有记录已更新或将执行插入。

重要的细节是使用changes()SQL函数来检查update语句是否命中了任何现有记录,如果没有命中任何记录,则只执行insert语句。

值得一提的是,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中做一个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

扩展亚里士多德<!>#8217;答案,您可以从虚拟'单身'表中选择(表格你自己的创作只有一行)。这避免了一些重复。

我还让这个示例可以在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开始,SQLite支持UPSERT。

来自文档

  

UPSERT是INSERT的一种特殊语法添加,如果INSERT违反唯一性约束,则会导致INSERT表现为UPDATE或no-op。 UPSERT不是标准SQL。 SQLite中的UPSERT遵循PostgreSQL建立的语法。 UPSERT语法被添加到SQLite版本3.24.0(待定)。

     

UPSERT是一个普通的INSERT语句,后跟特殊的ON CONFLICT子句

图片来源: https://www.sqlite.org /images/syntax/upsert-clause.gif

我知道的最佳方法是进行更新,然后进行插入。 选择<!>的开销<!>;是必要的,但这并不是一个可怕的负担,因为你正在搜索主键,这很快。

您应该可以使用表<!>放大器修改以下语句;字段名称可以做你想要的。

--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会自动执行UPDATE而不是INSERT。

... H个

这个方法重新混合了一些其他方法来回答这个问题并结合使用CTE(公用表格表达式)。我将介绍查询,然后解释我为什么做了我做的事。

如果有员工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函数中的空值替换为值,来决定要使用的默认值。

关注亚里士多德Pagaltzis 以及来自COALESCE的想法/ questions / 418898 / - / 4330694#4330694“> Eric B <!>#8217;答案,这里是一个upsert选项,只更新几列或插入完整行(如果它不存在)。

在这种情况下,假设标题和内容应该更新,保留现有的其他旧值,并在找不到名称时插入提供的值:

注意 idINSERT时被强制为NULL,因为它应该是自动增量。如果它只是一个生成的主键,那么new.fieldname也可以使用(参见亚里士多德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;

因此,一般规则是,如果要保留旧值,请使用<=>,当您想要更新值时,请使用<=>

刚刚阅读了这个帖子,并且对于这个<!>“UPSERT <!>”并不容易感到失望,我进一步调查了......

您可以在SQLITE中直接轻松地执行此操作。

而不是使用:INSERT INTO

使用:INSERT OR REPLACE INTO

这正是你想要它做的!

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;
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top