SQLite - UPSERT *not* INSERT or REPLACE

后端 未结 18 2612
猫巷女王i
猫巷女王i 2020-11-21 23:53

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

Insert Update stored proc on SQL Server

Is there some clever way to do this in SQLite that I have not thought of?

相关标签:
18条回答
  • 2020-11-22 00:30

    I think this may be what you are looking for: ON CONFLICT clause.

    If you define your table like this:

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

    Now, if you do an INSERT with an id that already exists, SQLite automagically does UPDATE instead of INSERT.

    Hth...

    0 讨论(0)
  • 2020-11-22 00:31
    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;
    
    0 讨论(0)
  • 2020-11-22 00:32

    If you are generally doing updates I would ..

    1. Begin a transaction
    2. Do the update
    3. Check the rowcount
    4. If it is 0 do the insert
    5. Commit

    If you are generally doing inserts I would

    1. Begin a transaction
    2. Try an insert
    3. Check for primary key violation error
    4. if we got an error do the update
    5. Commit

    This way you avoid the select and you are transactionally sound on Sqlite.

    0 讨论(0)
  • 2020-11-22 00:33

    Following Aristotle Pagaltzis and the idea of COALESCE from Eric B’s answer, here it is an upsert option to update only few columns or insert full row if it does not exist.

    In this case, imagine that title and content should be updated, keeping the other old values when existing and inserting supplied ones when name not found:

    NOTE id is forced to be NULL when INSERT as it is supposed to be autoincrement. If it is just a generated primary key then COALESCE can also be used (see Aristotle Pagaltzis comment).

    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;
    

    So the general rule would be, if you want to keep old values, use COALESCE, when you want to update values, use new.fieldname

    0 讨论(0)
  • 2020-11-22 00:34

    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
                    );
    
    0 讨论(0)
  • 2020-11-22 00:35

    Eric B’s answer is OK if you want to preserve just one or maybe two columns from the existing row. If you want to preserve a lot of columns, it gets too cumbersome fast.

    Here’s an approach that will scale well to any amount of columns on either side. To illustrate it I will assume the following schema:

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

    Note in particular that name is the natural key of the row – id is used only for foreign keys, so the point is for SQLite to pick the ID value itself when inserting a new row. But when updating an existing row based on its name, I want it to continue to have the old ID value (obviously!).

    I achieve a true UPSERT with the following construct:

     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;
    

    The exact form of this query can vary a bit. The key is the use of INSERT SELECT with a left outer join, to join an existing row to the new values.

    Here, if a row did not previously exist, old.id will be NULL and SQLite will then assign an ID automatically, but if there already was such a row, old.id will have an actual value and this will be reused. Which is exactly what I wanted.

    In fact this is very flexible. Note how the ts column is completely missing on all sides – because it has a DEFAULT value, SQLite will just do the right thing in any case, so I don’t have to take care of it myself.

    You can also include a column on both the new and old sides and then use e.g. COALESCE(new.content, old.content) in the outer SELECT to say “insert the new content if there was any, otherwise keep the old content” – e.g. if you are using a fixed query and are binding the new values with placeholders.

    0 讨论(0)
提交回复
热议问题