Стиль вопросов и ответов
Что ж, после нескольких часов изучения проблемы и борьбы с ней я обнаружил, что есть два способа решить эту проблему, в зависимости от структуры вашей таблицы и от того, активированы ли у вас ограничения внешних ключей для поддержания целостности. Я хотел бы поделиться этим в чистом формате, чтобы сэкономить время людям, которые могут оказаться в моей ситуации.
Вариант 1. Вы можете позволить себе удалить строку
Другими словами, у вас нет внешнего ключа, или, если он у вас есть, ваш механизм SQLite настроен так, что нет исключений целостности. Путь - ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ . Если вы пытаетесь вставить / обновить игрока, чей идентификатор уже существует, механизм SQLite удалит эту строку и вставит предоставленные вами данные. Теперь возникает вопрос: что делать, чтобы старый идентификатор оставался связанным?
Допустим, мы хотим выполнить UPSERT с данными user_name = 'steven' и age = 32.
Взгляните на этот код:
INSERT INTO players (id, name, age)
VALUES (
coalesce((select id from players where user_name='steven'),
(select max(id) from drawings) + 1),
32)
Хитрость заключается в слиянии. Он возвращает идентификатор пользователя «steven», если таковой имеется, а в противном случае возвращает новый свежий идентификатор.
Вариант 2. Вы не можете позволить себе удалить строку
Попробовав предыдущее решение, я понял, что в моем случае это может привести к уничтожению данных, поскольку этот идентификатор работает как внешний ключ для другой таблицы. Кроме того, я создал таблицу с предложением ON DELETE CASCADE , что означало бы, что он будет удалять данные без уведомления. Опасно.
Итак, я сначала подумал о предложении IF, но в SQLite есть только CASE . И этот СЛУЧАЙ нельзя использовать (или, по крайней мере, я не справился с этим) для выполнения одного запроса UPDATE, если СУЩЕСТВУЕТ (выберите идентификатор из игроков, где user_name = 'steven'), и INSERT, если это не так. Нет.
И вот, наконец, я успешно применил грубую силу. Логика заключается в том, что для каждого UPSERT, который вы хотите выполнить, сначала выполните INSERT OR IGNORE, чтобы убедиться, что есть строка с нашим пользователем, а затем выполните запрос UPDATE с точно такими же данными, которые вы пытались вставить.
Те же данные, что и раньше: user_name = 'steven' и age = 32.
-- make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32);
-- make sure it has the right data
UPDATE players SET user_name='steven', age=32 WHERE user_name='steven';
И это все!
РЕДАКТИРОВАТЬ
Как прокомментировал Энди, попытка сначала вставить, а затем обновить может привести к срабатыванию триггеров чаще, чем ожидалось. На мой взгляд, это не проблема безопасности данных, но действительно, запуск ненужных событий не имеет смысла. Следовательно, улучшенное решение будет:
-- Try to update any existing row
UPDATE players SET age=32 WHERE user_name='steven';
-- Make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32);