PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Retornar valores de coluna pré-UPDATE usando apenas SQL

Problema


O manual explica:

O opcional RETURNING cláusula faz com que UPDATE para calcular e retornar valor(es) com base em cada linha realmente atualizada. Qualquer expressão usando as colunas da tabela e/ou colunas de outras tabelas mencionadas em FROM , pode ser calculado. Os novos valores (pós-atualização) das colunas da tabela são usados . A sintaxe do RETURNING lista é idêntica à lista de saída de SELECT .

Minha ênfase em negrito. Não há como acessar a linha antiga em um RETURNING cláusula. Você pode contornar essa restrição com um gatilho ou um SELECT separado antes a UPDATE envolto em uma transação ou envolto em um CTE como foi comentado.

No entanto, o que você está tentando alcançar funciona perfeitamente se você se juntar a outra instância da tabela no FROM cláusula:

Solução sem gravações simultâneas

UPDATE tbl x
SET    tbl_id = 23
     , name = 'New Guy'
FROM   tbl y                -- using the FROM clause
WHERE  x.tbl_id = y.tbl_id  -- must be UNIQUE NOT NULL
AND    x.tbl_id = 3
RETURNING y.tbl_id AS old_id, y.name AS old_name
        , x.tbl_id          , x.name;

Devoluções:
 old_id | old_name | tbl_id |  name
--------+----------+--------+---------
  3     | Old Guy  | 23     | New Guy

A(s) coluna(s) usada(s) para auto-junção deve(m) ser UNIQUE NOT NULL . No exemplo simples, o WHERE condição está na mesma coluna tbl_id , mas isso é apenas coincidência. Funciona para qualquer condições.

Eu testei isso com as versões do PostgreSQL de 8.4 a 13.

É diferente para INSERT :
  • INSERT INTO ... FROM SELECT ... RETURNING id mapeamentos

Soluções com carga de gravação simultânea


Existem várias maneiras de evitar condições de corrida com operações de gravação simultâneas nas mesmas linhas. (Observe que operações de gravação simultâneas em linhas não relacionadas não são problema algum.) O método simples, lento e seguro (mas caro) é executar a transação com SERIALIZABLE nível de isolamento:
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ... ;
COMMIT;

Mas isso é provavelmente um exagero. E você precisa estar preparado para repetir a operação em caso de falha de serialização.

Mais simples e rápido (e tão confiável com carga de gravação simultânea) é um bloqueio explícito no um linha a ser atualizada:
UPDATE tbl x
SET    tbl_id = 24
     , name = 'New Gal'
FROM  (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y 
WHERE  x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name
        , x.tbl_id          , x.name;

Observe como o WHERE condição movida para a subconsulta (novamente, pode ser qualquer coisa ), e apenas a auto-junção (em UNIQUE NOT NULL column(s)) permanece na consulta externa. Isso garante que apenas as linhas bloqueadas pelo SELECT interno são processados. O WHERE condições podem resolver para um conjunto diferente de linhas um momento depois.

Ver:
  • Atomic UPDATE .. SELECT in Postgres

db<>mexa aqui
antigo sqlfiddle