SQLite UPSERT/UPDATE ИЛИ INSERT
Мне нужно выполнить UPSERT/INSERT или UPDATE для базы данных SQLite.
Существует команда INSERT OR REPLACE, которая во многих случаях может быть полезна. Но если вы хотите сохранить свой идентификатор с автоинкрементами на месте из-за внешних ключей, он не работает, так как он удаляет строку, создает новую, и, следовательно, эта новая строка имеет новый идентификатор.
Это будет таблица:
игроки - (первичный ключ по идентификатору, уникальное имя пользователя)
| id | user_name | age |
------------------------------
| 1982 | johnny | 23 |
| 1983 | steven | 29 |
| 1984 | pepee | 40 |
Ответы
Ответ 1
Это поздний ответ. Начиная с 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;
Примечание: для тех, кто использует версию SQLite более раннюю, чем 3.24.0, пожалуйста, обратитесь к этому ответу ниже (опубликовано мной, @MarqueIV).
Однако, если у вас есть возможность обновить, настоятельно рекомендуется сделать это, поскольку в отличие от моего решения, размещенное здесь решение достигает желаемого поведения в одном утверждении. Кроме того, вы получаете все другие функции, улучшения и исправления ошибок, которые обычно приходят с более свежим выпуском.
Ответ 2
Q & A Стиль
Хорошо, после нескольких часов исследования и борьбы с этой проблемой, я узнал, что есть два способа сделать это, в зависимости от структуры вашей таблицы, и если у вас есть ограничения на внешние ключи, чтобы поддерживать целостность. Я хотел бы поделиться этим в чистом формате, чтобы сэкономить время людям, которые могут быть в моей ситуации.
Вариант 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, что означало бы, что она будет удалять данные молча. Dangerous.
Итак, я сначала подумал о предложении IF, но SQLite имеет только CASE. И этот CASE нельзя использовать (или, по крайней мере, я его не использовал) для выполнения одного UPDATE-запроса, если EXISTS (выберите id из игроков, где user_name = 'steven), и INSERT, если это не так. Нет.
И затем, наконец, я с успехом использовал грубую силу. Логика заключается в том, что для каждого UPSERT, который вы хотите выполнить, сначала выполните INSERT ИЛИ 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 user_name='steven', age=32 WHERE user_name='steven';
-- Make sure it exists
INSERT OR IGNORE INTO players (user_name, age) VALUES ('steven', 32);
Ответ 3
Здесь подход, который не требует грубой силы "игнорировать", который работал бы только в случае нарушения ключа. Этот способ работает на основе любых условий, указанных вами в обновлении.
Попробуй это...
-- Try to update any existing row
UPDATE players
SET 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, поэтому предложение "Где" в Insert
теперь будет ложным и, следовательно, вставка не будет выполняться.
Никакой грубой силы не требуется.
Ответ 4
Проблема со всеми представленными ответами заключается в отсутствии необходимости принимать триггеры (и, возможно, другие побочные эффекты).
Решение, подобное
INSERT OR IGNORE ...
UPDATE ...
приводит к выполнению обоих триггеров (для вставки, а затем для обновления), когда строка не существует.
Правильное решение
UPDATE OR IGNORE ...
INSERT OR IGNORE ...
в этом случае выполняется только один оператор (когда строка существует или нет).
Ответ 5
Чтобы иметь чистый UPSERT без отверстий (для программистов), которые не ретранслируют по уникальным и другим клавишам:
UPDATE players SET user_name="gil", age=32 WHERE user_name='george';
SELECT changes();
SELECT changes() вернет число обновлений, выполненных в последнем запросе.
Затем проверьте, является ли возвращаемое значение из change() равным 0, если это выполняется:
INSERT INTO players (user_name, age) VALUES ('gil', 32);
Ответ 6
Вы также можете просто добавить предложение ON CONFLICT REPLACE к своему уникальному ограничению user_name и затем просто вставить INSERT, оставив SQLite решать, что делать в случае конфликта. Смотрите: https://sqlite.org/lang_conflict.html.
Также обратите внимание на предложение, касающееся триггеров удаления: когда стратегия разрешения конфликтов REPLACE удаляет строки для удовлетворения ограничения, триггеры удаления срабатывают тогда и только тогда, когда включены рекурсивные триггеры.
Ответ 7
Вариант 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 unique, and we filter by it as well
UPDATE players
SET age=20
WHERE user_name='johnny';
Относительно триггеров
Примечание. Я не тестировал его, чтобы увидеть, какие триггеры вызывается, но я предполагаю следующее:
Если строка не существует
- ПЕРЕД ВСТАВКОЙ
- INSERT с использованием INSTEAD OF
- ПОСЛЕ ВСТАВКИ
- ПЕРЕД ОБНОВЛЕНИЕМ
- ОБНОВЛЕНИЕ с помощью INSTEAD OF
- ПОСЛЕ ОБНОВЛЕНИЯ
Если строка существует
- ПЕРЕД ОБНОВЛЕНИЕМ
- ОБНОВЛЕНИЕ с помощью INSTEAD OF
- ПОСЛЕ ОБНОВЛЕНИЯ
Вариант 2: Вставить или заменить - сохранить свой собственный идентификатор
таким образом вы можете иметь одну команду 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.
Ответ 8
Принятый ответ не верен
из-за его 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'].';');
}
- запрос является функцией, например