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

Atualizar várias colunas em uma função de gatilho em plpgsql


Embora a resposta de @Gary esteja tecnicamente correta, ele não menciona que o PostgreSQL faz apoie este formulário:
UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)

Leia o manual em UPDATE mais uma vez.

Ainda é complicado fazer isso com SQL dinâmico. Como você não especificou, estou assumindo um caso simples em que as visualizações consistem nas mesmas colunas que suas tabelas subjacentes.
CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problemas


  • O registro especial NEW não é visível dentro de EXECUTE . Eu passo NEW como um único parâmetro com o USING cláusula de EXECUTE .

  • Conforme discutido, UPDATE com forma de lista precisa de valores individuais . Eu uso uma subseleção para dividir o registro em colunas individuais:
    UPDATE ...
    FROM  (SELECT ($1).*) x
    

    (Parênteses em torno de $1 não são opcionais.) Isso me permite simplesmente usar duas listas de colunas construídas com string_agg() da tabela de catálogo:um com e outro sem qualificação de tabela.

  • Não é possível atribuir um valor de linha como um todo a colunas individuais. O manual:

    De acordo com o padrão, o valor de origem para uma sublista entre parênteses de nomes de colunas de destino pode ser qualquer expressão com valor de linha que produza o número correto de colunas. O PostgreSQL só permite que o valor de origem seja um construtor de linha ou um sub-SELECT .

  • INSERT é implementado de forma mais simples. Supondo que a estrutura da visão e da tabela sejam idênticas, omito a lista de definição de coluna. (Pode ser melhorado, veja abaixo.)

Solução


Fiz várias atualizações na sua abordagem para fazê-la brilhar.

Função de gatilho para UPDATE :
CREATE OR REPLACE FUNCTION f_trg_up()
  RETURNS TRIGGER AS
$func$
DECLARE
   tbl  text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
   cols text;
   vals text;
BEGIN
   SELECT INTO cols, vals
          string_agg(quote_ident(attname), ', ')
         ,string_agg('x.' || quote_ident(attname), ', ')
   FROM   pg_attribute
   WHERE  attrelid = tbl::regclass
   AND    NOT attisdropped   -- no dropped (dead) columns
   AND    attnum > 0;        -- no system columns

   EXECUTE format('
   UPDATE %s t
   SET   (%s) = (%s)
   FROM  (SELECT ($1).*) x
   WHERE  t.id = ($2).id'
   , tbl, cols, vals) -- assuming unique "id" in every table
   USING NEW, OLD;

   RETURN NEW;
END
$func$ LANGUAGE plpgsql;

Função de gatilho para INSERT :
CREATE OR REPLACE FUNCTION f_trg_ins()
  RETURNS TRIGGER AS
$func$
DECLARE
    tbl text := quote_ident(TG_TABLE_SCHEMA) || '.'
             || quote_ident(substring(TG_TABLE_NAME from '(.+)_view$'));
BEGIN
   EXECUTE 'INSERT INTO ' || tbl || ' SELECT ($1).*'
   USING NEW;

   RETURN NEW;  -- don't return NULL unless you know what you're doing
END
$func$ LANGUAGE plpgsql;

Gatilhos:
CREATE TRIGGER trg_instead_up
INSTEAD OF UPDATE ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_up();

CREATE TRIGGER trg_instead_ins
INSTEAD OF INSERT ON a_view
FOR EACH ROW EXECUTE PROCEDURE f_trg_ins();

Fiddle SQL demonstrando INSERT e UPDATE .

Pontos principais


  • Inclua o nome do esquema para tornar a referência de tabela inequívoca. Pode haver várias instâncias do mesmo nome de tabela no mesmo banco de dados em vários esquemas!

  • Consulta pg_attribute em vez de information_schema.columns . Isso é menos portátil, mas muito mais rápido e me permite usar o table-OID.
    • Como verificar se uma tabela existe em um determinado esquema

  • Os nomes das tabelas NÃO são seguros contra SQLi quando tratadas como strings, como na construção de consultas para SQL dinâmico. Escape com quote_ident() ou format() ou com um tipo de identificador de objeto. Isso inclui as variáveis ​​de função de gatilho especial TG_TABLE_SCHEMA e TG_TABLE_NAME !

  • Transmitir para o tipo de identificador de objeto regclass para confirmar que o nome da tabela é válido e obter o OID para a consulta do catálogo.

  • Opcionalmente, use format() para construir a string de consulta dinâmica com segurança.

  • Não há necessidade de SQL dinâmico para a primeira consulta nas tabelas do catálogo. Mais rápido, mais simples.

  • Use RETURN NEW em vez de RETURN NULL nessas funções de gatilho, a menos que você saiba o que está fazendo. (NULL cancelaria o INSERT para a linha atual.)

  • Esta versão simples assume que cada tabela (e visualização) tem uma coluna única chamada id . Uma versão mais sofisticada pode usar a chave primária dinamicamente.

  • A função para UPDATE permite que as colunas da visualização e da tabela estejam em qualquer ordem , desde que o conjunto seja o mesmo. A função para INSERT espera que as colunas da visualização e da tabela estejam em ordem idêntica . Se você deseja permitir uma ordem arbitrária, adicione uma lista de definição de coluna ao INSERT comando, assim como com UPDATE .

  • A versão atualizada também abrange alterações no id coluna usando OLD Além disso.