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

Como definir o valor do campo de variável composta usando SQL dinâmico

Mais rápido com hstore


Desde o Postgres 9.0 , com o módulo adicional hstore instalado em seu banco de dados existe uma solução muito simples e rápida com o #= operador que...

substituir[s] campos em record com valores correspondentes de hstore .

Para instalar o módulo:
CREATE EXTENSION hstore;

Exemplos:
SELECT my_record #= '"field"=>"value"'::hstore;  -- with string literal
SELECT my_record #= hstore(field, value);        -- with values

Os valores devem ser convertidos em text e volta, obviamente.

Exemplo de funções plpgsql com mais detalhes:
  • Loop infinito na função de gatilho
  • Atribuir a NEW por chave em um gatilho Postgres

Agora funciona com json / jsonb , também!


Existem soluções semelhantes com json (página 9.3+) ou jsonb (pág. 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');

A funcionalidade não estava documentada, mas é oficial desde o Postgres 13. O manual:

No entanto, se base não for NULL, os valores que ela contém serão usados ​​para colunas sem correspondência.

Assim, você pode pegar qualquer linha existente e preencher campos arbitrários (sobrescrevendo o que está nela).

Principais vantagens do json vs hstore :
  • funciona com o Postgres padrão para que você não precise de um módulo adicional.
  • também funciona para matrizes aninhadas e tipos compostos.

Desvantagem menor:um pouco mais lento.

Veja a resposta adicionada de @Gei para detalhes.

Sem hstore e json


Se você estiver em uma versão mais antiga ou não puder instalar o módulo adicional hstore ou não pode assumir que está instalado, aqui está uma versão melhorada do que postei anteriormente. Ainda mais lento que o hstore operador, porém:
CREATE OR REPLACE FUNCTION f_setfield(INOUT _comp_val anyelement
                                          , _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
BEGIN

EXECUTE 'SELECT ' || array_to_string(ARRAY(
      SELECT CASE WHEN attname = _field
                THEN '$2'
                ELSE '($1).' || quote_ident(attname)
             END AS fld
      FROM   pg_catalog.pg_attribute
      WHERE  attrelid = pg_typeof(_comp_val)::text::regclass
      AND    attnum > 0
      AND    attisdropped = FALSE
      ORDER  BY attnum
      ), ',')
USING  _comp_val, _val
INTO   _comp_val;

END
$func$;

Ligar:
CREATE TEMP TABLE t( a int, b text);  -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');

Observações


  • Uma conversão explícita do valor _val para o tipo de dados de destino não é necessário, uma string literal na consulta dinâmica seria coagida automaticamente, evitando a subconsulta em pg_type . Mas dei um passo adiante:

  • Substitua quote_literal(_val) com inserção direta de valor via USING cláusula. Salva uma chamada de função e dois lançamentos e é mais seguro de qualquer maneira. text é coagido para o tipo de destino automaticamente no PostgreSQL moderno. (Não teste com versões anteriores a 9.1.)

  • array_to_string(ARRAY()) é mais rápido que string_agg() .

  • Nenhuma variável necessária, sem DECLARE . Menos atribuições.

  • Nenhuma subconsulta no SQL dinâmico. ($1).field é mais rápido.

  • pg_typeof(_comp_val)::text::regclass
    faz o mesmo que
    (SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
    para tipos compostos válidos, apenas mais rápido.
    Esta última modificação é baseada na suposição de que pg_type.typname é sempre idêntico ao pg_class.relname associado para tipos compostos registrados, e a conversão dupla pode substituir a subconsulta. Eu executei este teste em um grande banco de dados para verificar e ele veio vazio como esperado:
    SELECT *
    FROM   pg_catalog.pg_type t
    JOIN   pg_namespace  n ON n.oid = t.typnamespace
    WHERE  t.typrelid > 0  -- exclude non-composite types
    AND    t.typrelid IS DISTINCT FROM
          (quote_ident(n.nspname ) || '.' || quote_ident(typname))::regclass
  • O uso de um INOUT O parâmetro elimina a necessidade de um RETURN explícito . Este é apenas um atalho de notação. Pavel não vai gostar, ele prefere um RETURN explícito declaração...

Tudo junto é duas vezes mais rápido como a versão anterior.

Resposta original (desatualizada):


O resultado é uma versão ~ 2,25 vezes mais rápida . Mas eu provavelmente não poderia ter feito isso sem construir a segunda versão de Pavel.

Além disso, esta versão evita a maior parte do casting para texto e vice-versa fazendo tudo dentro de uma única consulta, por isso deve ser muito menos propenso a erros.
Testado com PostgreSQL 9.0 e 9.1 .
CREATE FUNCTION f_setfield(_comp_val anyelement, _field text, _val text)
  RETURNS anyelement
  LANGUAGE plpgsql STABLE AS
$func$
DECLARE
   _list text;
BEGIN
_list := (
   SELECT string_agg(x.fld, ',')
   FROM  (
      SELECT CASE WHEN a.attname = $2
              THEN quote_literal($3) || '::'|| (SELECT quote_ident(typname)
                                                FROM   pg_catalog.pg_type
                                                WHERE  oid = a.atttypid)
              ELSE quote_ident(a.attname)
             END AS fld
      FROM   pg_catalog.pg_attribute a 
      WHERE  a.attrelid = (SELECT typrelid
                           FROM   pg_catalog.pg_type
                           WHERE  oid = pg_typeof($1)::oid)
      AND    a.attnum > 0
      AND    a.attisdropped = false
      ORDER  BY a.attnum
      ) x
   );

EXECUTE 'SELECT ' || _list || ' FROM  (SELECT $1.*) x'
USING  $1
INTO   $1;

RETURN $1;
END
$func$;