http://en.wikipedia.org/wiki/Upsert
Вставить обновление хранимой процедуры на SQL Server
Есть ли какой-нибудь умный способ сделать это в SQLite, о котором я не думал?
В основном я хочу обновить три из четырех столбцов, если запись существует, Если он не существует, я хочу ВСТАВИТЬ запись со значением по умолчанию (NUL) для четвертого столбца.
ID - это первичный ключ, поэтому в 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, когда данные привязки являются полной транзакцией, что означает Каждая отправленная строка для обновления требует: операторов Prepare / Bind / Step / Finalize. в отличие от INSERT, который позволяет использовать функцию сброса
Жизнь объекта инструкции выглядит примерно так:
ОБНОВЛЕНИЕ Я предполагаю, что он медленный по сравнению с INSERT, но как он сравнивается с SELECT с использованием первичного ключа?
Возможно, мне следует использовать select для чтения 4-го столбца (Blob3), а затем использовать REPLACE для записи новой записи, смешивающей исходный 4-й столбец с новыми данными для первых 3 столбцов?
UPSERT доступен в версии SQLite 3.24.0


Наилучший подход, который я знаю, - это сделать обновление, а затем вставить его. "Накладные расходы на выбор" необходимы, но это не страшное бремя, поскольку вы выполняете поиск по первичному ключу, что происходит быстро.
У вас должна быть возможность изменить приведенные ниже операторы с именами ваших таблиц и полей, чтобы делать то, что вы хотите.
--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
);
Я думаю, что это не очень хорошая идея, потому что вам нужно дважды запросить движок базы данных.
Думаю, это может быть то, что вы ищете: Предложение ON CONFLICT.
Если вы определите свою таблицу так:
CREATE TABLE table1(
id INTEGER PRIMARY KEY ON CONFLICT REPLACE,
field1 TEXT
);
Теперь, если вы выполняете INSERT с уже существующим идентификатором, SQLite автоматически выполняет UPDATE вместо INSERT.
Hth ...
Я не думаю, что это сработает, он уничтожит столбцы, отсутствующие в инструкции вставки.
@Mosor: -1 от меня, извините. Это то же самое, что и выписка REPLACE.
-1, потому что выполняется удаление, а затем вставка, если первичный ключ уже существует.
Если вы вообще делаете обновления, я бы ..
Если вы обычно делаете вставки, я бы
Таким образом, вы избегаете выбора и получаете надежную транзакцию на Sqlite.
Если вы собираетесь проверить количество строк с помощью sqlite3_changes () на 3-м шаге, убедитесь, что вы не используете дескриптор БД из нескольких потоков для модификаций.
Разве следующее не будет менее многословным с тем же эффектом: 1) выбрать таблицу формы идентификатора, где id = 'x' 2) if (ResultSet.rows.length == 0) обновить таблицу, где id = 'x';
Как это будет работать при массовой вставке / обновлении? Хотя это нормально для одного.
Только что прочитав эту ветку и разочаровавшись в том, что было нелегко просто сделать это "UPSERT", я продолжил расследование ...
На самом деле вы можете сделать это напрямую и легко в SQLITE.
Вместо использования: INSERT INTO
Использование: INSERT OR REPLACE INTO
Это именно то, что вы хотите!
-1 INSERT OR REPLACE не является UPSERT. См. Причину в Грегшлом "ответ". Решение Эрика Б. действительно работает и требует нескольких голосов.
INSERT OR REPLACE - это НЕТ, эквивалентное «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")
Бум, вы потеряли имя сотрудника №1. SQLite заменил его на значение по умолчанию.
Ожидаемый результат UPSERT - изменение роли и сохранение имени.
Вот библиотека upsert для Ruby и обновить библиотеку для Python
Но верно то, что INSERT OR REPLACE в таблице с двумя столбцами (без нулей) эквивалентен UPSERT.
Согласитесь, это не 100% апсерт, но в некоторых случаях он может понравиться, как указано выше. Таким образом, программист должен будет сделать собственное суждение.
@QED нет, потому что delete + insert (который является заменой) - это 2 оператора dml, например, со своими собственными триггерами. Это не то же самое, что только один оператор обновления.
Предположим, что в таблице три столбца: 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');
ХОРОШО: использовать SQLite при конфликте Поддержка UPSERT в SQLite! Синтаксис UPSERT был добавлен в SQLite с версией 3.24.0!
UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет INSERT вести себя как UPDATE или бездействовать, если INSERT нарушит ограничение уникальности. UPSERT - это не стандартный SQL. UPSERT в SQLite следует синтаксису, установленному PostgreSQL.
ХОРОШО, но энергично: Это обновит 2 столбца. Когда ID = 1 существует, ИМЯ не изменится. Когда 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')
);
+1 молодец! Встроенное предложение select дает вам возможность переопределить функциональность ON CONFLICT REPLACE по умолчанию, если вам нужно объединить / сравнить старое значение и новое значение для любого поля.
Если на сотрудника ссылаются другие строки с каскадным удалением, то другие строки все равно будут удалены путем замены.
Добавьте предложение limit в подзапрос: выберите имя из Employee, где id = 1 limit 1. Хотя SQLite может не вызывать здесь ошибки, это хорошая практика, потому что другие диалекты SQL действительно требуют такого ограничения.
Последний запрос неверен. Это должно быть: coalesce ((выберите роль из Employee, где id = 1), 'Benchwarmer')
Извините, но, может быть, автор ошибся в описании четвертого примера? Должно быть «Когда существует ID = 1, РОЛЬ не будет затронут»., потому что ROLE является третьим параметром в четвертом запросе.
Не могли бы вы объяснить, почему Это вставит или заменит все столбцы новыми значениями для ID = 1: считается ПЛОХО в вашем первом примере? Представленная вами команда предназначена для создания новой записи с идентификатором 1, именем Джон Фу и ролью Исполнительный директор или перезаписи записи с идентификатором 1, если она уже существует, этими данными (при условии, что я бы является первичным ключом). Так почему же плохо, если именно это и происходит?
Это плохо, потому что последние примеры позволяют «если этот атрибут не установлен, установите его на это значение, в противном случае оставьте его». Разница в том, что один принудительно перезаписывает все значения, а другой позволяет более детально контролировать, что перезаписывается, а что устанавливается только в случае вставки. Подумайте: обновите запись пользователя для вашего суперадминистратора. «Если пользователя не существует, я устанавливаю его уровень на« новичок », в противном случае я сохраняю его уровень (здесь суперадминистратор)». Было бы неплохо сделать вашего администратора новичком. </ надуманный пример> :) См. также stackoverflow.com/a/4253806/122764 ниже.
@Cornelius: Это ясно, но это не то, что происходит в первом примере. Первый пример предназначен для принудительной установки всех столбцов, что и происходит, независимо от того, вставлена запись или заменена. Итак, почему это считается плохим? Связанный ответ также только указывает, почему может случиться что-то плохое при указании подмножества столбцов, как в вашем примере второй; похоже, что здесь не говорится о каких-либо плохих последствиях того, что происходит в вашем примере первый, INSERT OR REPLACE, при указании значений для столбцов все.
Обратите внимание, что это решение запускает триггеры удаления, а также может привести к нарушению внешнего ключа, потому что строка удаляется первой при конфликте, чего вы не ожидаете при выполнении UPSERT.
id = unique, если это первичный ключ, так что плохого в замене?
Взлом верхнего комментария: UPSERT теперь официально поддерживается SQLite
@ORMapper - это становится плохо, потому что если дополнительные столбцы добавляются в таблицу позже и заполняются из других вызовов, тогда вам нужно следить за каждым экземпляром «вставить или заменить» и добавлять дополнительные столбцы, иначе они будут обнулены / установить по умолчанию
Это только у меня, или "ХОРОШО, но быстро" должно быть здесь "ХОРОШО, но скучный"?
Я понимаю, что это старый поток, но в последнее время я работал с 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 в обновлении, но, похоже, трюк помогает. У меня также есть идея, что sqlite может полностью оптимизировать оператор обновления, если вызов changes () больше нуля. Так ли это на самом деле, мне неизвестно, но мужчина может мечтать, не так ли? ;)
Для получения бонусных баллов вы можете добавить эту строку, которая возвращает идентификатор строки, будь то новая вставленная строка или существующая строка.
select case changes() WHEN 0 THEN last_insert_rowid() else <primaryKeyValue> end;
Ответ Эрика Б. в порядке, если вы хотите сохранить только один или, может быть, два столбца из существующей строки. Если вы хотите сохранить много столбцов, это быстро становится слишком громоздким.
Вот подход, который хорошо масштабируется для любого количества столбцов с любой стороны. Чтобы проиллюстрировать это, я возьму следующую схему:
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 сам выбирает значение идентификатора при вставке новой строки. Но при обновлении существующей строки на основе ее 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, чтобы сказать «вставьте новый контент, если он есть, в противном случае оставьте старый контент» - например, если вы используете фиксированный запрос и связываете новые значения с заполнителями.
+1, отлично работает, но добавьте ограничение WHERE name = "about" на SELECT ... AS old, чтобы ускорить процесс. Если у вас 1 миллион строк, это очень медленно.
Хорошее замечание, +1 к вашему комментарию. Я оставлю это вне ответа, потому что добавление такого предложения WHERE требует как раз такого рода избыточности в запросе, которую я пытался избежать в первую очередь, когда придумал этот подход. Как всегда: когда вам нужна производительность, денормализуйте - в данном случае структуру запроса.
Вы можете упростить пример Аристотеля до следующего, если хотите: 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' )
Умный. Однако мне это не нравится со второго взгляда, как с первого, потому что он заставляет вас дважды записывать (или связывать) значение 'about'. Если вы хотите избежать этого, нет другого решения, кроме как поместить его в левый SELECT в соединении. И тогда вы могли бы также поместить туда все другие новые значения. Но это заставило меня заметить, что нет смысла использовать LEFT JOIN на (SELECT …) - можно просто LEFT JOIN page old ON old.name = new.name. Ох. Кроме того, можно использовать CTE, чтобы сделать его чище. Я соответствующим образом обновил ответ.
Разве это не приведет к ненужному срабатыванию триггеров ON DELETE, когда он выполняет замену (то есть обновление)?
Это обязательно вызовет триггеры ON DELETE. Незнаю о т.к. Для большинства пользователей это, вероятно, было бы ненужным, даже нежелательным, но, возможно, не для всех пользователей. Точно так же и тот факт, что он также будет каскадно удалять любые строки с внешними ключами в рассматриваемой строке - вероятно, проблема для многих пользователей. К сожалению, SQLite не имеет ничего общего с настоящим UPSERT. (Думаю, за исключением подделки с триггером INSTEAD OF UPDATE.)
COALESCE и ваше решение WHEN, чтобы получить отсутствующий обновить только некоторые строки, но вставить мои значения, если они не найдены без боли множества SELECTS в COALESCE.
@Miquel: Использование COALESCE было в ответе EricB еще до того, как я написал свой, и я с самого начала упомянул, что вы можете использовать COALESCE в любом месте SELECT в моем примере, если вам это нужно. Но для UPSERT (о котором идет речь в этом вопросе) моему решению фактически не нужен COALESCE - в отличие от ответа EricB - потому что LEFT JOIN выполняет эту работу. WITH на самом деле совершенно неуместен, он только делает запрос немного более аккуратным. Уловка к моему ответу - комбинация INSERT SELECT LEFT JOIN.
@AristotlePagaltzis, как упоминалось в моем ответе, проблема, с которой я столкнулся с этим запросом, заключается в том, что он вставляет значения NULL для столбцов old.xxxx, когда столбец не существует, это причина, по которой я предложил COALESCE(old.content, new.content). В этом случае он выбирает new.content, когда old.content не существует (случай INSERT).
COALESCE(old.content, new.content) означает, что new.content будет проигнорирован, когда old.content имеет значение. Если это то, что вам нужно, хорошо, но изначально задавался не этот вопрос.
Расширяя Ответ Аристотеля, вы можете ВЫБРАТЬ из фиктивной таблицы «singleton» (таблица, созданная вами с одной строкой). Это позволяет избежать дублирования.
Я также сохранил переносимый пример для 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";
Вот решение, которое на самом деле представляет собой UPSERT (UPDATE или INSERT) вместо INSERT OR REPLACE (который во многих ситуациях работает по-разному).
Работает это так:
1. Попробуйте обновить, если существует запись с таким же идентификатором.
2. Если обновление не изменило ни одной строки (NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)), то вставьте запись.
Таким образом, либо существующая запись была обновлена, либо будет выполнена вставка.
Важной деталью является использование функции SQL changes (), чтобы проверить, попал ли оператор обновления в какие-либо существующие записи, и выполнить оператор вставки только в том случае, если он не попал ни в одну запись.
Следует упомянуть, что функция 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;
Это кажется мне лучшим решением, чем решения Эрика. Однако INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0; тоже должен работать.
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 отлично справится с этим за один запрос.
Этот ответ был обновлен, поэтому комментарии ниже больше не применимы.
2018-05-18 СТОП-ПРЕСС.
Синтаксис Поддержка UPSERT в SQLite! UPSERT был добавлен в SQLite с версией 3.24.0 (ожидается)!
UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет INSERT вести себя как UPDATE или бездействовать, если INSERT нарушит ограничение уникальности. UPSERT - это не стандартный SQL. UPSERT в SQLite следует синтаксису, установленному PostgreSQL.
альтернативно:
Другой совершенно другой способ сделать это: в моем приложении я устанавливаю свой rowID в памяти на long.MaxValue, когда я создаю строку в памяти. (MaxValue никогда не будет использоваться в качестве идентификатора, вы не проживете достаточно долго .... Тогда, если rowID не является этим значением, тогда он должен уже быть в базе данных, поэтому требуется ОБНОВЛЕНИЕ, если это MaxValue, тогда ему нужна вставка. Это полезно только в том случае, если вы можете отслеживать идентификаторы строк в своем приложении.
INSERT INTO table(...) SELECT ... WHERE changes() = 0; у меня работает.
Красиво и просто, но есть состояние гонки, когда строка удаляется между обновлением и вставкой, не так ли?
@ w00t Что, если вы запустите это внутри транзакции?
@copolii на самом деле не уверен, что этого достаточно, возможно, он должен заблокировать строку?
Так работает модуль nodejs sqlite-upsert. github.com/pbrandt1/sqlite3-upsert/blob/master/index.js
Этот метод представляет собой ремикс некоторых других методов из ответа на этот вопрос и включает использование CTE (общих табличных выражений). Я представлю запрос, а затем объясню, почему я сделал то, что сделал.
Я хотел бы изменить фамилию сотрудника 300 на ДЭВИС, если есть сотрудник 300. В противном случае я добавлю нового сотрудника.
Название таблицы: сотрудники Столбцы: 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 на то, какими должны быть значения.
Если кто-то хочет прочитать мое решение для 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, пожалуйста, обратитесь к официальному ссылка на сайт
Следуя Аристотель Пагальцис и идее 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, я тестировал этот запрос и, похоже, работает в этом случае, я был бы очень благодарен, если бы вы могли указать мне решение с автоинкрементами
В случае автоинкрементного ключа вы хочу вставляете NULL в качестве ключа, потому что это говорит 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
Image source: https://www.sqlite.org/images/syntax/upsert-clause.gif
Android по-прежнему находится на уровне 3.19 с API 27
Обновления от Бернхардта:
Вы действительно можете выполнить апсерт в SQLite, просто он выглядит немного иначе, чем вы привыкли. Это выглядело бы примерно так:
INSERT INTO table_name (id, column1, column2)
VALUES ("youruuid", "value12", "value2")
ON CONFLICT(id) DO UPDATE
SET column1 = "value1", column2 = "value2"
На самом деле это неправильно. Должно получиться так: INSERT INTO table_name (id, column1, column2) VALUES (123, 'value1', 'value2') ON CONFLICT (id) DO UPDATE SET column1 = 'value1', column2 = 'value2'
Да, понятно, вставке тоже нужен идентификатор. Я доработаю его, чтобы он был более правильным. Тоже нравится конфликт на id.
Если не возражаете, проделайте это за две операции.
Шаги:
1) Добавьте новые элементы с помощью «ВСТАВИТЬ ИЛИ ИГНОРИРОВАТЬ»
2) Обновите существующие элементы с помощью «ОБНОВЛЕНИЕ»
Входными данными для обоих шагов является один и тот же набор новых или обновляемых элементов. Прекрасно работает с существующими элементами, которые не нуждаются в изменениях. Они будут обновлены, но с теми же данными, и, следовательно, чистый результат - без изменений.
Конечно, медленнее и т. д. Неэффективно. Ага.
Легко написать sql и поддерживать и понимать его? Определенно.
Это компромисс, который следует учитывать. Отлично подходит для небольших апсертов. Отлично подходит для тех, кто не против пожертвовать эффективностью ради удобства сопровождения кода.
Всем на заметку: ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ - НЕ о чем этот пост. ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ создаст НОВУЮ строку в вашей таблице с новым идентификатором. Это не ОБНОВЛЕНИЕ.
SQLite - UPSERT доступен в предварительной версии, ссылка: sqlite.1065341.n5.nabble.com/…