SQLite - UPSERT * не * ВСТАВКА або ЗАМІНА


535

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

Вставте оновлення, що зберігається, на SQL Server

Чи є якийсь розумний спосіб зробити це в SQLite, про який я не думав?

В основному я хочу оновити три з чотирьох стовпців, якщо запис існує, якщо його немає, я хочу ВСТАВИТИ запис із значенням за замовчуванням (NUL) для четвертого стовпця.

Ідентифікатор є первинним ключем, тому до UPSERT буде лише один запис.

(Я намагаюся уникати накладних витрат SELECT, щоб визначити, чи потрібно мені ОНОВНІСТЬ або ВСТАВИТИ)

Пропозиції?


Я не можу підтвердити, що синтаксис на SQLite-сайті для TABLE CREATE. Я не створив демонстраційну версію для тестування, але вона, здається, не підтримується.

Якби це було, у мене є три стовпці, щоб це насправді виглядало так:

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

але перші два краплі не викличуть конфлікту, лише ідентифікатор, так що я вважаю, що Blob1 і Blob2 не будуть замінені (за бажанням)


ОНОВЛЕННЯ в SQLite, коли дані прив'язки є повною транзакцією, тобто кожен відправлений рядок для оновлення вимагає: Підготувати / Зв’язати / Крок / Завершити операції на відміну від INSERT, що дозволяє використовувати функцію скидання

Життя об’єкта висловлювання виглядає приблизно так:

  1. Створіть об’єкт за допомогою sqlite3_prepare_v2 ()
  2. Прив’яжіть значення до параметрів хоста за допомогою інтерфейсів sqlite3_bind_.
  3. Запустіть SQL, зателефонувавши sqlite3_step ()
  4. Скиньте оператор за допомогою sqlite3_reset (), потім поверніться до кроку 2 та повторіть.
  5. Знищіть об'єкт оператора за допомогою sqlite3_finalize ().

ОНОВЛЕННЯ Я здогадуюсь повільний порівняно з INSERT, але як він порівняється з SELECT за допомогою первинного ключа?

Можливо, я повинен використати select, щоб прочитати 4-й стовпчик (Blob3), а потім використовувати ЗАМЕНИТИ, щоб написати новий запис, змішуючи оригінальний 4-й стовпець з новими даними для перших 3 стовпців?


5
SQLite - UPSERT доступний у попередньому випуску, див .: sqlite.1065341.n5.nabble.com/…
gaurav

3
UPSERT доступний у версії 3.24.0 SQLite
pablo_worker

Відповіді:


854

Припустимо, що у таблиці три стовпці: ідентифікатор, ім’я, роль


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

ДОБРО : Використовуйте SQLite On конфліктне ставлення Підтримка UPSERT в SQLite! Синтаксис UPSERT додано до SQLite з версією 3.24.0!

UPSERT - це спеціальне синтаксичне доповнення до INSERT, яке змушує INSERT поводитись як ОНОВЛЕННЯ або непридатність, якщо INSERT порушує обмеження унікальності. UPSERT не є стандартним SQL. UPSERT в SQLite дотримується синтаксису, встановленого PostgreSQL.

введіть тут опис зображення

ДОБРИЙ, але тенденційний: це оновить 2 стовпці. Якщо ідентифікатор = 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 існує, РОЛЬ не вплине. Коли ID = 1 не існує, замість значення за замовчуванням роль буде встановлена ​​на "Benchwarmer".

INSERT OR REPLACE INTO Employee (id, name, role) 
  VALUES (  1, 
            'Susan Bar',
            COALESCE((SELECT role FROM Employee WHERE id = 1), 'Benchwarmer')
          );

33
+1 блискуче! Вбудований пункт вибору надає вам можливість змінити функцію ON CONFLICT REPLACE за замовчуванням, якщо вам потрібно поєднати / порівняти старе значення та нове значення для будь-якого поля.
G__

24
Якщо працівник посилається на інші рядки з каскадним видаленням, то інші рядки все одно будуть видалені шляхом заміни.
Дон Реба

246
Це боляче. SQlite потребує UPSERT.
andig

21
Чи можете ви пояснити, чому це буде вставляти або замінювати всі стовпці новими значеннями для ID = 1: вважається BAD у вашому першому прикладі? Команда, яку ви там представили, призначена для створення нової записи з ідентифікатором 1, імені Джона Фоо та виконавчого директора ролі , або замінення запису, ідентифікатор якого 1, якщо він вже є, тими даними (припускаючи, що ідентифікатор є первинним ключем) . Отже, чому це погано, якщо саме так і відбувається?
АБО Mapper

10
@ Корнелій: Це зрозуміло, але це не те, що відбувається в першому прикладі. Перший приклад призначений для примусового встановлення всіх стовпців, саме так і відбувається, незалежно від того, вставляється чи замінюється запис. Отже, чому це вважається поганим? Зв'язана відповідь також лише вказує, чому може трапитися щось погане, коли вказується підмножина стовпців, як у вашому другому прикладі; Схоже, це не пояснює жодних поганих наслідків того, що відбувається у вашому першому прикладі, INSERT OR REPLACEвказуючи значення для всіх стовпців.
АБО Mapper

134

INSERT OR REPLACE є НЕ еквівалентно "UPSERT".

Скажіть, у мене є таблиця Співробітник з полями id, ім'я та роль:

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 буде зміна ролі та збереження назви.


21
-1 від мене боюся. Так, прийнята відповідь неправильна, але, хоча ваша відповідь вказує на проблему, це теж не відповідь. Дійсна відповідь, див . Розумне рішення Еріка Б. з використанням вбудованого coalesce((select ..), 'new value')пункту. Відповіді Еріку тут потрібно більше голосів.
День

22
Справді. Ерік, безумовно, найкраща відповідь і заслуговує більше голосів. Зважаючи на це, я думаю, що, вказуючи на проблему, я трохи сприяв пошуку хорошої відповіді (відповідь Еріка з’явилася пізніше і спирається на зразки таблиць sql у моїй відповіді). Тож не впевнений, чи заслуговую на -1, але
ніколи

6
+1, щоб компенсувати -1 вище. Лол. Цікава тимчасова шкала на цій темі. Очевидно, що ваша відповідь була за тиждень до Еріка, але ви обоє відповіли на це питання майже через два роки після того, як його задали. +1 також для Еріка, хоча для детальної роботи.
Багатий


2
@QED ні, тому що delete + insert (що є заміною) - це 2 dml заяви, наприклад, з власними тригерами. Це не те саме, що лише 1 заяву оновлення.
Себас

109

Відповідь Еріка В гаразд, якщо ви хочете зберегти лише один або, можливо, два стовпці з існуючого рядка. Якщо ви хочете зберегти багато стовпців, це стає занадто громіздким.

Ось підхід, який зможе масштабувати будь-яку кількість стовпців з обох сторін. Щоб проілюструвати це, я прийму наступну схему:

 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 полягає в тому, щоб під час вставлення нового рядка SQLite сам вибирав значення ідентифікатора. Але оновлюючи існуючий рядок на основі його name, я хочу, щоб він і надалі мав старе значення ідентифікатора (очевидно!).

Я досягаю правди UPSERTза допомогою наступної конструкції:

 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 потім призначить ідентифікатор автоматично, але якщо такий рядок уже був, old.idматиме фактичне значення і це буде повторно використане. А це саме те, що я хотів.

Насправді це дуже гнучко. Зауважте, як tsстовпець повністю відсутній з усіх боків - оскільки він має DEFAULTзначення, SQLite просто зробить правильну справу в будь-якому випадку, тому я не повинен сам про це піклуватися.

Ви можете також включити колонку на обох newі oldсторін , а потім використовувати , наприклад , COALESCE(new.content, old.content)в зовнішньому SELECTсказати «вставити новий зміст, чи не було, в іншому випадку зберегти старий зміст» , - наприклад , якщо ви використовуєте фіксований запит і є обов'язковими для нового значення із заповнювачами.


13
+1, чудово працює, але додайте WHERE name = "about"обмеження SELECT ... AS oldдля прискорення роботи. Якщо у вас 1м + рядки, це дуже повільно.

Хороший пункт, +1 на ваш коментар. Я залишаю це без відповіді, тому що додавання такого WHEREпункту вимагає просто такої надмірності в запиті, яку я намагався в першу чергу усунути, коли я придумав такий підхід. Як завжди: коли вам потрібна продуктивність, денормалізація - структура запиту, в цьому випадку.
Арістотель Пагалціс

4
Ви можете спростити приклад аристотеля до цього, якщо ви хочете: INSERT OR REPLACE INTO page (id, name, title, content, author) SELECT id, 'about', 'About this site', content, 42 FROM ( SELECT NULL ) LEFT JOIN ( SELECT * FROM page WHERE name = 'about' )
jcox

4
Чи не буде це зайвим ON DELETEспрацьовувати, коли він виконує заміну (тобто оновлення)?
Каракурі

3
Це, безумовно, спричинить ON DELETEспрацьовування. Давно про непотрібність. Для більшості користувачів це, мабуть, буде непотрібним, навіть небажаним, але, можливо, не для всіх користувачів. Так само за те, що він також буде каскадно видаляти будь-які рядки із сторонніми ключами у відповідний рядок - ймовірно, це проблема для багатьох користувачів. На жаль, SQLite не має нічого ближчого до реального UPSERT. (Окрім того INSTEAD OF UPDATE, що, напевно, підробляють його
куражем

86

Якщо ви зазвичай робите оновлення, я б ..

  1. Почніть операцію
  2. Зробіть оновлення
  3. Перевірте кількість рядків
  4. Якщо це 0, зробіть вставку
  5. Здійснити

Якщо ви, як правило, робите вставки, я б

  1. Почніть операцію
  2. Спробуйте вставити
  3. Перевірте наявність помилки в первинному ключі
  4. якщо ми отримали помилку, зробіть оновлення
  5. Здійснити

Таким чином ви уникаєте вибору, і ви трансакційно звучать на Sqlite.


Спасибі, просто попросили це @ stackoverflow.com/questions/2717590 / ... . +1 від мене! =)
Алікс Аксель

4
Якщо ви збираєтеся перевірити кількість рядків за допомогою sqlite3_changes () на 3-му кроці, переконайтеся, що ви не використовуєте для обробки змін обробку БД з декількох потоків.
Лінулін

3
Чи не будуть наступні менш словними, але з тим самим ефектом: 1) виберіть таблицю форми id, де id = 'x' 2), якщо (ResultSet.rows.length == 0) оновити таблицю, де id = 'x';
Флорін

20
Я дуже хотів, щоб ВСТАВКА АБО ОНОВЛЕНО було частиною мови
Флорін

Це закінчилось для мене найкращим чином, намагаючись зробити ЗАМІНУВАТИ ВНІШЕ або завжди, а вставка / оновлення вбивало мою ефективність, коли я в першу чергу робив оновлення замість вставок.
PherricOxide

83

Цю відповідь було оновлено, тому коментарі нижче вже не застосовуються.

2018-05-18 ЗАСТОСУЙТЕ ПРЕСС.

Підтримка UPSERT в SQLite! Синтаксис UPSERT додано до SQLite з версією 3.24.0 (очікує на розгляд)!

UPSERT - це спеціальне синтаксичне доповнення до INSERT, яке змушує INSERT поводитись як ОНОВЛЕННЯ або непридатність, якщо INSERT порушує обмеження унікальності. UPSERT не є стандартним SQL. UPSERT в SQLite дотримується синтаксису, встановленого PostgreSQL.

введіть тут опис зображення

альтернативно:

Ще один зовсім інший спосіб зробити це: У своїй програмі я встановив, що в пам’яті rowID пам’яті буде long.MaxValue, коли я створюю рядок у пам’яті. (MaxValue ніколи не буде використовуватися як ідентифікатор, ви не будете жити досить довго .... Тоді, якщо rowID не є таким значенням, то він повинен бути вже в базі даних, тому потрібне оновлення, якщо це MaxValue, тоді йому потрібна вставка. Це корисно лише в тому випадку, якщо ви можете відстежувати rowIDs у вашому додатку.


5
Амінь. Просте - краще, ніж складне. Це здається трохи простішим, ніж прийнята відповідь.
ккурян

4
Я подумав, що ти не можеш ВСТУПИТИ У ... ДЕ в sqlite? Це синтаксична помилка для мене в sqlite3
Чарлі Мартін,

5
@CharlieMartin вірно. Цей синтаксис недійсний для SQLite - ось що вимагав ОП. Заява WHEREне може бути додана до INSERTзаяви: sqlite-insert ...
colm.anseo

4
Ця відповідь у мене втратила багато часу, питання стосується SQLITE, і я не знав, що ВСТАВИТЬ БУДЬ, де не було підтримано цілком, будь ласка, додайте цю примітку, що вона не відповідає для sqlite у вашій відповіді
Miquel

3
@colminator та інші: я виправив його SQL-заяву, використовуючи вашу пропозицію.
F Lekschas

61

Я розумію, що це стара тема, але я працював у 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 запити, де застереження про оновлення, але, здається, це робити. У мене також є таке бачення, що sqlite може повністю оптимізувати оператор оновлення, якщо виклик змін () більший за нуль. Незалежно від того, чи це насправді це, що не виходить, але людина може мріяти, чи не може він? ;)

Для бонусних очок ви можете додати цей рядок, який повертає вам ідентифікатор рядка, будь то нещодавно вставлений рядок або існуючий рядок.

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

+ 1. Саме те, що я намагався замінити, роблячи перетворення з деякого коду TSQL SQL Server. Дякую. SQL, який я намагався замінити, був подібнийUpdate <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
DarrenMB

14

Ось рішення, яке насправді являє собою UPSERT (UPDATE або INSERT) замість ВСТАВЛЕННЯ АБО ЗАМІНИ (що працює у різних ситуаціях по-різному).

Це працює так:
1. Спробуйте оновити, якщо існує запис із тим самим ідентифікатором.
2. Якщо оновлення не змінило жодних рядків ( NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), тоді вставити запис.

Таким чином, або оновлений запис було оновлено, або буде виконана вставка.

Важливою деталлю є використання функції SQL змін (), щоб перевірити, чи потрапляє оператор оновлення в будь-які існуючі записи, і виконувати лише оператор вставлення, якщо він не потрапив до жодної записи.

Варто зазначити, що функція change () не повертає зміни, які виконуються тригерами нижчого рівня (див. 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;

2
Це здається для мене кращим рішенням, ніж Ерік. Однак INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;також має працювати.
bkausbk

Дякую, людина, він також працює у WebSQL (використовуючи плагін Cordova та SQLite)
Zappescu

11

Починаючи з версії 3.24.0 UPSERT підтримується SQLite.

З документації :

UPSERT - це спеціальне синтаксичне доповнення до INSERT, яке змушує INSERT поводитись як ОНОВЛЕННЯ або непридатність, якщо INSERT порушує обмеження унікальності. UPSERT не є стандартним SQL. UPSERT в SQLite дотримується синтаксису, встановленого PostgreSQL. Синтаксис UPSERT додано до SQLite з версією 3.24.0 (очікує на розгляд).

UPSERT - це звичайне твердження INSERT, яке супроводжується спеціальним пунктом ON CONFLICT

введіть тут опис зображення

Джерело зображення: https://www.sqlite.org/images/syntax/upsert-clause.gif


8

Ви дійсно можете зробити перерву в 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

5

Розширюючи відповідь Арістотеля, ви можете ВИБІРАТИ з манекена «одинарний» таблицю (таблицю власного творіння з одним рядком). Це дозволяє уникнути певного дублювання.

Я також зберігав приклад портативного в 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";

4

Найкращий підхід, який я знаю, - це оновлення, а потім вставка. "Накладні витрати на вибір" необхідні, але це не страшний тягар, оскільки ви шукаєте на первинному ключі, який швидко.

Ви повинні мати змогу змінювати наведені нижче оператори з іменами таблиці та полів, щоб робити те, що ви хочете.

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

7
Шлях до складного.
фраст

Я вважаю, що це не дуже гарна ідея, оскільки вам потрібно зробити двічі запит до двигуна бази даних.
Рікардо да Роша Вітор

3

Якщо хтось хоче прочитати моє рішення для SQLite в Кордові, я отримав цей загальний метод js завдяки відповіді @david вище.

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

Потім будуйте транзакції програмно.

"Значення" - це масив, який слід створити раніше, і він представляє рядки, які ви хочете вставити або оновити в таблицю.

"remoteid" - це ідентифікатор, який я використовував як довідник, оскільки синхронізуюсь із віддаленим сервером.

Для використання плагіна SQLite Cordova перейдіть на офіційне посилання


2

Цей метод реміксує декілька інших методів з відповіді на це питання і включає використання CTE (Загальні вирази таблиць). Я введу запит, потім поясню, чому я зробив те, що робив.

Я хотів би змінити прізвище працівника 300 на DAVIS, якщо є працівник 300. В іншому випадку я додам нового працівника.

Назва таблиці: Співробітники Стовпці: id, ім’я, прізвище

Запит:

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 на те, якими мають бути значення.


2

Слідом за Аристотель Pagaltzis і ідею COALESCEвід відповіді Еріка Б , ось це варіант upsert оновити тільки декілька стовпців або вставити повну рядок , якщо вона не існує.

У цьому випадку уявіть, що заголовок та вміст слід оновити, зберігаючи інші старі значення, коли вони існують, та вставляючи надані, коли ім’я не знайдено:

ПРИМІТКА id змушений бути NULL тоді, коли INSERTвін повинен бути автоматичним збільшенням. Якщо це лише генерований первинний ключ, то COALESCEйого також можна використовувати (див. Коментар Арістотеля Пагальціса ).

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


COALESCE(old.id, new.id)напевно не так з ключем автоматичного збільшення. І хоча "зберігати більшу частину рядка незмінною, за винятком випадків, коли значення відсутні," це звучить як випадок використання, який у когось насправді може бути, я не думаю, що саме це шукають люди, шукаючи, як зробити UPSERT.
Арістотель Пагалціс

@AristotlePagaltzis просимо вибачення, якщо я помиляюся, я не використовую автоматичне підвищення. Що я шукаю за допомогою цього запиту, це оновити лише декілька копій або вставити повний рядок із наданими значеннями, якщо його немає . Я грав з вашим запитом, і я не міг його досягти під час вставки: стовпці, вибрані з oldтаблиці, куди призначено NULL, а не до значень, наданих у new. Це причина використання COALESCE. Я не фахівець з sqlite, я тестував цей запит і, здається, працював у цій справі, я б вам дуже вдячний, якщо ви могли б вказати мені на рішення з автоматичним підвищенням
Miquel

2
У разі автоматичного збільшення ключа, ви хочете вставити NULLяк ключ, тому що це говорить SQLite замість цього вставити наступне доступне значення.
Арістотель Пагалціс

Я не кажу, що ви не повинні робити те, що ви робите (якщо вам це потрібно, то вам це потрібно), просто що це насправді не відповідь на питання тут. Як правило, UPSERT означає, що у вас є рядок, який ви хочете зберегти в таблиці, і просто не знаєте, чи вже у вас є відповідна рядок, щоб ввести значення або потрібно вставити їх як новий рядок. Ваш випадок використання полягає в тому, що якщо у вас вже є відповідна рядок, ви хочете ігнорувати більшість значень з нового рядка . Це добре, це просто не питання, яке задавали.
Арістотель Пагалціс

1

Я думаю, це саме те, що ви шукаєте: пункт ON CONFLICT .

Якщо ви визначите свою таблицю так:

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

Тепер, якщо ви робите INSERT з ідентифікатором, який вже існує, SQLite автоматично робить UPDATE замість INSERT.

Хт ...


6
Я не думаю, що це працює, це знищить стовпці, відсутніх у заяві на вставку
Sam Saffron

3
@Mosor: -1 від мене, вибач. Це те саме, що виписувати REPLACEзаяву.
Алікс Аксель

7
-1 тому, що це видаляє, а потім вставляє, якщо первинний ключ вже існує.
фраст

0

Якщо ви не проти зробити це за дві операції.

Кроки:

1) Додайте нові елементи за допомогою "ВСТАВИТЬ АБО IGNORE"

2) Оновіть наявні елементи за допомогою "UPDATE"

Вхід до обох етапів - однакова колекція нових або оновлених елементів. Добре працює з існуючими елементами, які не потребують змін. Вони будуть оновлюватися, але з тими ж даними, тому чистий результат не буде змін.

Звичайно, повільніше і т. Д. Неефективна. Так.

Легко написати sql та підтримувати та розуміти його? Безумовно.

Це компроміс для розгляду. Чудово підходить для невеликих наборів. Чудово підходить для тих, хто не проти жертвувати ефективністю для ремонту коду.


Зауважте всім: ВСТАВИТИ АБО ЗАМОВИТИ НЕ, про що йдеться у цій публікації. ВСТАВИТИ АБО ЗАМОВИТИ створить НОВУ рядок у вашій таблиці з новим ідентифікатором. Це не ОНОВЛЕННЯ.
sapbucket

-2

Щойно прочитавши цю тему і розчарувавшись у тому, що не просто було до цього "UPSERT", я дослідив далі ...

Насправді це можна зробити прямо і легко в SQLITE.

Замість використання: INSERT INTO

Використання: INSERT OR REPLACE INTO

Це робить саме те, що ви хочете!


21
-1 INSERT OR REPLACEне є UPSERT. Дивіться "відповідь" gregschlom з причини, чому. Рішення Еріка В насправді працює і потребує певних результатів.
День

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

Це занадто складно, SQL може впоратись із цим просто за один запит
Робін Кантерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.