Pergunta

Alguns meses atrás eu aprendi com uma resposta sobre estouro de pilha como executar várias atualizações ao mesmo tempo no MySQL usando a seguinte sintaxe:

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);

Eu já mudei para PostgreSQL e, aparentemente, isso não é correto. Ele está se referindo a todas as tabelas corretas assim que eu supor que é uma questão de diferentes palavras-chave que está sendo usado, mas eu não tenho certeza de onde na documentação do PostgreSQL este é coberto.

Para esclarecer, eu quero inserir várias coisas e se eles já existem para atualizá-los.

Foi útil?

Solução

PostgreSQL desde a versão 9.5 tem UPSERT sintaxe, com < strong> ON CONFLICT cláusula. com a seguinte sintaxe (semelhante ao MySQL)

INSERT INTO the_table (id, column_1, column_2) 
VALUES (1, 'A', 'X'), (2, 'B', 'Y'), (3, 'C', 'Z')
ON CONFLICT (id) DO UPDATE 
  SET column_1 = excluded.column_1, 
      column_2 = excluded.column_2;

Como pesquisar arquivos de grupo de e-mail do PostgreSQL para obter "Upsert" leva à descoberta de um exemplo de fazer o que você talvez queira fazer, no manual:

Exemplo 38-2. Exceções com UPDATE / INSERIR

Este exemplo utiliza excepção de manuseamento para executar qualquer UPDATE ou INSERT, conforme apropriado:

CREATE TABLE db (a INT PRIMARY KEY, b TEXT);

CREATE FUNCTION merge_db(key INT, data TEXT) RETURNS VOID AS
$$
BEGIN
    LOOP
        -- first try to update the key
        -- note that "a" must be unique
        UPDATE db SET b = data WHERE a = key;
        IF found THEN
            RETURN;
        END IF;
        -- not there, so try to insert the key
        -- if someone else inserts the same key concurrently,
        -- we could get a unique-key failure
        BEGIN
            INSERT INTO db(a,b) VALUES (key, data);
            RETURN;
        EXCEPTION WHEN unique_violation THEN
            -- do nothing, and loop to try the UPDATE again
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql;

SELECT merge_db(1, 'david');
SELECT merge_db(1, 'dennis');

Há possivelmente um exemplo de como fazer isso em massa, usando CTEs em 9.1 e acima, na hackers mailing List :

WITH foos AS (SELECT (UNNEST(%foo[])).*)
updated as (UPDATE foo SET foo.a = foos.a ... RETURNING foo.id)
INSERT INTO foo SELECT foos.* FROM foos LEFT JOIN updated USING(id)
WHERE updated.id IS NULL;

de a_horse_with_no_name resposta para um exemplo mais claro.

Outras dicas

Aviso: este não é seguro se executado a partir de várias sessões ao mesmo tempo (veja advertências abaixo)

.

Outra forma inteligente de fazer uma "UPSERT" no PostgreSQL é fazer duas instruções UPDATE / INSERT seqüenciais que são cada um projetado para ter sucesso ou não têm nenhum efeito.

UPDATE table SET field='C', field2='Z' WHERE id=3;
INSERT INTO table (id, field, field2)
       SELECT 3, 'C', 'Z'
       WHERE NOT EXISTS (SELECT 1 FROM table WHERE id=3);

A atualização será bem sucedida se uma linha com "id = 3" já existe, caso contrário ele não tem nenhum efeito.

A INSERÇÃO só terá sucesso se fila com "id = 3" ainda não existir.

Você pode combinar estes dois em uma única string e executá-los ambos com uma única instrução SQL executar a partir da aplicação. Executá-los juntos em uma única transação é altamente recomendado.

Isso funciona muito bem quando executado isoladamente ou em uma tabela bloqueada, mas está sujeito a condições de corrida que significa que ainda pode falhar com erro de chave duplicada, se uma linha é inserida simultaneamente, ou pode terminar sem linha inserida quando uma linha é eliminado em simultâneo. Uma transação SERIALIZABLE no PostgreSQL 9.1 ou superior vai lidar com isso de forma confiável com o custo de uma elevada taxa de falha de serialização, o que significa que você terá que repetir muito. Consulte porque é upsert tão complicado , que discute neste caso com mais detalhes.

Esta abordagem é também sujeita a atualizações perdidas em isolamento read committed a menos que a aplicação verifica a contagem de linhas afetadas e verifica que tanto o insert ou o update afetou uma linha .

Com PostgreSQL 9.1 isto pode ser conseguido usando um gravável CTE ( mesa comum expressão ) :

WITH new_values (id, field1, field2) as (
  values 
     (1, 'A', 'X'),
     (2, 'B', 'Y'),
     (3, 'C', 'Z')

),
upsert as
( 
    update mytable m 
        set field1 = nv.field1,
            field2 = nv.field2
    FROM new_values nv
    WHERE m.id = nv.id
    RETURNING m.*
)
INSERT INTO mytable (id, field1, field2)
SELECT id, field1, field2
FROM new_values
WHERE NOT EXISTS (SELECT 1 
                  FROM upsert up 
                  WHERE up.id = new_values.id)

Ver essas entradas no blog:


Note que esta solução faz não evitar uma violação de chave única, mas não é vulnerável a atualizações perdidas.
Veja a acompanhamento por Craig Ringer on dba.stackexchange.com

No PostgreSQL 9.5 e mais recente você pode usar INSERT ... ON CONFLICT UPDATE.

Consulte a documentação .

A MySQL INSERT ... ON DUPLICATE KEY UPDATE pode ser diretamente reformulada a um ON CONFLICT UPDATE. Nem é a sintaxe SQL padrão, ambos são extensões específicas do banco de dados. Há boas razões MERGE não foi utilizado para este , uma nova sintaxe não foi criado apenas por diversão. (Sintaxe do MySQL também tem questões que significa que não foi adotada diretamente).

por exemplo. dada configuração:

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

a consulta MySQL:

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

torna-se:

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

As diferenças:

  • Você deve especificar o nome da coluna (ou nome de restrição exclusiva) a utilizar para a verificação de exclusividade. Essa é a ON CONFLICT (columnname) DO

  • O SET palavra-chave deve ser utilizado, como se isso fosse uma declaração UPDATE normais

Ele tem algumas características interessantes também:

  • Você pode ter uma cláusula WHERE em seu UPDATE (permitindo que você efetivamente transformar ON CONFLICT UPDATE em ON CONFLICT IGNORE para determinados valores)

  • Os valores propostos-para-inserção estão disponíveis como a linha-EXCLUDED variável, que tem a mesma estrutura que a tabela de destino. Você pode obter os valores originais na tabela usando o nome da tabela. Portanto, neste caso EXCLUDED.c será 10 (porque isso é o que nós tentamos inserir) e "table".c será 3 porque esse é o valor atual na tabela. Você pode usar um ou ambos nas expressões SET e cláusula WHERE.

Para o fundo em upsert ver Como UPSERT (MERGE, INSERT ... ON DUPLICATE UPDATE) no PostgreSQL?

Eu estava procurando a mesma coisa quando eu vim aqui, mas a falta de um genérico "upsert" função me botherd um pouco então eu pensei que você poderia simplesmente passar a atualização e inserção de SQL como argumentos de que forma a função do manual

que ficaria assim:

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;
$$;

e, talvez, fazer o que você inicialmente queria fazer, lote "upsert", você poderia usar Tcl para dividir os SQL_UPDATE e loop as atualizações individuais, o hit preformance será muito pequeno ver http://archives.postgresql.org/pgsql-performance/2006-04/msg00557.php

o maior custo é executar a consulta do seu código, no banco de dados do lado do custo de execução é muito menor

Não existe um comando simples de fazê-lo.

A abordagem mais correta é a função de uso, como o de docs .

Outra solução (embora não tão seguro) é fazer atualização com o retorno, verificação de quais linhas foram atualizações, e inserir o resto deles

Algo ao longo das linhas de:

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;

assumindo id: 2 foi devolvido:

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

Claro que vai socorrer mais cedo ou mais tarde (em ambiente concorrente), como não há condição de corrida clara aqui, mas normalmente ele vai trabalhar.

Aqui está um artigo mais longo e mais abrangente sobre o tópico .

Pessoalmente, eu configurar uma "regra" ligado à instrução de inserção. Digamos que você tinha um "dns" mesa que o DNS sucessos gravados por cliente em uma base per-time:

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

Você queria ser capaz de linhas re-inserção com valores atualizados, ou criá-los se eles já não existem. Digitado na customer_id eo tempo. Algo parecido com isto:

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));

Update: Este tem o potencial de falhar se inserções simultâneas estão acontecendo, como ele irá gerar exceções unique_violation. No entanto, a transação não-terminado vai continuar e ter sucesso, e você só precisa repetir a operação interrompida.

No entanto, se há toneladas de inserções acontecendo o tempo todo, você vai querer colocar um bloqueio de tabela em torno das instruções de inserção: bloqueio AÇÃO ROW EXCLUSIVE irá evitar quaisquer operações que possam inserir, excluir ou atualizar linhas em sua tabela de destino. No entanto, as atualizações que não atualizam a chave única são seguros, por isso, se você nenhuma operação vai fazer isso, use os bloqueios de aconselhamento em seu lugar.

Além disso, o comando COPY não usa regras, por isso, se você está inserindo com cópia, você vai precisar usar gatilhos vez.

I personalizado "upsert" função acima, se você deseja inserir e REPLACE:

`

 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;`

E depois de executar, fazer algo como isto:

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

É importante colocar double dollar-vírgula para evitar erros do compilador

  • verificar a velocidade ...

Semelhante a resposta mais popular, mas funciona um pouco mais rápido:

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)

(fonte: http: //www.the-art-of- web.com/sql/upsert/ )

Eu tenho o mesmo problema para gerenciar as configurações da conta, como pares de valores de nome. Os critérios do projeto é que diferentes clientes podem ter configurações diferentes conjuntos.

A minha solução, semelhante a JWP é apagar a granel e substituir, gerando o registro merge dentro de sua aplicação.

Isso é muito à prova de balas, independente de plataforma e uma vez que existem nunca mais do que cerca de 20 configurações por cliente, este é apenas 3 chamadas carga db bastante baixos -. Provavelmente o método mais rápido

A alternativa de atualizar linhas individuais - a verificação de exceções, em seguida, inserindo - ou alguma combinação é o código horroroso, lento e muitas vezes quebra porque (como mencionado acima) não padrão SQL manipulação de exceção mudando de db para db - ou até mesmo uma versão para outra .

 #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

De acordo com a documentação do PostgreSQL da declaração INSERT , manipulação o caso ON DUPLICATE KEY não é suportado. Essa parte da sintaxe é uma extensão do MySQL proprietário.

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

Eu uso essa função 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

Para integrar pequenos conjuntos, usando a função acima é bom. No entanto, se você estiver mesclando grandes quantidades de dados, eu sugiro olhar em http: //mbk.projects.postgresql .org

O actual melhor prática que eu estou ciente de é:

  1. copiar dados novos / atualizados em tabela temporária (certeza, ou você pode fazer inserir se o custo é ok)
  2. adquirir a trava [opcional] (Advisory é preferível bloqueios de tabela, IMO)
  3. Mesclar. (A parte divertida)

Atualização irá retornar o número de linhas modificadas. Se você usar JDBC (Java), você pode então verificar este valor contra 0 e, se há linhas foram afetadas, INSERT fogo em seu lugar. Se você usar alguma outra linguagem de programação, talvez o número de linhas modificadas ainda pode ser obtido, a documentação de verificação.

Esta pode não ser tão elegante, mas você tem SQL muito mais simples que é mais simples de se usar a partir do código de chamada. Diferentemente, se você escrever o script de linha de dez em PL / PSQL, você provavelmente deve ter um teste de unidade de um ou outro tipo apenas para isso sozinho.

Editar: isso não funcionar como esperado. Ao contrário da resposta aceita, isso produz violações de chave única quando dois processos chamar repetidamente upsert_foo simultaneamente.

Eureka! Eu descobri uma maneira de fazê-lo em uma consulta: uso UPDATE ... RETURNING para testar se quaisquer linhas foram afetadas:

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;

O UPDATE tem que ser feito em um procedimento separado, porque, infelizmente, este é um erro de sintaxe:

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

Agora, ele funciona como desejado:

SELECT upsert_foo(1, 'hi');
SELECT upsert_foo(1, 'bye');
SELECT upsert_foo(3, 'hi');
SELECT upsert_foo(3, 'bye');
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top