SQLite UPSERT / обновить или вставить
Мне нужно выполнить UPSERT / INSERT или UPDATE против базы данных SQLite.
есть команда INSERT или REPLACE, которая во многих случаях может быть полезна. Но если вы хотите сохранить свой идентификатор с автоинкрементом на месте из-за внешних ключей, он не работает, так как он удаляет строку, создает новую, и, следовательно, эта новая строка имеет новый идентификатор.
Это будет таблица:
игроки - (первичный ключ на id, имя_пользователя уникальный)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
8 ответов:
это поздний ответ. Начиная с SQLIte 3.24.0, выпущенного 4 июня 2018 года, наконец-то появилась поддержка UPSERT предложение после синтаксиса PostgreSQL.
INSERT INTO players (user_name, age) VALUES('steven', 32) ON CONFLICT(user_name) DO UPDATE SET age=excluded.age;
Q&A Style
Ну, после исследования и борьбы с проблемой в течение нескольких часов, я узнал, что есть два способа сделать это, в зависимости от структуры вашей таблицы, и если у вас есть внешние ключи ограничения активированы для поддержания целостности. Я хотел бы поделиться этим в чистом формате, чтобы сэкономить время для людей, которые могут быть в моей ситуации.
Вариант 1: Вы можете позволить себе удалить строку
другими словами, у вас нет внешнего ключа, или если они у вас есть, ваш движок SQLite настроен так, чтобы не было исключений целостности. Путь - это ВСТАВИТЬ ИЛИ ЗАМЕНИТЬ. Если вы пытаетесь вставить / обновить плеер, идентификатор которого уже существует, механизм SQLite удалит эту строку и вставит данные, которые вы предоставляете. Теперь возникает вопрос: Что делать, чтобы сохранить прежний идентификатор?
допустим, мы хотим UPSERT С данными user_name= 'steven' и Возраст=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 имеет только случае. А это случае не может быть использован (или, по крайней мере, мне это не удалось), чтобы выполнить один обновление запрос, если существует (выберите id из игроков, где user_name= 'steven'), и вставить если это не так. Не пойдет.
и тогда, наконец, я использовал грубую силу, с успехом. Логика есть, для каждого UPSERT что вы хотите выполнить, сначала выполните a ВСТАВИТЬ ИЛИ ИГНОРИРОВАТЬ чтобы убедиться, что есть строка с нашим пользователем, а затем выполнить обновление запрос с точно такими же данными вы пытались вставить.
те же данные, что и раньше: user_name='steven' и возраст=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';
и это все!
EDIT
как прокомментировал Энди, пытаясь сначала вставить, а затем обновить может привести к срабатыванию триггеров чаще, чем ожидалось. На мой взгляд, это не проблема безопасности данных, но это правда, что запуск ненужных событий имеет мало смысла. Таким образом, улучшенное решение будет:
-- Try to update any existing row UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; -- Make sure it exists INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32);
вот подход, который не требует грубой силы "игнорировать", который будет работать только в случае ключевого нарушения. Этот способ работает на основе любой условия, указанные в обновление.
попробуйте это...
-- Try to update any existing row UPDATE players SET user_name='steven', age=32 WHERE user_name='steven'; -- If no update happened (i.e. the row didn't exist) then insert one INSERT INTO players (user_name, age) SELECT 'steven', 32 WHERE (Select Changes() = 0);
Как Это Работает
"магия" здесь вы используете
Where (Select Changes() = 0)
предложение, чтобы определить, есть ли какие-либо строки для вставки, и так как это основано на вашем собственномWhere
предложение, это может быть для всего, что вы определяете, а не только основные нарушения.в приведенном выше примере, если нет никаких изменений от обновления (т. е. запись не существует), то
Changes()
= 0, поэтомуWhere
п. вInsert
оператор возвращает значение true, и новая строка вставляется с указанными данными.если
Update
сделал обновить существующую строку, затемChanges()
= 1, поэтому предложение 'Where' вInsert
теперь будет false и, таким образом, вставка не будет иметь место.нет грубой силы необходимый.
проблема со всеми представленными ответами это полное отсутствие учета триггеров (и, возможно, других побочных эффектов). Решение, как
INSERT OR IGNORE ... UPDATE ...
приводит к выполнению обоих триггеров (для вставки, а затем для обновления), когда строка не существует.
правильное решение-это
UPDATE OR IGNORE ... INSERT OR IGNORE ...
в этом случае выполняется только один оператор (когда строка существует или нет).
чтобы иметь чистый UPSERT без отверстий (для программистов) , которые не ретранслируют уникальные и другие ключи:
UPDATE players SET user_name="gil", age=32 WHERE user_name='george'; SELECT changes();
SELECT changes () вернет количество обновлений, сделанных в последнем запросе. Затем проверьте, если возвращаемое значение из changes () равно 0, если так выполнить:
INSERT INTO players (user_name, age) VALUES ('gil', 32);
Вариант 1: Вставить - > Обновить
если вы хотите, чтобы избежать как
changes()=0
иINSERT OR IGNORE
даже если вы не можете позволить себе удалить строку - вы можете использовать эту логику;первый, вставить (если не существует), а потом обновление путем фильтрации с помощью уникального ключа.
пример
-- Table structure CREATE TABLE players ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name VARCHAR (255) NOT NULL UNIQUE, age INTEGER NOT NULL ); -- Insert if NOT exists INSERT INTO players (user_name, age) SELECT 'johnny', 20 WHERE NOT EXISTS (SELECT 1 FROM players WHERE user_name='johnny' AND age=20); -- Update (will affect row, only if found) -- no point to update user_name to 'johnny' since it's unique, and we filter by it as well UPDATE players SET age=20 WHERE user_name='johnny';
В Отношении Триггеры
обратите внимание: я не проверял его, чтобы увидеть, какие триггеры вызывают, но я предположим в следующее:
если строка не существует
- ПЕРЕД ВСТАВКОЙ
- вставить с помощью вместо
- ПОСЛЕ ВСТАВКИ
- ПЕРЕД ОБНОВЛЕНИЕМ
- обновление с помощью вместо
- ПОСЛЕ ОБНОВЛЕНИЯ
если строка не существует
- ПЕРЕД ОБНОВЛЕНИЕМ
- обновление с помощью вместо
- ПОСЛЕ ОБНОВЛЕНИЯ
Вариант 2: вставить или заменить - сохранить свой собственный ID
таким образом вы можете иметь одну команду SQL
-- Table structure CREATE TABLE players ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_name VARCHAR (255) NOT NULL UNIQUE, age INTEGER NOT NULL ); -- Single command to insert or update INSERT OR REPLACE INTO players (id, user_name, age) VALUES ((SELECT id from players WHERE user_name='johnny' AND age=20), 'johnny', 20);
изменить: добавлена опция 2.
вы также можете просто добавить предложение on CONFLICT REPLACE к вашему уникальному ограничению user_name, а затем просто вставить его, оставив его SQLite, чтобы выяснить, что делать в случае конфликта. Смотрите:https://sqlite.org/lang_conflict.html.
также обратите внимание на предложение относительно триггеров удаления: когда стратегия разрешения конфликтов замены удаляет строки для удовлетворения ограничения, триггеры удаления срабатывают тогда и только тогда, когда рекурсивные триггеры включены.
принятый ответ не является правильным
потому что его 2 запроса
только комплекс !!
это простой 2 запрос:$check=query('select id from players where user_name="steven";'); if(empty($check)) { query('insert into players (user_name,age) values ("steven",32);'); } else { query('update players set age=13 where id='.$check['id'].';'); }
- запрос-это функция, например