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

Actualice varias columnas en una función de activación en plpgsql

Si bien la respuesta de @Gary es técnicamente correcta, no menciona que PostgreSQL apoya este formulario:

UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)

Lee el manual en UPDATE una vez más.

Todavía es complicado hacerlo con SQL dinámico. Como no especificó, asumo un caso simple en el que las vistas constan de las mismas columnas que sus tablas subyacentes.

CREATE VIEW tbl_view AS SELECT * FROM tbl;

Problemas

  • El registro especial NEW no es visible dentro de EXECUTE . Paso NEW como un solo parámetro con USING cláusula de EXECUTE .

  • Como se discutió, UPDATE con formulario de lista necesita valores individuales . Uso una subselección para dividir el registro en columnas individuales:

    UPDATE ...
    FROM  (SELECT ($1).*) x
    

    (Paréntesis alrededor de $1 no son opcionales). Esto me permite simplemente usar dos listas de columnas construidas con string_agg() de la tabla del catálogo:una con y otra sin calificación de tabla.

  • No es posible asignar un valor de fila como un todo a columnas individuales. El manual:

    De acuerdo con el estándar, el valor de origen para una sublista entre paréntesis de nombres de columna de destino puede ser cualquier expresión con valor de fila que produzca el número correcto de columnas. PostgreSQL solo permite que el valor de origen sea un constructor de filas o un sub-SELECT .

  • INSERT se implementa de forma más sencilla. Suponiendo que la estructura de la vista y la tabla son idénticas, omito la lista de definición de columna. (Se puede mejorar, ver más abajo).

Solución

Realicé una serie de actualizaciones a su enfoque para que brille.

Función de activación 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;

Función de activación 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;

Activadores:

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();

Violín SQL demostrando INSERT y UPDATE .

Puntos principales

  • Incluya el nombre del esquema para que la referencia de la tabla no sea ambigua. ¡Puede haber múltiples instancias del mismo nombre de tabla en la misma base de datos en múltiples esquemas!

  • Consulta pg_attribute en lugar de information_schema.columns . Eso es menos portátil, pero mucho más rápido y me permite usar la tabla-OID.

    • Cómo verificar si una tabla existe en un esquema dado
  • Los nombres de las tablas NO son seguros contra SQLi cuando se manejan como cadenas como en la creación de consultas para SQL dinámico. Escape con quote_ident() o format() o con un tipo de identificador de objeto. Esto incluye las variables de función de activación especiales TG_TABLE_SCHEMA y TG_TABLE_NAME !

  • Transmitir al tipo de identificador de objeto regclass para afirmar que el nombre de la tabla es válido y obtener el OID para la búsqueda en el catálogo.

  • Opcionalmente use format() para construir la cadena de consulta dinámica de forma segura.

  • No se necesita SQL dinámico para la primera consulta en las tablas del catálogo. Más rápido, más simple.

  • Usa RETURN NEW en lugar de RETURN NULL en estas funciones de activación a menos que sepa lo que está haciendo. (NULL cancelaría el INSERT para la fila actual.)

  • Esta versión simple asume que cada tabla (y vista) tiene una columna única llamada id . Una versión más sofisticada podría usar la clave principal de forma dinámica.

  • La función para UPDATE permite que las columnas de vista y tabla estén en cualquier orden , siempre que el conjunto sea el mismo. La función para INSERT espera que las columnas de vista y tabla estén en orden idéntico . Si desea permitir un orden arbitrario, agregue una lista de definición de columna a INSERT comando, al igual que con UPDATE .

  • La versión actualizada también cubre los cambios en el id columna usando OLD además.