SQLite для вставки / обновления или вставки

Мне нужно выполнить UPSERT / INSERT или UPDATE против базы данных SQLite.

существует команда INSERT или REPLACE, которая во многих случаях может быть полезна. Но если вы хотите сохранить свои идентификаторы с автоинкрементом из-за внешних ключей, он не работает, так как он удаляет строку, создает новую и, следовательно, эта новая строка имеет новый идентификатор.

Это будет таблица:

players - (первичный ключ на 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 & Стиль

Ну, после исследования и борьбы с проблемой в течение нескольких часов, я узнал, что есть два способа сделать это, в зависимости от структуры вашей таблицы и если у вас есть ограничения внешних ключей, активированные для поддержания целостности. Я хотел бы поделиться этим в чистом формате, чтобы сэкономить время людям, которые могут быть в моей ситуации.


Вариант 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: вы не можете позволить себе удалить строку

после игры с предыдущим решением я понял, что в моем случае это может привести к уничтожению данных, так как этот идентификатор работает как внешний ключ для другой таблицы. Кроме того, я создал таблицу с пункт НА УДАЛИТЬ КАСКАД, что означало бы, что он удалит данные молча. Опасный.

Итак, я сначала подумал о предложении IF, но SQLite имеет только случае. И это случае нельзя использовать (или, по крайней мере, мне это не удалось) для выполнения одного обновление запрос, если существует (выберите id из игроков, где user_name= 'steven') и вставить если это не так. Нет.

и затем, наконец, я использовал грубую силу, с успехом. Логика есть, для каждого UPSERT что вы хотите выполнить, сначала выполнить ВСТАВИТЬ ИЛИ ИГНОРИРОВАТЬ чтобы убедиться, что есть строка с нашим пользователем, а затем выполнить обновление запрос с точно такими же данными вы пытались вставить.

те же данные, что и раньше: 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); 

вот подход, который не требует грубой силы "игнорировать", который будет работать только в случае ключевого нарушения. Этот способ работает на основе любой условия, указанные в обновление.

попробуйте это...

-- 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);

Edit: добавлена опция 2.


вы также можете просто добавить предложение on CONFLICT REPLACE в уникальное ограничение user_name, а затем просто вставить, оставив его SQLite, чтобы выяснить, что делать в случае конфликта. Смотри:https://sqlite.org/lang_conflict.html.

также обратите внимание на предложение, касающееся триггеров удаления: когда стратегия разрешения конфликтов REPLACE удаляет строки, чтобы удовлетворить ограничение, триггеры удаления срабатывают, если и только если рекурсивные триггеры включены.


принятый ответ не является правильным
обусловленно его 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'].';');
}


  • запрос-это функция, например