Si bien la respuesta de @Gary es técnicamente correcta, no menciona que PostgreSQL sí 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 deEXECUTE
. PasoNEW
como un solo parámetro conUSING
cláusula deEXECUTE
. -
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 constring_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 deinformation_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()
oformat()
o con un tipo de identificador de objeto. Esto incluye las variables de función de activación especialesTG_TABLE_SCHEMA
yTG_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 deRETURN NULL
en estas funciones de activación a menos que sepa lo que está haciendo. (NULL
cancelaría elINSERT
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 paraINSERT
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 aINSERT
comando, al igual que conUPDATE
. -
La versión actualizada también cubre los cambios en el
id
columna usandoOLD
además.