SQLite INSERT - ПРИ ДВОЙНОМ ОБНОВЛЕНИИ КЛЮЧА (UPSERT)

98

В MySQL есть что-то вроде этого:

INSERT INTO visits (ip, hits)
VALUES ('127.0.0.1', 1)
ON DUPLICATE KEY UPDATE hits = hits + 1;

Насколько я знаю, этой функции в SQLite нет, я хочу знать, есть ли способ добиться того же эффекта без выполнения двух запросов. Также, если это невозможно, что вы предпочтете:

  1. ВЫБРАТЬ + (ВСТАВИТЬ или ОБНОВИТЬ) или
  2. ОБНОВЛЕНИЕ (+ ВСТАВИТЬ, если ОБНОВЛЕНИЕ не удалось )
Аликс Аксель
источник

Ответы:

31

Начиная с версии 3.24.0 SQLite также поддерживает upsert , так что теперь вы можете просто написать следующее

INSERT INTO visits (ip, hits)
VALUES ('127.0.0.1', 1)
ON CONFLICT(ip) DO UPDATE SET hits = hits + 1;
szmate1618
источник
3
Мне было интересно, можете ли вы сделать несколько upsertподобных операций в одной транзакции, то есть с помощью executemany()функции Python ?
Radio Controlled
117
INSERT OR IGNORE INTO visits VALUES ($ip, 0);
UPDATE visits SET hits = hits + 1 WHERE ip LIKE $ip;

Это требует, чтобы столбец «ip» имел ограничение UNIQUE (или PRIMARY KEY).


РЕДАКТИРОВАТЬ: еще одно отличное решение: https://stackoverflow.com/a/4330694/89771 .

dan04
источник
2
Для протокола, REPLACEэто не вариант.
Аликс Аксель
1
Что касается ссылки «еще одно отличное решение», я бы также рассмотрел другой ответ на тот же вопрос: stackoverflow.com/a/418988/3650835
KayakinKoder 09
19

Я бы предпочел UPDATE (+ INSERT if UPDATE fails). Меньше кода = меньше ошибок.

кодеголик
источник
1
Спасибо! @Sam ( stackoverflow.com/questions/418898/… ), похоже, с вами согласен. Я тоже предпочитаю этот подход.
Аликс Аксель
@Smith Я имел в виду использование простых операторов UPDATE и INSERT и проверку возвращаемого значения.
codeholic
Это не имеет атомарности, возможно, INSERT завершится ошибкой, если какой-то другой процесс будет вставлен между ними.
Робин Лавалле,
7

Текущий ответ будет работать только в sqlite OR mysql (в зависимости от того, используете ли вы OR или нет). Итак, если вам нужна кросс-совместимость с dbms, подойдет следующее ...

REPLACE INTO `visits` (ip, value) VALUES ($ip, 0);
Джейкоб Томасон
источник
3
Принятый ответ работает на SQLite (это была моя цель). REPLACEбудет работать и на SQLite, но в MySQL он всегда будет сбрасывать счетчик на 0 - хотя запрос будет переносимым, конечный результат будет сильно отличаться.
Аликс Аксель
Вы правы, я думал, ОП искал что-то портативное. Я понимаю, что REPLACE INTO не будет работать во всех случаях, особенно когда требуется сохранение PK, но будет работать во многих случаях.
Джейкоб Томасон
Чистый отказ вместо сброса данных - это особенность, а не ошибка.
Тобу
-4

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

Он быстрее MySQL и экономит нагрузку, поэтому MySQL может сосредоточиться на других вещах.

Xeoncross
источник
Если данные не так важны, да. Однако, если это используется на загруженном сайте, где многие IP-адреса обращаются к службе, экземпляры memcached могут заполниться и привести к удалению некоторого содержимого. Резервное копирование содержимого memcached также было бы интересно (при необходимости)
Эллиот Фостер,
@ElliotFoster Memcached может обрабатывать столько данных, сколько ОЗУ, которое вы на него набираете (если вы хотите упорства, используйте redis или мембазу). Если вы получаете более 1 миллиона посетителей в день, вы, вероятно, можете позволить себе предоставить своему экземпляру memcache более 30 МБ оперативной памяти (я думаю, это значение по умолчанию). Однако он определенно может справиться с гораздо более высокой нагрузкой, чем SQLite и MySQL, с учетом объема памяти, который вы ему предоставляете - просто нет никакого сравнения.
Xeoncross
Пожалуйста, не принимайте мой комментарий как голосование против memcache, поскольку я считаю, что это фантастический инструмент. Как и redis (я не могу говорить о мембазе, поскольку я ее не использовал). Однако memcache / redis - не самые надежные хранилища. Да, у Redis есть постоянство, но данные сохраняются на диске в течение определенного интервала (в последний раз я смотрел), а в memcache нет вообще. Как я уже сказал, если данные не важны (или могут быть легко воспроизведены), тогда memcache и компания отлично подходят. В исходном сообщении также спрашивалось о sqlite, который сильно отличается от MySQL и, вероятно, означает, что они ограничены другими способами.
Эллиот Фостер,
Вы можете настроить Redis на сохранение данных так быстро, как вы хотите (за X секунд или Y изменений).
Buffalo