Мне нужно выполнить UPSERT / INSERT OR UPDATE для базы данных SQLite.
Есть команда INSERT OR REPLACE, которая во многих случаях может быть полезной. Но если вы хотите сохранить свой идентификатор с автоинкрементом из-за внешних ключей, он не работает, поскольку он удаляет строку, создает новую и, следовательно, эта новая строка имеет новый идентификатор.
Это будет таблица:
игроки - (первичный ключ по id, имя_пользователя уникальное)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
db.execSQL("insert into bla(id,name) values (?,?) on conflict(id) do update set name=?")
. Выдает мне синтаксическую ошибку в слове «on»Стиль вопросов и ответов
Что ж, после нескольких часов изучения проблемы и борьбы с ней я обнаружил, что есть два способа решить эту проблему, в зависимости от структуры вашей таблицы и от того, активированы ли у вас ограничения внешних ключей для поддержания целостности. Я хотел бы поделиться этим в чистом формате, чтобы сэкономить время людям, которые могут оказаться в моей ситуации.
Вариант 1. Вы можете позволить себе удалить строку
Другими словами, у вас нет внешнего ключа, или, если он у вас есть, ваш механизм SQLite настроен так, что нет исключений целостности. Путь - ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ . Если вы пытаетесь вставить / обновить игрока, чей идентификатор уже существует, механизм SQLite удалит эту строку и вставит предоставленные вами данные. Теперь возникает вопрос: что делать, чтобы старый идентификатор оставался связанным?
Допустим, мы хотим выполнить UPSERT с данными user_name = 'steven' и age = 32.
Взгляните на этот код:
Хитрость заключается в слиянии. Он возвращает идентификатор пользователя «steven», если таковой имеется, а в противном случае возвращает новый свежий идентификатор.
Вариант 2. Вы не можете позволить себе удалить строку
Попробовав предыдущее решение, я понял, что в моем случае это может привести к уничтожению данных, поскольку этот идентификатор работает как внешний ключ для другой таблицы. Кроме того, я создал таблицу с предложением ON DELETE CASCADE , что означало бы, что он будет удалять данные без уведомления. Опасно.
Итак, я сначала подумал о предложении IF, но в SQLite есть только CASE . И этот СЛУЧАЙ нельзя использовать (или, по крайней мере, я не справился с этим) для выполнения одного запроса UPDATE, если СУЩЕСТВУЕТ (выберите идентификатор из игроков, где user_name = 'steven'), и INSERT, если это не так. Нет.
И вот, наконец, я успешно применил грубую силу. Логика заключается в том, что для каждого UPSERT, который вы хотите выполнить, сначала выполните INSERT OR IGNORE, чтобы убедиться, что есть строка с нашим пользователем, а затем выполните запрос UPDATE с точно такими же данными, которые вы пытались вставить.
Те же данные, что и раньше: user_name = 'steven' и age = 32.
И это все!
РЕДАКТИРОВАТЬ
Как прокомментировал Энди, попытка сначала вставить, а затем обновить может привести к срабатыванию триггеров чаще, чем ожидалось. На мой взгляд, это не проблема безопасности данных, но действительно, запуск ненужных событий не имеет смысла. Следовательно, улучшенное решение будет:
источник
Вот подход, который не требует «игнорирования» грубой силы, который будет работать только в случае нарушения ключа. Этот способ работает при любых условиях, указанных вами в обновлении.
Попробуй это...
Как это устроено
«Волшебный соус» здесь используется
Changes()
вWhere
предложении.Changes()
представляет количество строк, затронутых последней операцией, которая в данном случае является обновлением.В приведенном выше примере, если после обновления нет никаких изменений (т. Е. Запись не существует), то
Changes()
= 0, поэтомуWhere
предложение вInsert
операторе оценивается как истинное, и новая строка вставляется с указанными данными.Если
Update
действительноChanges()
обновил существующую строку, то = 1 (или, точнее, не ноль, если было обновлено более одной строки), поэтому предложение Where вInsert
now оценивается как false, и, таким образом, вставка не выполняется .Прелесть этого заключается в том, что нет необходимости в грубой силе или излишнем удалении, а затем повторной вставке данных, что может привести к нарушению вышестоящих ключей в отношениях внешнего ключа.
Кроме того, поскольку это просто стандартное
Where
предложение, оно может быть основано на чем угодно, а не только на ключевых нарушениях. Точно так же вы можете использоватьChanges()
в сочетании с любыми другими выражениями, которые вам нужны / разрешены.источник
Changes() = 0
вернет false, а две строки сделают INSERT OR REPLACEUPSERT
? Но даже в этом случае это хорошо , что обновление происходит, настройка,Changes=1
иначеINSERT
оператор будет неправильно запускаться, чего вы не хотите.Проблема со всеми представленными ответами заключается в полном отсутствии учета триггеров (и, возможно, других побочных эффектов). Решение вроде
приводит к выполнению обоих триггеров (для вставки, а затем для обновления), когда строка не существует.
Правильное решение
в этом случае выполняется только один оператор (если строка существует или нет).
источник
UPDATE OR IGNORE
это нужно, поскольку обновление не выйдет из строя, если строки не найдены.Чтобы иметь чистый UPSERT без дыр (для программистов), не использующих уникальные и другие ключи:
SELECT changes () вернет количество обновлений, выполненных в последнем запросе. Затем проверьте, равно ли возвращаемое значение от changes () значение 0, если да, выполните:
источник
Вы также можете просто добавить предложение ON CONFLICT REPLACE к уникальному ограничению user_name, а затем просто INSERT прочь, предоставив SQLite выяснить, что делать в случае конфликта. См. Https://sqlite.org/lang_conflict.html .
Также обратите внимание на предложение о триггерах удаления: когда стратегия разрешения конфликтов REPLACE удаляет строки, чтобы удовлетворить ограничение, триггеры удаления срабатывают тогда и только тогда, когда рекурсивные триггеры включены.
источник
Вариант 1: Вставить -> Обновить
Если вам нравится избегать и того,
changes()=0
и другого, иINSERT OR IGNORE
даже если вы не можете позволить себе удаление строки - вы можете использовать эту логику;Сначала вставьте (если не существует), а затем обновите , отфильтровав уникальный ключ.
пример
Что касается триггеров
Примечание: я не тестировал, какие триггеры вызываются, но предполагаю следующее:
если строка не существует
если строка существует
Вариант 2. Вставьте или замените - оставьте свой идентификатор
таким образом у вас может быть одна команда SQL
Изменить: добавлен вариант 2.
источник