Як ОНОВИТИ рядок у таблиці або ВСТАВИТИ його, якщо він не існує?


83

У мене є наступна таблиця лічильників:

CREATE TABLE cache (
    key text PRIMARY KEY,
    generation int
);

Я хотів би збільшити один із лічильників або встановити його на нуль, якщо відповідний рядок ще не існує. Чи є спосіб зробити це без проблем одночасності в стандартному SQL? Операція іноді є частиною транзакції, іноді окремою.

SQL, якщо це можливо, повинен працювати без змін у SQLite, PostgreSQL та MySQL.

Під час пошуку було отримано кілька ідей, які страждають від проблем паралелізму або є специфічними для бази даних:

  • Спробуйте INSERTновий рядок, і UPDATEякщо сталася помилка. На жаль, помилка на INSERTперериває поточну транзакцію.

  • UPDATEрядок, і якщо жоден рядок не був змінений, INSERTновий рядок.

  • MySQL має ON DUPLICATE KEY UPDATEпункт.

EDIT: Дякую за всі чудові відповіді. Схоже, Пол має рацію, і немає жодного портативного способу зробити це. Це для мене досить дивно, оскільки це звучить як дуже елементарна операція.


6
Ви не знайдете жодного рішення, яке б працювало для всіх цих СУБД. Вибачте.
Пол Томблін,

1
можливий дублікат SQLite - UPSERT * not * INSERT or REPLACE
PearsonArtPhoto

Відповіді:


137

MySQL (а згодом і SQLite) також підтримує синтаксис REPLACE INTO:

REPLACE INTO my_table (pk_id, col1) VALUES (5, '123');

Це автоматично ідентифікує первинний ключ і знаходить відповідний рядок для оновлення, вставляючи новий, якщо жодного не знайдено.


13
Насправді, якщо бути конкретним, MySQL REPLACE завжди робить вставку, але спочатку видалить рядок, якщо він уже існує. dev.mysql.com/doc/refman/4.1/en/replace.html
Еван,

90
Важливо розуміти, що це вставка + видалення і ніколи та оновлення. Наслідком цього є те, що ви завжди хочете переконатись, коли будете робити заміну, ви завжди повинні включати дані для всіх полів.
Зоредаче

2
@Zoredache Технічно це "видалити, потім вставити", оскільки (n) "вставити + видалити" фактично те саме, що і видалити, але це розділяє волосся.
Agi Hammerthief

2
@Agihammerthief існує цілком реальна різниця, а саме те, що щойно вставлений рядок НЕ матиме того самого первинного ключа, що і рядок, який було видалено. З ON DUPLICATE це буде той самий первинний ключ (якщо ви спеціально його не зміните).
Тім Стрейдхорст,

32

SQLite підтримує заміну рядка, якщо він уже існує:

INSERT OR REPLACE INTO [...blah...]

Ви можете скоротити це до

REPLACE INTO [...blah...]

Цей ярлик був доданий для сумісності з REPLACE INTOвиразом MySQL .


Це вимагає, щоб ви визначили a PRAMARY KEYу своїх значеннях.
DawnSong

24

Я б зробив щось на зразок наступного:

INSERT INTO cache VALUES (key, generation)
ON DUPLICATE KEY UPDATE (key = key, generation = generation + 1);

Встановлення значення генерації до 0 у коді або в sql, але використання ON DUP ... для збільшення значення. Я думаю, це все одно синтаксис.


1
Ця відповідь повинна бути чесно вибраною відповіддю. Це неруйнівне редагування, якщо ви не подаєте всі поля до запису.
Йорданія

9

пропозиція ON DUPLICATE KEY UPDATE є найкращим рішенням, оскільки: REPLACE робить DELETE, за яким слід INSERT, тому на колись незначний період запис видаляється, створюючи все таку незначну можливість того, що запит може повернутися, пропустивши це, якщо сторінка була переглянуто під час запиту REPLACE.

Я вважаю за краще ВСТАВИТИ ... ПРИ ДВОЙНОМУ ОНОВЛЕННІ ... з цієї причини.

Рішення jmoz є найкращим: хоча я віддаю перевагу синтаксису SET перед дужками

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;

4
REPLACE є атомним, тому немає періоду, коли рядок не існує.
Brilliand

8

Я не знаю, що ви збираєтеся знайти нейтральне для платформи рішення.

Це зазвичай називають "UPSERT".

Перегляньте деякі пов’язані обговорення:


Чи справді це рішення застосовне? і портативний?
Райан Грем

5

У PostgreSQL немає команди злиття, і насправді її написання не є тривіальним - насправді є дивні випадки ребер, які роблять завдання "цікавим".

Найкращим (як у: робота в максимально можливих умовах) є використання функції - такої, яка показана в посібнику (merge_db).

Якщо ви не хочете використовувати функцію, ви зазвичай можете уникнути:

updated = db.execute(UPDATE ... RETURNING 1)
if (!updated)
  db.execute(INSERT...)

Тільки пам’ятайте, що це не доказ вини, і він врешті-решт вийде з ладу.



0

Якщо у вас немає загального способу атомарного оновлення або вставки (наприклад, за допомогою транзакції), ви можете повернутися до іншої схеми блокування. 0-байтовий файл, системний мьютекс, іменований канал тощо ...


0

Чи не могли б ви використати тригер вставки? Якщо це не вдається, зробіть оновлення.


Тригер (принаймні в PostgreSQL) працює, коли команда працювала. тобто у вас не може бути тригера, який запускається, коли базова команда не вдалася.

0

Якщо ви добре користуєтесь бібліотекою, яка пише для вас SQL, тоді ви можете використовувати Upsert (наразі лише Ruby та Python):

Pet.upsert({:name => 'Jerry'}, :breed => 'beagle')
Pet.upsert({:name => 'Jerry'}, :color => 'brown')

Це працює в MySQL, Postgres та SQLite3.

Він пише збережену процедуру або визначену користувачем функцію (UDF) в MySQL та Postgres. Він використовує INSERT OR REPLACEв SQLite3.

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