sql >> Base de Datos >  >> RDS >> PostgreSQL

Cómo establecer el valor del campo variable compuesto usando SQL dinámico

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 en pg_type . Pero lo llevé un paso más allá:

  • Reemplazar quote_literal(_val) con inserción directa de valor a través de USING 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 que string_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 que pg_type.typname siempre es idéntico al pg_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 un RETURN explícito . Esto es solo un atajo de notación. A Pavel no le gustará, prefiere un RETURN 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$;