Вставить, при повторном обновлении в PostgreSQL? - PullRequest
587 голосов
/ 10 июля 2009

Несколько месяцев назад я узнал из ответа на Stack Overflow, как выполнять несколько обновлений одновременно в MySQL, используя следующий синтаксис:

INSERT INTO table (id, field, field2) VALUES (1, A, X), (2, B, Y), (3, C, Z)
ON DUPLICATE KEY UPDATE field=VALUES(Col1), field2=VALUES(Col2);

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

Чтобы уточнить, я хочу вставить несколько вещей и, если они уже существуют, обновить их.

Ответы [ 16 ]

5 голосов
/ 03 декабря 2014

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

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
5 голосов
/ 02 сентября 2014

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

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

5 голосов
/ 29 декабря 2012
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
5 голосов
/ 11 июля 2009

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

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

  1. КОПИРОВАНИЕ новых / обновленных данных в временную таблицу (конечно, или вы можете сделать INSERT, если стоимость в порядке)
  2. Получить блокировку [необязательно] (рекомендуется для блокировок таблицы, IMO)
  3. Объединить. (забавная часть)
5 голосов
/ 10 июля 2009

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

4 голосов
/ 02 января 2012

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

Эврика! Я нашел способ сделать это в одном запросе: используйте 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');
...