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, когда привязка данных является полной транзакцией, что означает, что каждая отправляемая строка, подлежащая обновлению, требует: операторов Prepare / Bind / Step / Finalize в отличие от INSERT, которая позволяет использовать функцию сброса
Жизнь объекта оператора выглядит примерно так:
- Создайте объект с помощью sqlite3_prepare_v2 ()
- Привязать значения к параметрам хоста, используя интерфейсы sqlite3_bind_.
- Запустите SQL, вызвав sqlite3_step ()
- Сбросьте инструкцию с помощью sqlite3_reset (), затем вернитесь к шагу 2 и повторите.
- Уничтожьте объект оператора с помощью sqlite3_finalize ().
ОБНОВЛЕНИЕ Я предполагаю, медленный по сравнению с INSERT, но как это сравнить с SELECT с использованием первичного ключа?
Возможно, мне следует использовать select, чтобы прочитать 4-й столбец (Blob3), а затем использовать REPLACE, чтобы написать новую запись, смешивая исходный 4-й столбец с новыми данными для первых 3 столбцов?
Ответы:
Предполагая три столбца в таблице: ID, NAME, ROLE
ПЛОХО: Это вставит или заменит все столбцы с новыми значениями для ID = 1:
ПЛОХО: Это вставит или заменит 2 столбца ... столбцу ИМЯ будет присвоено значение NULL или значение по умолчанию:
ХОРОШО : Используйте SQLite On предложение о конфликте поддержки UPSERT в SQLite! Синтаксис UPSERT был добавлен в SQLite с версией 3.24.0!
UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет INSERT вести себя как UPDATE или no-op, если INSERT нарушает ограничение уникальности. UPSERT не является стандартным SQL. UPSERT в SQLite следует синтаксису, установленному PostgreSQL.
ХОРОШО, но старательно: это обновит 2 столбца. Когда ID = 1 существует, имя не изменится. Когда ID = 1 не существует, имя будет значением по умолчанию (NULL).
Это обновит 2 столбца. Когда ID = 1 существует, роль не будет затронута. Когда ID = 1 не существует, роль будет установлена на «Benchwarmer» вместо значения по умолчанию.
источник
INSERT OR REPLACE
а указывает значения для всех столбцов.ВСТАВКА ИЛИ ЗАМЕНА НЕ эквивалентны «UPSERT».
Допустим, у меня есть таблица Employee с полями id, name и role:
Бум, вы потеряли имя сотрудника номер 1. SQLite заменил его значением по умолчанию.
Ожидаемый результат UPSERT будет состоять в том, чтобы изменить роль и сохранить имя.
источник
coalesce((select ..), 'new value')
предложения. Я думаю, что для ответа Эрика здесь нужно больше голосов.Ответ Эрика Б: « Хорошо», если вы хотите сохранить один или два столбца из существующей строки. Если вы хотите сохранить много столбцов, он становится слишком громоздким.
Вот подход, который хорошо масштабируется для любого количества столбцов с обеих сторон. Для иллюстрации приведу следующую схему:
В частности, обратите внимание, что
name
это естественный ключ строки -id
используется только для внешних ключей, поэтому для SQLite важно выбрать само значение идентификатора при вставке новой строки. Но при обновлении существующей строки на ее основеname
я хочу, чтобы она продолжала иметь старое значение идентификатора (очевидно!).Я достигаю истины
UPSERT
с помощью следующей конструкции:Точная форма этого запроса может немного отличаться. Ключом является использование
INSERT SELECT
с левым внешним соединением, чтобы присоединить существующую строку к новым значениям.Здесь, если строка ранее не существовала,
old.id
будет,NULL
и SQLite тогда назначит идентификатор автоматически, но если такая строка уже была,old.id
будет иметь фактическое значение, и это будет использовано повторно. Что именно то, что я хотел.На самом деле это очень гибко. Обратите внимание, что
ts
столбец полностью отсутствует со всех сторон - так как он имеетDEFAULT
значение, SQLite просто сделает правильные вещи в любом случае, поэтому мне не нужно заботиться об этом самому.Вы можете также включить колонку на обоих
new
иold
сторон , а затем использовать , например ,COALESCE(new.content, old.content)
во внешнемSELECT
сказать «вставить новое содержание, не было ли, в противном случае сохранить старое содержание» , - например , если вы используете фиксированный запрос и являются обязательными для нового значения с заполнителями.источник
WHERE name = "about"
ограничение наSELECT ... AS old
ускорение. Если у вас 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' )
ON DELETE
запускает триггеры, когда он выполняет замену (то есть обновление)?ON DELETE
триггеры. Незнаю о излишне. Для большинства пользователей это, вероятно, будет ненужным, даже нежелательным, но, возможно, не для всех пользователей. Аналогичным образом за то, что он также будет каскадно удалять любые строки с внешними ключами в рассматриваемой строке - вероятно, это проблема для многих пользователей. К сожалению, SQLite не имеет ничего общего с настоящим UPSERT. (INSTEAD OF UPDATE
Я полагаю, за исключением подделки его спусковым крючком.)Если вы вообще делаете обновления, я бы ..
Если вы вообще делаете вставки, я бы
Таким образом, вы избегаете выбора и получаете транзакционный звук на Sqlite.
источник
Этот ответ был обновлен, поэтому комментарии ниже не применяются.
2018-05-18 СТОП ПРЕСС.
Поддержка UPSERT в SQLite!Синтаксис UPSERT был добавлен в SQLite с версией 3.24.0 (ожидается)!
UPSERT - это специальное синтаксическое дополнение к INSERT, которое заставляет INSERT вести себя как UPDATE или no-op, если INSERT нарушает ограничение уникальности. UPSERT не является стандартным SQL. UPSERT в SQLite следует синтаксису, установленному PostgreSQL.
альтернативно:
Еще один совершенно другой способ сделать это: в моем приложении я установил в строке rowID значение long.MaxValue при создании строки в памяти. (MaxValue никогда не будет использоваться в качестве идентификатора, который вы не проживете достаточно долго ... Тогда, если rowID не является этим значением, тогда он уже должен быть в базе данных, поэтому требуется ОБНОВЛЕНИЕ, если это MaxValue, тогда ему нужна вставка. Это полезно, только если вы можете отслеживать идентификаторы строк в своем приложении.
источник
WHERE
не может быть добавлено кINSERT
утверждению: sqlite-insert ...Я понимаю, что это старый поток, но в последнее время я работал в sqlite3 и придумал этот метод, который лучше соответствовал моим потребностям в динамическом генерировании параметризованных запросов:
Это по-прежнему 2 запроса с предложением where при обновлении, но, похоже, делает свое дело. У меня также есть такое видение, что sqlite может полностью оптимизировать оператор обновления, если вызов change () больше нуля. Насколько мне известно, действительно ли это на самом деле, но человек может мечтать, не так ли? ;)
Для бонусных баллов вы можете добавить эту строку, которая возвращает вам идентификатор строки, будь то новая вставленная строка или существующая строка.
источник
Update <statement> If @@ROWCOUNT=0 INSERT INTO <statement>
Вот решение, которое действительно является UPSERT (UPDATE или INSERT) вместо INSERT OR REPLACE (которое во многих ситуациях работает по-разному).
Это работает так:
1. Попытайтесь обновить, если существует запись с таким же Id.
2. Если обновление не изменило ни одной строки (
NOT EXISTS(SELECT changes() AS change FROM Contact WHERE change <> 0)
), вставьте запись.Таким образом, либо существующая запись была обновлена, либо будет выполнена вставка.
Важной деталью является использование SQL-функции changes () для проверки попадания оператора обновления в какие-либо существующие записи и выполнения оператора вставки только в том случае, если он не обнаружил ни одной записи.
Следует отметить, что функция changes () не возвращает изменения, выполненные триггерами более низкого уровня (см. Http://sqlite.org/lang_corefunc.html#changes ), поэтому обязательно примите это во внимание.
Вот SQL ...
Тестовое обновление:
Тестовая вставка:
источник
INSERT INTO Contact (Id, Name) SELECT 3, 'Bob' WHERE changes() = 0;
должно также работать.Начиная с версии 3.24.0, UPSERT поддерживается SQLite.
Из документации :
Источник изображения: https://www.sqlite.org/images/syntax/upsert-clause.gif
источник
Вы действительно можете сделать переход в SQLite, он выглядит немного иначе, чем вы привыкли. Это будет выглядеть примерно так:
источник
Расширяя ответ Аристотеля, вы можете ВЫБРАТЬ из фиктивной «синглтонной» таблицы (таблицы вашего собственного создания с одной строкой). Это позволяет избежать некоторого дублирования.
Я также сохранил пример переносимого через MySQL и SQLite и использовал столбец date_added в качестве примера того, как вы можете установить столбец только в первый раз.
источник
Лучший подход, который я знаю, - это сделать обновление с последующей вставкой. «Затраты на выбор» необходимы, но это не страшное бремя, поскольку вы выполняете поиск по первичному ключу, что очень быстро.
Вы должны иметь возможность изменять приведенные ниже операторы с именами таблиц и полей, чтобы делать то, что вы хотите.
источник
Если кто-то хочет прочитать мое решение для SQLite в Кордове, я получил этот общий метод js благодаря ответу @david выше.
Итак, сначала подберите имена столбцов с помощью этой функции:
Затем создайте транзакции программно.
«Значения» - это массив, который вы должны построить раньше, и он представляет строки, которые вы хотите вставить или обновить в таблице.
«remoteid» - это идентификатор, который я использовал в качестве ссылки, поскольку я синхронизируюсь с моим удаленным сервером.
Для использования плагина SQLite Cordova, пожалуйста, обратитесь к официальной ссылке
источник
Этот метод смешивает несколько других методов из ответа на этот вопрос и включает в себя использование CTE (Common Table Expressions). Я представлю запрос, а затем объясню, почему я сделал то, что сделал.
Я хотел бы изменить фамилию сотрудника 300 на ДЭВИС, если есть сотрудник 300. В противном случае я добавлю нового сотрудника.
Имя таблицы: сотрудники Столбцы: id, first_name, last_name
Запрос:
По сути, я использовал CTE, чтобы уменьшить количество раз, когда оператор select должен использоваться для определения значений по умолчанию. Поскольку это CTE, мы просто выбираем нужные столбцы из таблицы, и оператор INSERT использует это.
Теперь вы можете решить, какие значения по умолчанию вы хотите использовать, заменив пустые значения в функции COALESCE на то, какими должны быть значения.
источник
Вслед за Аристотель Pagaltzis и идею
COALESCE
от ответа Эрика Б , вот это вариант upsert обновить только несколько столбцов или вставить полную строку , если она не существует.В этом случае представьте, что заголовок и содержимое должны быть обновлены, сохраняя другие старые значения, если они существуют, и вставляя предоставленные, если имя не найдено:
ПРИМЕЧАНИЕ
id
принудительно принимает значение NULL, когдаINSERT
предполагается автоинкремент. Если это просто сгенерированный первичный ключ,COALESCE
его также можно использовать (см. Комментарий Аристотеля Пагальтзиса ).Таким образом, общее правило: если вы хотите сохранить старые значения, используйте
COALESCE
, когда вы хотите обновить значения, используйтеnew.fieldname
источник
COALESCE(old.id, new.id)
определенно не так с автоинкрементным ключом. И хотя «держать большую часть строки без изменений, за исключением случаев, когда значения отсутствуют» звучит как сценарий использования, который кто-то может иметь на самом деле, я не думаю, что это то, что люди ищут, когда ищут, как сделать UPSERT.old
таблицы, в которой они были назначеныNULL
, а не значения, указанные вnew
. Это причина для использованияCOALESCE
. Я не эксперт в sqlite, я тестировал этот запрос и, кажется, работает для этого случая, я был бы очень признателен, если бы вы могли указать мне на решение с автоинкрементамиNULL
как ключ, потому что это говорит SQLite вместо этого вставить следующее доступное значение.Я думаю, что это может быть то, что вы ищете: ON CONFLICT .
Если вы определите свою таблицу следующим образом:
Теперь, если вы делаете INSERT с уже существующим идентификатором, SQLite автоматически выполняет UPDATE вместо INSERT.
Hth ...
источник
REPLACE
выписка.Если вы не против сделать это в две операции.
шаги:
1) Добавьте новые предметы с помощью «INSERT OR IGNORE»
2) Обновить существующие элементы с помощью «ОБНОВЛЕНИЕ»
Входными данными для обоих шагов является одна и та же коллекция новых или обновляемых элементов. Прекрасно работает с существующими элементами, которые не нуждаются в изменениях. Они будут обновлены, но с теми же данными, и, следовательно, чистый результат не изменится.
Конечно, медленнее и т. Д. Неэффективно. Ага.
Легко написать SQL и поддерживать и понимать это? Определенно.
Это компромисс, чтобы рассмотреть. Прекрасно работает для небольших апперсетов. Прекрасно работает для тех, кто не против жертвовать эффективностью поддержки кода.
источник
Просто прочитав эту ветку и разочаровавшись в том, что нелегко было просто заняться этим "UPSERT", я продолжил расследование ...
Вы можете сделать это прямо и легко в SQLITE.
Вместо использования:
INSERT INTO
Использование:
INSERT OR REPLACE INTO
Это именно то, что вы хотите!
источник
INSERT OR REPLACE
не являетсяUPSERT
. Смотри "ответ" Грегшлома по причине почему. Решение Эрика Б. на самом деле работает и нуждается в некоторых ответах.если
COUNT(*) = 0
иначе если
COUNT(*) > 0
источник