Как мне обновить строку в таблице или вставить ее, если она не существует? - PullRequest
80 голосов
/ 27 марта 2009

У меня есть следующая таблица счетчиков:

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

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

SQL должен работать без изменений на SQLite, PostgreSQL и MySQL, если это возможно.

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

  • Попробуйте INSERT новую строку и UPDATE, если произошла ошибка. К сожалению, ошибка INSERT прерывает текущую транзакцию.

  • UPDATE строка, и если строки не были изменены, INSERT новая строка.

  • В MySQL есть предложение ON DUPLICATE KEY UPDATE.

РЕДАКТИРОВАТЬ: Спасибо за все великолепные ответы. Похоже, что Пол прав, и нет единого, портативного способа сделать это. Это довольно удивительно для меня, так как это звучит как очень простая операция.

Ответы [ 10 ]

132 голосов
/ 27 марта 2009

MySQL (и впоследствии SQLite) также поддерживают синтаксис REPLACE INTO:

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

Это автоматически идентифицирует первичный ключ и находит соответствующую строку для обновления, вставляя новую, если ничего не найдено.

31 голосов
/ 27 марта 2009

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

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

Вы можете сократить это до

REPLACE INTO [...blah...]

Этот ярлык был добавлен для совместимости с выражением MySQL REPLACE INTO.

20 голосов
/ 27 марта 2009

Я бы сделал что-то вроде следующего:

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

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

9 голосов
/ 29 марта 2009

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

Я предпочитаю INSERT ... ON DUPLICATE UPDATE ... по этой причине.

Решение jmoz является лучшим: хотя я предпочитаю синтаксис SET для круглых скобок

INSERT INTO cache 
SET key = 'key', generation = 'generation'
ON DUPLICATE KEY 
UPDATE key = 'key', generation = (generation + 1)
;
8 голосов
/ 27 марта 2009

Я не знаю, что вы собираетесь найти нейтральное платформой решение.

Это обычно называется "UPSERT".

См. Некоторые связанные обсуждения:

5 голосов
/ 27 марта 2009

В PostgreSQL нет команды слияния, и на самом деле ее написание не тривиально - на самом деле существуют странные крайние случаи, которые делают задачу «интересной».

Лучший (как в случае работы в самых возможных условиях) подход - использовать функцию, например, показанную в manual (merge_db).

Если вы не хотите использовать функцию, вы можете обычно сойти с рук:

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

Просто помните, что это не является доказательством неисправности и в конечном итоге выйдет из строя.

4 голосов
/ 27 марта 2009

Стандартный SQL предоставляет инструкцию MERGE для этой задачи. Не все СУБД поддерживают оператор MERGE.

0 голосов
/ 05 ноября 2013

Если вы согласны с использованием библиотеки, которая пишет для вас 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.

0 голосов
/ 27 марта 2009

Не могли бы вы использовать триггер вставки? Если это не удается, сделайте обновление.

0 голосов
/ 27 марта 2009

Если у вас нет общего способа атомарного обновления или вставки (например, с помощью транзакции), вы можете использовать другую схему блокировки. 0-байтовый файл, системный мьютекс, именованный канал и т. Д. *

...