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'].';');
}


  • запрос является функцией, например