Más rápido con hstore
Desde Postgres 9.0 , con el módulo adicional hstore
instalado en su base de datos hay una solución muy simple y rápida con el #=
operador que...
reemplazar[s] campos en record
con valores coincidentes de hstore
.
Para instalar el módulo:
CREATE EXTENSION hstore;
Ejemplos:
SELECT my_record #= '"field"=>"value"'::hstore; -- with string literal
SELECT my_record #= hstore(field, value); -- with values
Los valores deben convertirse en text
y viceversa, obviamente.
Ejemplo de funciones plpgsql con más detalles:
- Bucle sin fin en la función de disparo
- Asignar a NUEVO por clave en un activador de Postgres
Ahora funciona con json
/ jsonb
, también!
Hay soluciones similares con json
(pág. 9.3+) o jsonb
(pág. 9.4+)
SELECT json_populate_record (my_record, json_build_object('key', 'new-value');
La funcionalidad no estaba documentada, pero es oficial desde Postgres 13. El manual:
Sin embargo, si la base no es NULL, los valores que contiene se utilizarán para las columnas no coincidentes.
De modo que puede tomar cualquier fila existente y completar campos arbitrarios (sobrescribiendo lo que contiene).
Principales ventajas de json
vs hstore
:
- funciona con Postgres estándar, por lo que no necesita un módulo adicional.
- también funciona para matrices anidadas y tipos compuestos.
Desventaja menor:un poco más lento.
Consulte la respuesta agregada de @Geir para obtener más detalles.
Sin hstore
y json
Si tiene una versión anterior o no puede instalar el módulo adicional hstore
o no puede asumir que está instalado, aquí hay una versión mejorada de lo que publiqué anteriormente. Todavía más lento que el hstore
operador, sin embargo:
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$;
Llamar:
CREATE TEMP TABLE t( a int, b text); -- Composite type for testing
SELECT f_setfield(NULL::t, 'a', '1');
Notas
-
Una conversión explícita del valor
_val
al tipo de datos de destino no es necesario, un literal de cadena en la consulta dinámica se forzaría automáticamente, obviando la subconsulta enpg_type
. Pero lo llevé un paso más allá: -
Reemplazar
quote_literal(_val)
con inserción directa de valor a través deUSING
cláusula. Guarda una llamada de función y dos lanzamientos, y es más seguro de todos modos.text
se coacciona al tipo de destino automáticamente en PostgreSQL moderno. (No se probó con versiones anteriores a la 9.1.) -
array_to_string(ARRAY())
es más rápido questring_agg()
. -
No se necesitan variables, no
DECLARE
. Menos tareas. -
No hay subconsulta en el SQL dinámico.
($1).field
es más rápido. -
pg_typeof(_comp_val)::text::regclass
hace lo mismo que(SELECT typrelid FROM pg_catalog.pg_type WHERE oid = pg_typeof($1)::oid)
para tipos compuestos válidos, simplemente más rápido.
Esta última modificación se basa en el supuesto de quepg_type.typname
siempre es idéntico alpg_class.relname
asociado para tipos compuestos registrados, y la conversión doble puede reemplazar la subconsulta. Ejecuté esta prueba en una gran base de datos para verificar y resultó vacía como se esperaba:
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
- El uso de un
INOUT
El parámetro obvia la necesidad de unRETURN
explícito . Esto es solo un atajo de notación. A Pavel no le gustará, prefiere unRETURN
explícito declaración...
Todo junto es el doble de rápido como la versión anterior.
Respuesta original (obsoleta):
El resultado es una versión que es ~ 2,25 veces más rápida . Pero probablemente no podría haberlo hecho sin basarme en la segunda versión de Pavel.
Además, esta versión evita la mayor parte del casting al texto y viceversa haciendo todo dentro de una sola consulta, por lo que debería ser mucho menos propenso a errores.
Probado con PostgreSQL 9.0 y 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$;