Как мне обновить строку в таблице или вставить ее, если она не существует?

71

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

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

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

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

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

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

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

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

EDIT: Спасибо за все замечательные ответы. Похоже, что Пол прав, и нет ни одного, портативного способа сделать это. Это удивительно для меня, поскольку это звучит как очень простая операция.

Показать ещё 2 комментария
Теги:
upsert

10 ответов

130
Лучший ответ

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

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

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

  • 4
    > (и впоследствии SQLite) ... вы полностью украли это из моего ответа :-)
  • 3
    Извините - надеюсь, это не считается грубым?
Показать ещё 5 комментариев
27

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

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

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

REPLACE INTO [...blah...]

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

  • 0
    Это требует, чтобы вы определили PRAMARY KEY в ваших значениях.
15

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

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

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

  • 1
    Я использовал это решение в приложениях php / mysql, оно работает очень хорошо.
  • 1
    Этот ответ должен честно быть выбранным ответом. Это неразрушающее редактирование, если вы не отправляете все поля в запись.
8

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

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

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

  • 0
    Это решение действительно применимо? и портативный?
7

предложение 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)
;
  • 4
    REPLACE является атомарным, поэтому нет периода, когда строка не существует.
5

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

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

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

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

Просто помните, что это не ошибка, и она будет терпеть неудачу.

4

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

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.

0

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

  • 0
    Триггер (по крайней мере, в PostgreSQL) работает, когда команда работает. то есть у вас не может быть триггера, который запускается при сбое базовой команды.
  • 0
    Hunh. Не знал этого. Благодарю.
0

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню