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

275

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

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

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

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

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

SparkyNZ
источник
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"));
janm
источник
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'

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

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

moshik
источник
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, вставьте его. Это самый простой и подходящий для всех РСУБД .

Burcin
источник
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.

Steely Wing
источник
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...виду, что удалит запись перед вставкой новой, если ее придется заменить, что может быть плохо, если у вас есть каскады внешних ключей или другие триггеры удаления.

ComradeJoecool
источник
Он существует в документации, и у меня есть последняя версия 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, чтобы получить текущее значение для полей, которые вы не хотите изменять.

metamatt
источник
1

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

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

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

Мэтт Матиас
источник