关于sql:SQLite – UPSERT *不是* INSERT或REPLACE

SQLite - UPSERT *not* INSERT or REPLACE

http:///wiki/upsert产生

插入在SQL Server存储过程更新

有没有一些聪明的方式这样做,我没有在SQLite的思想?

基本上我想要更新三出四列,如果记录存在,如果它不存在一个要插入记录默认(空)值为四列。

这是一个主键ID将只会有这样的一个upsert记录。

(i)在试图避免的开销选择为了determin如果我需要更新或插入的人)

建议的?

我无法确认这是语法上的SQLite的表创建的网站。我需要建立一个测试演示它,但它似乎支持的轨道。

如果它是什么,我有三列这样的酒店,实际上看起来像:

1
2
3
4
5
6
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是一个完整的事务,的意思每个发送的更新需要:准备行步/ / /绑定调用语句它允许使用不同的位置插入的复位功能

生活是一本:什么样的语句对象

  • 创建对象使用_ SQLite3 _ V2(准备)
  • 主机使用绑定参数的价值_ SQLite3的_绑定接口。
  • 通过调用_运行SQL程序(步骤)
  • 使用复位复位_ SQLite3的声明()然后回到步骤2和重复。
  • 声明对象的破坏_ finalize()的使用管理。
  • 更新我一直就这样在事后来劝告是慢比插入,但诀窍是compare to select使用主键?

    也许我应该阅读使用SELECT列第四blob3)然后使用replace写新的混合原始记录与新数据4柱3列为第一?


    假设表中有3列。身份证、姓名、角色

    错误:这将插入或替换ID=1的所有列,并使用新值:

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

    错误:这将插入或替换2列…名称列将设置为空或默认值:

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

    好:这将更新其中2列。当id=1存在时,名称将不受影响。当id=1不存在时,名称将为默认值(空)。

    1
    2
    3
    4
    5
    INSERT OR REPLACE INTO Employee (id, ROLE, name)
      VALUES (  1,
                'code monkey',
                (SELECT name FROM Employee WHERE id = 1)
              );

    这将更新其中2列。当id=1存在时,角色将不受影响。当id=1不存在时,角色将被设置为"Benchwarmer",而不是默认值。

    1
    2
    3
    4
    5
    INSERT OR REPLACE INTO Employee (id, name, ROLE)
      VALUES (  1,
                'Susan Bar',
                COALESCE((SELECT ROLE FROM Employee WHERE id = 1), 'Benchwarmer')
              );


    插入或替换不等于"向上插入"。

    假设我让表EMPLOYEE具有字段ID、名称和角色:

    1
    2
    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的预期输出将是更改角色并保留名称。


    如果您只想保留现有行中的一列或两列,那么EricB的答案是可以的。如果你想保留大量的列,那么它会很快变得过于繁琐。

    这里有一种方法可以很好地扩展到任意一侧的任意数量的列。为了说明这一点,我将假设以下模式:

    1
    2
    3
    4
    5
    6
    7
    8
     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

    1
    2
    3
    4
     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在任何情况下都会做正确的事情,所以我不必亲自处理它。

    您还可以在newold两侧都包含一列,然后在外部SELECT中使用例如COALESCE(new.content, old.content)来表示"如果有新内容,请插入新内容,否则保留旧内容"—例如,如果您使用的是固定查询,并且正在将新值与占位符绑定。


    如果你通常在做更新,我会……

  • 开始交易
  • 做更新
  • 检查行数
  • 如果是0,则插入
  • 提交
  • 如果你一般是做插页,我会

  • 开始交易
  • 尝试插入
  • 检查主键冲突错误
  • 如果有错误,请更新
  • 提交
  • 这样就避免了选择,并且您在sqlite上的事务处理能力很好。


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

    2018-05-18停止按压。

    在sqlite中追加支持!upsert语法已添加到sqlite中,版本为3.24.0(待定)!

    upsert是对insert的一种特殊语法添加,如果insert违反了唯一性约束,它会使insert表现为update或no op。upsert不是标准的SQL。SQLite中的upsert遵循PostgreSQL建立的语法。

    enter image description here

    可选地:

    另一种完全不同的方法是:在应用程序中,当我在内存中创建行时,我将内存中的rowid设置为long.maxvalue。(MaxValue将永远不会被用作ID,您将活得不够长…。如果rowid不是该值,那么它必须已经在数据库中,因此需要更新(如果它是maxvalue),然后需要插入。只有在应用程序中可以跟踪rowids时,这才有用。


    我意识到这是一个旧线程,但我最近一直在sqlite3中工作,并提出了更适合我动态生成参数化查询的方法:

    1
    2
    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子句,但似乎可以做到这一点。我的头脑中也有这样的想法:如果对changes()的调用大于零,则sqlite可以完全优化掉update语句。这是否真的超出了我的知识范围,但一个人可以做梦,不是吗?;)

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

    1
    SELECT CASE changes() WHEN 0 THEN last_insert_rowid() ELSE <primaryKeyValue> END;


    这里的解决方案实际上是一个upsert(更新或插入),而不是一个insert或replace(在许多情况下工作方式不同)。

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

    因此,要么更新现有记录,要么执行插入。

    重要的细节是使用changes()sql函数检查update语句是否命中任何现有记录,并且仅在未命中任何记录时执行insert语句。

    值得一提的是,changes()函数不会返回由较低级别触发器执行的更改(请参见http://sqlite.org/lang ou corefunc.html changes),因此请务必考虑到这一点。

    这是SQL…

    测试更新:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    --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;

    测试插入件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    --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,它看起来和你习惯的有点不同。它看起来像:

    1
    2
    3
    4
    INSERT INTO TABLE name (column1, column2)
    VALUES ("value12","value2") WHERE id = 123
    ON CONFLICT DO UPDATE
    SET column1 ="value1", column2 ="value2" WHERE id = 123

    扩展亚里士多德的答案,你可以从一个虚拟的"singleton"表(你自己创建的一个表,只有一行)中选择。这样可以避免一些重复。

    我还让这个示例在mysql和sqlite中保持可移植性,并使用了一个"date-added"列作为示例,说明如何只在第一次设置列。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     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 is a special syntax addition to INSERT that causes the INSERT to behave as an UPDATE or a no-op if the INSERT would violate a uniqueness constraint. UPSERT is not standard SQL. UPSERT in SQLite follows the syntax established by PostgreSQL. UPSERT syntax was added to SQLite with version 3.24.0 (pending).

    An UPSERT is an ordinary INSERT statement that is followed by the special ON CONFLICT clause

    enter image description here

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


    我知道的最好的方法是更新,然后插入。"select的开销"是必要的,但这并不是一个可怕的负担,因为您正在搜索主键,它很快。

    您应该能够用表和字段名修改下面的语句,以满足您的需要。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    --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
                    );


    如果有人想阅读我在科尔多瓦的sqlite解决方案,我得到了这个通用的JS方法,这要归功于上面的@david answer。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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");
            });
        });
    });
    }

    所以,首先用这个函数选取列名:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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);
        });
    });
    }

    然后以编程方式构建事务。

    "values"是一个应该在前面构建的数组,它表示要在表中插入或更新的行。

    "remoteid"是我用作引用的ID,因为我正在与远程服务器同步。

    有关sqlite cordova插件的使用,请参阅官方链接


    该方法混合了回答中的其他几个方法,并结合了CTE(公共表表达式)的使用。我将介绍这个问题,然后解释为什么我做了我所做的。

    如果有员工300,我想把员工300的姓改成戴维斯。否则,我将添加一个新员工。

    表名:员工列:ID、名、姓

    查询是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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函数中的空值来决定要使用什么默认值。


    遵循亚里士多德·帕戈尔齐斯(Aristotle Pagaltzis)和埃里克·B(Eric B)的回答,这里是一个upsert选项,只更新少数列或插入完整行(如果不存在的话)。

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

    注:当INSERT被认为是自动递增时,id被强制为空。如果它只是一个生成的主键,那么也可以使用COALESCE(参见亚里士多德·帕戈尔茨的评论)。

    1
    2
    3
    4
    5
    6
    7
    8
    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


    我想这可能是你想要的:关于冲突条款。

    如果您这样定义您的表:

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

    现在,如果使用已经存在的ID进行插入,则sqlite将自动更新而不是插入。

    嗯…


    我刚刚读了这篇文章,对自己不容易就这样"冒进"感到失望,于是我进一步调查了……

    实际上,您可以在sqlite中直接轻松地执行此操作。

    不使用:INSERT INTO

    用途:INSERT OR REPLACE INTO

    这正是你想要的!


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

    如果COUNT(*) = 0

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

    如果是COUNT(*) > 0

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