Обновление и новые значения в PostgreSQL [дубликат]

Более гибкое решение для создания элементов и связывания событий ( source )

// creating a dynamic element (container div)
var $div = $("
", {id: 'myid1', class: 'myclass'}); //creating a dynamic button var $btn = $("

Примечание. Это создаст экземпляр обработчика события для каждого элемента (может повлиять на производительность при использовании в петлях)

522
задан a_horse_with_no_name 3 March 2017 в 19:13
поделиться

13 ответов

CREATE OR REPLACE FUNCTION save_user(_id integer, _name character varying)
  RETURNS boolean AS
$BODY$
BEGIN
    UPDATE users SET name = _name WHERE id = _id;
    IF FOUND THEN
        RETURN true;
    END IF;
    BEGIN
        INSERT INTO users (id, name) VALUES (_id, _name);
    EXCEPTION WHEN OTHERS THEN
            UPDATE users SET name = _name WHERE id = _id;
        END;
    RETURN TRUE;
END;

$BODY$
  LANGUAGE plpgsql VOLATILE STRICT
4
ответ дан Ahmad 19 August 2018 в 08:32
поделиться

Подобно большинству ответов, но работает немного быстрее:

WITH upsert AS (UPDATE spider_count SET tally=1 WHERE date='today' RETURNING *)
INSERT INTO spider_count (spider, tally) SELECT 'Googlebot', 1 WHERE NOT EXISTS (SELECT * FROM upsert)

(источник: http://www.the-art-of-web.com/sql/upsert / )

5
ответ дан alexkovelsky 19 August 2018 в 08:32
поделиться
  • 1
    Это приведет к сбою при одновременном запуске в течение двух сеансов, потому что ни одно обновление не увидит существующую строку, поэтому оба обновления попадут в нулевые строки, поэтому оба запроса выдадут вставку. – Craig Ringer 8 May 2015 в 07:55

Лично я установил «правило», прикрепленное к инструкции insert. Скажем, у вас была таблица «dns», в которой каждый раз записывались удары DNS для каждого клиента:

CREATE TABLE dns (
    "time" timestamp without time zone NOT NULL,
    customer_id integer NOT NULL,
    hits integer
);

Вы хотели иметь возможность повторно вставлять строки с обновленными значениями или создавать их, если они не было уже. Ключ к customer_id и времени. Что-то вроде этого:

CREATE RULE replace_dns AS 
    ON INSERT TO dns 
    WHERE (EXISTS (SELECT 1 FROM dns WHERE ((dns."time" = new."time") 
            AND (dns.customer_id = new.customer_id)))) 
    DO INSTEAD UPDATE dns 
        SET hits = new.hits 
        WHERE ((dns."time" = new."time") AND (dns.customer_id = new.customer_id));

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

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

Кроме того, команда COPY не использует ПРАВИЛА, поэтому, если вы вставляете с COPY, вам нужно будет использовать триггеры.

8
ответ дан Ch'marr 19 August 2018 в 08:32
поделиться

Согласно документации PostgreSQL к оператору INSERT , обработка случая ON DUPLICATE KEY не поддерживается. Эта часть синтаксиса является проприетарным расширением MySQL.

3
ответ дан Christian Hang-Hicks 19 August 2018 в 08:32
поделиться
  • 1
    @Lucian MERGE также действительно больше работает с OLAP; см. stackoverflow.com/q/17267417/398670 для объяснения. Он не определяет семантику параллелизма, и большинство людей, которые используют его для upsert, просто создают ошибки. – Craig Ringer 8 May 2015 в 07:56

В PostgreSQL 9.5 и новее вы можете использовать INSERT ... ON CONFLICT UPDATE.

См. документацию .

MySQL INSERT ... ON DUPLICATE KEY UPDATE можно прямо перефразировать до a ON CONFLICT UPDATE. Синтаксис SQL-стандарта не является, они оба расширения для конкретной базы данных. Есть веские причины MERGE не использовались для этого , новый синтаксис не был создан только для удовольствия. (Синтаксис MySQL также имеет проблемы, которые означают, что он не был принят напрямую).

, например. заданная настройка:

CREATE TABLE tablename (a integer primary key, b integer, c integer);
INSERT INTO tablename (a, b, c) values (1, 2, 3);

запрос MySQL:

INSERT INTO tablename (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

становится:

INSERT INTO tablename (a, b, c) values (1, 2, 10)
ON CONFLICT (a) DO UPDATE SET c = tablename.c + 1;

Различия:

  • Вы должны указать имя столбца (или уникальное имя ограничения), которое будет использоваться для проверки уникальности. Это ON CONFLICT (columnname) DO
  • Необходимо использовать ключевое слово SET, как если бы это был обычный UPDATE оператор

Он также имеет некоторые интересные функции:

  • У вас может быть предложение WHERE на вашем UPDATE (позволяющее эффективно повернуть ON CONFLICT UPDATE в ON CONFLICT IGNORE для определенных значений)
  • значения вставки доступны как переменная строки EXCLUDED, которая имеет ту же структуру, что и целевая таблица. Вы можете получить исходные значения в таблице, используя имя таблицы. Поэтому в этом случае EXCLUDED.c будет 10 (потому что мы пытались вставить), а "table".c будет 3, потому что это текущее значение в таблице. Вы можете использовать один или оба в выражениях SET и WHERE.

Для фона на upsert см. Как выполнить UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE ) в PostgreSQL?

98
ответ дан Community 19 August 2018 в 08:32
поделиться
  • 1
    Короткий ответ: если запись существует, INSERT ничего не делает. Длинный ответ: SELECT в INSERT вернет столько результатов, сколько совпадений предложения where. Это не более одного (если номер один не находится в результате подвыборки), в противном случае - ноль. Таким образом, INSERT добавит либо одну, либо нулевую строку. – Peter Becker 15 October 2011 в 11:34
  • 2
    – Endy Tjahjono 21 October 2011 в 07:46
  • 3
    @ FrançoisBeausoleil: вероятность состояния гонки намного меньше, чем при использовании «try / handle exception». подход – a_horse_with_no_name 21 February 2012 в 17:32
  • 4
    это должен быть правильный ответ .. с некоторыми незначительными трюками, он может быть использован для массового обновления. Humm .. Интересно, можно ли использовать таблицу temp. – baash05 1 March 2012 в 02:03
  • 5
    Единственное, что мне не нравится в этом, это то, что это будет намного медленнее, потому что каждый взлет будет его собственным индивидуальным вызовом в базу данных. – baash05 1 March 2012 в 02:27
  • 6
    @ baash05 может быть способ сделать это навалом, см. мой обновленный ответ. – Stephen Denne 2 March 2012 в 02:02
  • 7
    @a_horse_with_no_name Как вы точно имеете в виду, что шансы на условия гонки намного меньше? Когда я выполняю этот запрос одновременно с одними и теми же записями, я получаю ошибку «дублирующее ключевое значение», нарушает уникальное ограничение & quot; 100% времени, пока запрос не обнаружит, что запись была вставлена. Это полный пример? – Jeroen van Dijk 26 March 2012 в 10:39
  • 8
    @a_horse_with_no_name Ваше решение работает в параллельных ситуациях, когда вы завершаете оператор upsert со следующей блокировкой: BEGIN WORK; LOCK TABLE mytable IN SHARE ROW ЭКСКЛЮЗИВНЫЙ РЕЖИМ; & lt; UPSERT ЗДЕСЬ & gt ;; КОМИТЕТНАЯ РАБОТА; – Jeroen van Dijk 26 March 2012 в 12:15
  • 9
    @JeroenvanDijk: спасибо. То, что я имел в виду с "намного меньше" заключается в том, что если несколько транзакций к этому (и зафиксировать изменение!), временной интервал между обновлением и вставкой меньше, поскольку все это всего лишь один оператор. Вы всегда можете генерировать нарушение pk двумя независимыми операторами INSERT. Если вы заблокируете всю таблицу, вы фактически сериализуете весь доступ к ней (что-то, что вы могли бы достичь и с сериализуемым уровнем изоляции). – a_horse_with_no_name 26 March 2012 в 12:28
  • 10
    это вдохновило рубиновую библиотеку: github.com/seamusabshere/upsert – Seamus Abshere 7 July 2012 в 02:03
  • 11
    @keaplogik, ограничение 9.1 - это записываемый CTE (общие табличные выражения), который описан в другом из ответов. Синтаксис, используемый в этом ответе, очень прост и долго поддерживается. – bovine 6 March 2013 в 21:58
  • 12
    – Craig Ringer 24 June 2013 в 04:47
  • 13
    Единственное, что я сделал бы по-другому, это использовать FOR 1..2 LOOP вместо LOOP, чтобы, если какое-то другое уникальное ограничение нарушено, оно не будет вращаться бесконечно. – olamork 6 December 2013 в 00:32
  • 14
    Предупреждение. Это связано с потерями обновлений в изоляции read committed, если ваше приложение не проверяет, чтобы insert или update имели ненулевой ряд строк. См. dba.stackexchange.com/q/78510/7788 – Craig Ringer 7 October 2014 в 08:16
  • 15
    Эта функция входит в 9.5 - wiki.postgresql.org/wiki/UPSERT – Yurik 13 October 2015 в 14:11
  • 16
    Я рассмотрел решение 9.5 PostgreSQL, как вы описали выше, потому что я испытывал пробелы в поле автоматического приращения, а в MySQL ON DUPLICATE KEY UPDATE. Я загрузил Postgres 9.5 и реализовал ваш код, но, как ни странно, такая же проблема возникает в Postgres: серийное поле первичного ключа не является последовательным (есть промежутки между вставками и обновлениями). Любая идея, что здесь происходит? Это нормально? Любая идея, как избежать такого поведения? Спасибо. – W.M. 7 August 2016 в 10:47
  • 17
    @ W.M. Это в значительной степени присуще операции upsert. Вы должны оценить функцию, которая генерирует последовательность перед попыткой вставки. Так как такие последовательности предназначены для одновременной работы, они освобождаются от обычной семантики транзакций, но даже если они не являются генерацией, она не вызывается в субтранзакции и откатывается, она завершается нормально и завершается с остальной частью операции. Таким образом, это могло бы произойти даже с "бесщелевым" последовательности. Единственный способ, которым БД мог бы избежать этого, - это отложить оценку генерации последовательности до тех пор, пока не будет проверена проверка ключа. – Craig Ringer 8 August 2016 в 01:15
  • 18
    @ W.M. что создало бы свои проблемы. В принципе, ты застрял. Но если вы полагаетесь на serial / auto_increment, будучи безразличным, у вас уже есть ошибки. У вас могут быть пробелы последовательности из-за откатов, включая переходные ошибки - перезагрузка под нагрузкой, ошибки в транзакции клиента, сбои и т. Д. Вы никогда не должны полагаться на SERIAL / SEQUENCE или AUTO_INCREMENT, которые не имеют пробелов. Если вам нужны бесщеточные последовательности, они сложнее; вам обычно нужно использовать таблицу счетчиков. Google расскажет вам больше. Но имейте в виду, что безшовные последовательности предотвращают параллелизм вставки. – Craig Ringer 8 August 2016 в 01:17
  • 19
    @ W.M. Если вам абсолютно необходимы бесконечные последовательности и upsert, вы можете использовать подход, основанный на функциях, описанный в руководстве, а также реализацию безщелезной последовательности, которая использует таблицу счетчиков. Поскольку BEGIN ... EXCEPTION ... работает в подтранзакционном режиме, который возвращается на ошибку, приращение вашей последовательности будет откат, если INSERT не удалось. – Craig Ringer 8 August 2016 в 01:18
  • 20
    Большое спасибо @Craig Ringer, это было довольно информативно. Я понял, что могу просто отказаться от использования этого первичного ключа с автоматическим добавлением. Я сделал составной первичный из 3-х полей и для моей конкретной текущей потребности, действительно нет необходимости в бесщелевом поле автоматического приращения. Еще раз спасибо, информация, которую вы предоставили, спасет меня в будущем, пытаясь предотвратить естественное и здоровое поведение БД. Теперь я понимаю это лучше. – W.M. 8 August 2016 в 15:58

Нет простой команды для этого.

Самый правильный подход - использовать функцию, такую ​​как функция из docs .

Другое решение (хотя это и не так безопасно) заключается в том, чтобы выполнить обновление с возвратом, проверить, какие строки были обновлениями, и вставить остальные из них

Что-то по строкам:

update table
set column = x.column
from (values (1,'aa'),(2,'bb'),(3,'cc')) as x (id, column)
where table.id = x.id
returning id;

: 2 было возвращено:

insert into table (id, column) values (1, 'aa'), (3, 'cc');

Конечно, он рано или поздно выйдет из строя (в параллельной среде), так как здесь есть явное состояние гонки, но обычно оно будет работать.

Вот еще более длинная и более полная статья по теме .

12
ответ дан Craig Ringer 19 August 2018 в 08:32
поделиться
  • 1
    Если вы используете этот параметр, убедитесь, что он возвращается, даже если обновление ничего не делает. Я видел запросы на оптимизацию базы данных, такие как «Обновить таблицу foo set bar = 4, где bar = 4». – thelem 20 January 2012 в 16:58

У меня такая же проблема для управления настройками учетной записи, как и пары значений имени. Критерии проектирования - это то, что разные клиенты могут иметь разные наборы настроек.

Мое решение, подобно JWP, состоит в том, чтобы объединить стирание и замену, создавая запись слияния в вашем приложении.

Это довольно пуленепробиваемая, независимая от платформы и поскольку на клиента не более 20 настроек, это всего лишь 3 довольно низкого уровня загрузки db-вызовов - возможно, самый быстрый метод.

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

 #This is pseudo-code - within the application:
 BEGIN TRANSACTION - get transaction lock
 SELECT all current name value pairs where id = $id into a hash record
 create a merge record from the current and update record
  (set intersection where shared keys in new win, and empty values in new are deleted).
 DELETE all name value pairs where id = $id
 COPY/INSERT merged records 
 END TRANSACTION
5
ответ дан Dave Jarvis 19 August 2018 в 08:32
поделиться
  • 1
    Добро пожаловать в SO. Хорошее введение! :-) – Don Question 9 November 2012 в 01:36
  • 2
    Это больше похоже на REPLACE INTO, чем INSERT INTO ... ON DUPLICATE KEY UPDATE, что может вызвать проблему, если вы используете триггеры. В конечном итоге вы будете запускать удаление и вставлять триггеры / правила, а не обновлять их. – cHao 15 May 2014 в 17:48

Я настраиваю функцию «upsert» выше, если вы хотите ВСТАВИТЬ И ЗАМЕНИТЬ:

`

 CREATE OR REPLACE FUNCTION upsert(sql_insert text, sql_update text)

 RETURNS void AS
 $BODY$
 BEGIN
    -- first try to insert and after to update. Note : insert has pk and update not...

    EXECUTE sql_insert;
    RETURN;
    EXCEPTION WHEN unique_violation THEN
    EXECUTE sql_update; 
    IF FOUND THEN 
        RETURN; 
    END IF;
 END;
 $BODY$
 LANGUAGE plpgsql VOLATILE
 COST 100;
 ALTER FUNCTION upsert(text, text)
 OWNER TO postgres;`

И после выполнения сделайте что-то вроде этого:

SELECT upsert($$INSERT INTO ...$$,$$UPDATE... $$)

Важно поместить двойную долларовую запятую, чтобы избежать ошибок компилятора

  • проверить скорость ...
7
ответ дан Felipe FMMobile 19 August 2018 в 08:32
поделиться

Для слияния небольших множеств использование вышеуказанной функции в порядке. Однако, если вы объединяете большие объемы данных, я бы предложил посмотреть в http://mbk.projects.postgresql.org

Текущая лучшая практика, которую я известно о:

  1. КОПИРОВАТЬ новые / обновленные данные в таблицу temp (обязательно, или вы можете сделать INSERT, если стоимость в порядке)
  2. Блокировка захвата [опционально] (консультативный предпочтительнее блокировки таблиц, IMO)
  3. Объединить. (забавная часть)
3
ответ дан gd1 19 August 2018 в 08:32
поделиться

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

Это может быть не так элегантно, но у вас гораздо более простой SQL, который более тривиально использовать из вызывающего кода. Иными словами, если вы пишете десять строк сценария в PL / PSQL, вы, вероятно, должны иметь единичный тест того или иного вида только для него.

4
ответ дан h22 19 August 2018 в 08:32
поделиться

Изменить: это не работает должным образом. В отличие от принятого ответа, это приводит к уникальным нарушениям ключа, когда два процесса неоднократно называют upsert_foo одновременно.

Eureka! Я понял способ сделать это в одном запросе: используйте UPDATE ... RETURNING, чтобы проверить, были ли затронуты какие-либо строки:

CREATE TABLE foo (k INT PRIMARY KEY, v TEXT);

CREATE FUNCTION update_foo(k INT, v TEXT)
RETURNS SETOF INT AS $$
    UPDATE foo SET v = $2 WHERE k = $1 RETURNING $1
$$ LANGUAGE sql;

CREATE FUNCTION upsert_foo(k INT, v TEXT)
RETURNS VOID AS $$
    INSERT INTO foo
        SELECT $1, $2
        WHERE NOT EXISTS (SELECT update_foo($1, $2))
$$ LANGUAGE sql;

UPDATE необходимо выполнить в отдельной процедуре, потому что, к сожалению, это синтаксическая ошибка:

... WHERE NOT EXISTS (UPDATE ...)

Теперь она работает по желанию:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
3
ответ дан Joey Adams 19 August 2018 в 08:32
поделиться
  • 1
    Вы можете объединить их в один оператор, если используете записываемый CTE. Но, как и большинство решений, размещенных здесь, этот неверен и будет терпеть неудачу при одновременном обновлении. – Craig Ringer 8 May 2015 в 07:54

Я использую эту функцию merge

CREATE OR REPLACE FUNCTION merge_tabla(key INT, data TEXT)
  RETURNS void AS
$BODY$
BEGIN
    IF EXISTS(SELECT a FROM tabla WHERE a = key)
        THEN
            UPDATE tabla SET b = data WHERE a = key;
        RETURN;
    ELSE
        INSERT INTO tabla(a,b) VALUES (key, data);
        RETURN;
    END IF;
END;
$BODY$
LANGUAGE plpgsql
4
ответ дан Mise 19 August 2018 в 08:32
поделиться
  • 1
    Лучше всего сначала выполнить update, а затем проверить количество обновленных строк. (См. Ответ Ахмада) – a_horse_with_no_name 9 January 2015 в 11:47

Я искал то же самое, когда я пришел сюда, но отсутствие общей функции «upsert» немного меня беспокоило, поэтому я подумал, что вы можете просто передать обновление и вставить sql в качестве аргументов в эту функцию из руководства

, который будет выглядеть так:

CREATE FUNCTION upsert (sql_update TEXT, sql_insert TEXT)
    RETURNS VOID
    LANGUAGE plpgsql
AS $$
BEGIN
    LOOP
        -- first try to update
        EXECUTE sql_update;
        -- check if the row is found
        IF FOUND THEN
            RETURN;
        END IF;
        -- not found so insert the row
        BEGIN
            EXECUTE sql_insert;
            RETURN;
            EXCEPTION WHEN unique_violation THEN
                -- do nothing and loop
        END;
    END LOOP;
END;
$$;

и, возможно, делать то, что вы изначально хотели сделать, пакетный «upsert», вы могли бы использовать Tcl для разделения sql_update и петли отдельного обновления, предел производительности будет очень небольшим, см. http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php

самая высокая стоимость выполняется запрос из вашего кода, на стороне базы данных стоимость выполнения намного меньше

16
ответ дан Paul Scheltema 19 August 2018 в 08:32
поделиться
  • 1
    Вам все равно придется запускать это в цикле повтора, и он склонен к расам с одновременным DELETE, если вы не заблокируете таблицу или не изолируете транзакцию SERIALIZABLE от PostgreSQL 9.1 или выше. – Craig Ringer 22 May 2013 в 11:00
Другие вопросы по тегам:

Похожие вопросы: