ВСТАВИТЬ, ЕСЛИ НЕ СУЩЕСТВУЕТ ДРУГОЕ ОБНОВЛЕНИЕ


275

Я нашел несколько «подходящих» решений для классического «Как вставить новую запись или обновить ее, если она уже существует», но я не могу заставить ни одну из них работать в SQLite.

У меня есть таблица, определенная следующим образом:

CREATE TABLE Book 
ID     INTEGER PRIMARY KEY AUTOINCREMENT,
Name   VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level  INTEGER,
Seen   INTEGER

Что я хочу сделать, это добавить запись с уникальным именем. Если имя уже существует, я хочу изменить поля.

Может кто-нибудь сказать мне, как это сделать, пожалуйста?


3
«вставить или заменить» совершенно отличается от «вставить или обновить»
— Fattie

Ответы:


320

Посмотрите на http://sqlite.org/lang_conflict.html .

Вы хотите что-то вроде:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);

Обратите внимание, что любое поле, отсутствующее в списке вставок, будет установлено в NULL, если строка уже существует в таблице. Вот почему есть выборка для IDстолбца: в случае замены оператор установит его в NULL, а затем будет выделен новый идентификатор.

Этот подход также может быть использован, если вы хотите оставить отдельные значения полей в одиночку, если строка в случае замены, но установить значение NULL в случае вставки.

Например, если вы хотите оставить в Seenпокое:

insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
   (select ID from Book where Name = "SearchName"),
   "SearchName",
    5,
    6,
    (select Seen from Book where Name = "SearchName"));

109
Неправильное «вставить или заменить» отличается от «вставить или обновить». Для действительного ответа см stackoverflow.com/questions/418898/...
— Rds

12
@rds Нет, это не так, потому что в этом вопросе написано «изменить поля», а первичный ключ не является частью списка столбцов, но все остальные поля являются. Если у вас будут угловые случаи, когда вы не заменяете все значения полей, или если вы возитесь с первичным ключом, вы должны сделать что-то другое. Если у вас есть полный набор новых полей, этот подход просто подойдет. У вас есть конкретная проблема, которую я не вижу?
— Янв

9
Это действительно, если вы знаете все новое значение для всех полей. Если пользователь обновляет только, скажем Level, этот подход не может быть использован.
— Rds

4
Правильно . Другой ответ уместен, но этот подход действителен для обновления всех полей (кроме ключа).
— Ярослав Рахматуллин

2
Да, это совершенно неправильно. Что произойдет, если я просто хочу обновить значение одного столбца в Confliction из нового. В вышеупомянутом случае все другие данные будут заменены новыми, которые не являются правильными.
— Mrug

85

Вы должны использовать INSERT OR IGNOREкоманду, за которой следует UPDATEкоманда: В следующем примере nameэто первичный ключ:

INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'

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

Вторая команда обновит запись (которая теперь определенно существует)


6
в какой момент он будет игнорировать? когда имя и возраст совпадают?
— МОД

Это должно быть решением ... если вы используете какой-либо триггер на вставке, принятый ответ срабатывает каждый раз. Это не делает и выполняет только обновление
— PodTech.io

1
Он игнорирует, основываясь исключительно на имени. Помните, что только столбец «имя» является первичным ключом.
— Габриэль Феррер

3
Когда запись новая, тогда обновление не нужно, но все равно будет выполнено, что приведет к снижению производительности?
— MarcG

Как выполнить подготовленное заявление для этого?
— Прашант

65

Вам нужно установить ограничение на таблицу, чтобы вызвать « конфликт », который вы затем разрешите, выполнив замену:

CREATE TABLE data   (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);

Тогда вы можете выдать:

INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);

«SELECT * FROM data» даст вам:

2|2|2|3.0
3|1|2|5.0

Обратите внимание, что data.id равен «3», а не «1», потому что REPLACE выполняет DELETE и INSERT, а не UPDATE. Это также означает, что вы должны убедиться, что вы определили все необходимые столбцы, иначе вы получите неожиданные значения NULL.


33

Сначала обновите это. Если затронутое количество строк = 0, вставьте его. Это самый простой и подходящий для всех РСУБД .


11
У двух операций не должно быть проблем с транзакцией на правильном уровне изоляции, независимо от базы данных.
— Янв

5
Insert or Replaceдействительно более предпочтительным.
— MPelletier

1
Мне бы очень хотелось, чтобы в документации по ИТ было больше примеров. Я попробовал это ниже, и это не работает (мой синтаксис явно неправильный). Есть идеи о том, что это должно быть? INSERT INTO Книга (имя, TypeID, уровень, увиденное) ЗНАЧЕНИЯ («Супермен», «2», «14», «0») ПО ЗАМЕНЕ КОНФЛИКТА Книга (имя, TypeID, уровень, увиденное) ЗНАЧЕНИЯ («Супермен», ' 2 ',' 14 ',' 0 ')
— SparkyNZ

6
Кроме того, что если значения строк в точности совпадают, то число затронутых строк будет равно нулю, и будет создана новая повторяющаяся строка.
— Барксайд

2
+1, потому что INSERT OR REPLACE удалит исходную строку при конфликте, и если вы не установите все столбцы, вы потеряете исходные значения
— Павел Мачиняк

20

INSERT OR REPLACE заменит другие поля на значение по умолчанию.

sqlite> CREATE TABLE Book (
  ID     INTEGER PRIMARY KEY AUTOINCREMENT,
  Name   TEXT,
  TypeID INTEGER,
  Level  INTEGER,
  Seen   INTEGER
);

sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');

sqlite> SELECT * FROM Book;
1001|SQLite|||

Если вы хотите сохранить другое поле

sqlite> SELECT * FROM Book;
1001|C++|10|10|0

sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;

sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0

или используя UPSERT (синтаксис был добавлен в SQLite с версией 3.24.0 (2018-06-04))

INSERT INTO Book (ID, Name)
  VALUES (1001, 'SQLite')
  ON CONFLICT (ID) DO
  UPDATE SET Name=excluded.Name;

В excluded.префикс равно значению в VALUES.


16

Upsert это то, что вы хотите. UPSERTСинтаксис был добавлен в SQLite с версией 3.24.0 (2018-06-04).

CREATE TABLE phonebook2(
  name TEXT PRIMARY KEY,
  phonenumber TEXT,
  validDate DATE
);

INSERT INTO phonebook2(name,phonenumber,validDate)
  VALUES('Alice','704-555-1212','2018-05-08')
  ON CONFLICT(name) DO UPDATE SET
    phonenumber=excluded.phonenumber,
    validDate=excluded.validDate
  WHERE excluded.validDate>phonebook2.validDate;

Имейте в виду, что на данном этапе фактическое слово «UPSERT» не является частью синтаксиса upsert.

Правильный синтаксис

INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...

и если вы делаете INSERT INTO SELECT ...свой выбор, нужно хотя бы WHERE trueрешить неоднозначность синтаксического анализатора относительно токена ONс синтаксисом соединения.

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


Он существует в документации, и у меня есть последняя версия sqlite, которая является 3.25.1, но она не работает для меня
— Bentaiba Miled Basma

вы пробовали эти два запроса выше дословно?
— Товарищ Joecool

Обратите внимание, что Ubuntu 18.04 поставляется с SQLite3 v3.22 , а не с 3.25, поэтому он не поддерживает UPSERTсинтаксис.
— starbeamrainbowlabs

Спасибо за справочную версию по этой функции!
— Маттео

6

Если у вас нет первичного ключа, вы можете вставить, если не существует, а затем сделать обновление. Таблица должна содержать хотя бы одну запись перед использованием.

INSERT INTO Test 
   (id, name)
   SELECT 
      101 as id, 
      'Bob' as name
   FROM Test
       WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;

Update Test SET id='101' WHERE name='Bob';

Это единственное решение, которое сработало для меня без создания дублирующих записей. Вероятно, потому что таблица, которую я использую, не имеет первичных ключей и имеет 2 столбца без значений по умолчанию. Несмотря на то, что это немного длинное решение, оно выполняет свою работу должным образом и работает, как ожидалось.
— Инбар Роуз

4

Я верю, что ты хочешь UPSERT .

«INSERT OR REPLACE» без дополнительного обмана в этом ответе сбрасывает любые поля, которые вы не указали, в NULL или другое значение по умолчанию. (Такое поведение INSERT OR REPLACE отличается от UPDATE; оно в точности похоже на INSERT, потому что на самом деле это INSERT; однако, если вам нужно было UPDATE-if-существует, вы, вероятно, захотите семантику UPDATE и будете неприятно удивлены фактическим результатом.)

Хитрость из предложенной реализации UPSERT заключается в том, чтобы в основном использовать INSERT OR REPLACE, но указать все поля, используя встроенные предложения SELECT, чтобы получить текущее значение для полей, которые вы не хотите изменять.


1

Я думаю, что стоит отметить, что здесь может быть какое-то неожиданное поведение, если вы не до конца понимаете, как PRIMARY KEY и UNIQUE взаимодействуют.

Например, если вы хотите вставить запись, только если поле ИМЯ в настоящее время не занято, и если это так, вы хотите, чтобы исключение ограничения срабатывало, чтобы сообщить вам, тогда INSERT OR REPLACE не будет выбрасывать и исключение и вместо этого будет разрешите само УНИКАЛЬНОЕ ограничение, заменив конфликтующую запись (существующая запись с тем же ИМЯ ). Гаспар очень хорошо демонстрирует это в своем ответе выше.

Если вы хотите, чтобы исключение ограничения сработало, вы должны использовать инструкцию INSERT и полагаться на отдельную команду UPDATE, чтобы обновить запись, как только вы узнаете, что имя не занято.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.