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 emrecord
com valores correspondentes dehstore
.
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 empg_type
. Mas dei um passo adiante:
-
Substituaquote_literal(_val)
com inserção direta de valor viaUSING
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 questring_agg()
.
-
Nenhuma variável necessária, semDECLARE
. 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 quepg_type.typname
é sempre idêntico aopg_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 umRETURN
explícito . Este é apenas um atalho de notação. Pavel não vai gostar, ele prefere umRETURN
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$;