ВСТАВИТЬСЯ, ЯКЩО НЕ ІСНУЄ ОНОВЛЕННЯ ІНШОГО?


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
Так, це абсолютно неправильно. Що станеться, якщо я просто хочу оновити значення однієї стовпця на конфлікт на нову. У вищенаведеному випадку всі інші дані будуть замінені новими, що є невірними.
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);

"ВИБРАТИ * З даних" дасть вам:

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

Зауважте, що data.id - "3", а не "1", оскільки REPLACE робить ВИДАЛЕННЯ та ВСТАВКА, а не ОНОВЛЕННЯ. Це також означає, що ви повинні гарантувати, що ви визначите всі необхідні стовпці, або ви отримаєте несподівані значення NULL.


33

По-перше, оновіть його. Якщо кількість зачеплених рядків = 0, вставте його. Це найпростіший і підходить для всіх RDBMS .


11
Дві операції не повинні мати жодних проблем з транзакцією на потрібному рівні ізоляції, незалежно від бази даних.
janm

5
Insert or Replaceє справді більш кращим.
MPelletier

1
Я дуже хочу, щоб ІТ-документація містила більше прикладів. Я спробував це нижче, і це не працює (мій синтаксис, очевидно, неправильний). Будь-які ідеї щодо того, яким воно має бути? ВСТАВИТИ У книгу (ім'я, TypeID, рівень, побачені) НАСТРОЙКИ ("Супермен", "2", "14", "0") НА ЗАМІНУ КОНФЛІКТНОЇ ЗАМІНИ (Ім'я, ТипID, Рівень, Побачене) НАСТРОЙКИ ("Супермен", " 2 ',' 14 ',' 0 ')
SparkyNZ

6
Також що робити, якщо значення рядків точно однакові, то кількість вражених рядків буде нульовою, і буде створено новий повторний рядок.
ставка

2
+1, тому що ВСТАВИТЬ АБО ЗАМОВИТИ, буде видалено початковий рядок конфлікту, і якщо ви не встановите всі стовпці, ви втратите початкові значення
Павло Мачиняк

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" не є частиною синтаксису.

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

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 стовпчики без значень за замовчуванням. Незважаючи на те, що це трохи тривале рішення, він виконує роботу належним чином і працює так, як очікувалося.
Inbar Rose

4

Я вважаю, що ви хочете UPSERT .

"ВСТАВИТЬ АБО ЗАМОВИТИ" без додаткової хитрості у цій відповіді скидає поля, які ви не вказали, на NULL або інше значення за замовчуванням. (Така поведінка INSERT OR REPLACE на відміну від UPDATE; вона точно схожа на INSERT, тому що вона насправді є INSERT; однак, якщо ви хотіли UPDATE-якщо є, то ви, ймовірно, хочете семантики UPDATE та будете неприємно здивовані фактичним результатом.)

Хитрість запропонованої реалізації UPSERT полягає в тому, щоб використовувати INSERT АБО ЗАМОВИТИ, але вказати всі поля, використовуючи вбудовані пропозиції SELECT для отримання поточного значення для полів, які ви не бажаєте змінювати.


1

Я думаю, що варто зазначити, що тут може статися деяка несподівана поведінка, якщо ви не досконало розумієте, як взаємодіють ПРИМІТНИЙ КЛЮЧ і УНІКАЛЬНІ .

Наприклад, якщо ви хочете вставити запис лише у тому випадку, якщо в даний момент не буде прийнято поле NAME , і якщо він є, ви хочете сказати вам виняток обмеження для запуску, то ВСТАВИТИСЯ АБО ЗАМОВЛЕННЯ не буде кидати і виняток, а замість цього усуньте обмеження UNIQUE , замінивши конфліктуючий запис (існуючий запис тим самим NAME ). Гаспард демонструє це дійсно добре у своїй відповіді вище.

Якщо ви хочете, щоб виняток з обмеженнями для запуску, вам потрібно використовувати оператор INSERT та покластись на окрему команду UPDATE для оновлення запису, як тільки ви дізнаєтесь, що ім'я не прийнято.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.