Bien que la réponse de @ Gary soit techniquement correcte, il omet de mentionner que PostgreSQL fait prendre en charge ce formulaire :
UPDATE tbl
SET (col1, col2, ...) = (expression1, expression2, ..)
Lire le manuel sur UPDATE
encore une fois.
Il est toujours difficile de faire son travail avec SQL dynamique. Puisque vous n'avez pas spécifié, je suppose un cas simple où les vues se composent des mêmes colonnes que leurs tables sous-jacentes.
CREATE VIEW tbl_view AS SELECT * FROM tbl;
Problèmes
-
L'enregistrement spécial
NEW
n'est pas visible dansEXECUTE
. Je passeNEW
en tant que paramètre unique avec leUSING
clause deEXECUTE
. -
Comme indiqué,
UPDATE
avec une forme de liste nécessite des valeurs individuelles . J'utilise une sous-sélection pour diviser l'enregistrement en colonnes individuelles :UPDATE ... FROM (SELECT ($1).*) x
(Parenthèse autour de
$1
ne sont pas facultatifs.) Cela me permet d'utiliser simplement deux listes de colonnes construites avecstring_agg()
depuis la table catalogue :une avec et une sans qualification de table. -
Il n'est pas possible d'affecter une valeur de ligne dans son ensemble à des colonnes individuelles. Le manuel :
Selon la norme, la valeur source d'une sous-liste entre parenthèses de noms de colonnes cibles peut être n'importe quelle expression de valeur de ligne produisant le nombre correct de colonnes. PostgreSQL permet uniquement à la valeur source d'être un constructeur de ligne ou un sous-
SELECT
. -
INSERT
est mis en œuvre plus simplement. En supposant que la structure de la vue et de la table sont identiques, j'omets la liste des définitions de colonne. (Peut être amélioré, voir ci-dessous.)
Solution
J'ai apporté un certain nombre de mises à jour à votre approche pour la faire briller.
Fonction de déclenchement pour 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;
Fonction de déclenchement pour 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;
Déclencheurs :
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();
Violon SQL démontrant INSERT
et UPDATE
.
Points majeurs
-
Incluez le nom du schéma pour rendre la référence de table non ambiguë. Il peut y avoir plusieurs instances du même nom de table dans la même base de données dans plusieurs schémas !
-
Requête
pg_attribute
au lieu deinformation_schema.columns
. C'est moins portable, mais beaucoup plus rapide et me permet d'utiliser la table-OID.- Comment vérifier si une table existe dans un schéma donné
-
Les noms de table ne sont PAS sûrs contre SQLi lorsqu'ils sont traités comme des chaînes, comme dans la création de requêtes pour SQL dynamique. Échappez-vous avec
quote_ident()
ouformat()
ou avec un type d'identificateur d'objet. Cela inclut les variables de fonction de déclenchement spécialesTG_TABLE_SCHEMA
etTG_TABLE_NAME
! -
Convertir en type d'identifiant d'objet
regclass
pour affirmer que le nom de la table est valide et obtenir l'OID pour la recherche dans le catalogue. -
Utilisez éventuellement
format()
pour créer la chaîne de requête dynamique en toute sécurité. -
Pas besoin de SQL dynamique pour la première requête sur les tables du catalogue. Plus rapide, plus simple.
-
Utilisez
RETURN NEW
au lieu deRETURN NULL
dans ces fonctions de déclenchement à moins que vous ne sachiez ce que vous faites. (NULL
annulerait leINSERT
pour la ligne actuelle.) -
Cette version simple suppose que chaque table (et vue) a une colonne unique nommée
id
. Une version plus sophistiquée pourrait utiliser la clé primaire de manière dynamique. -
La fonction pour
UPDATE
permet aux colonnes de la vue et de la table d'être dans n'importe quel ordre , tant que l'ensemble est le même. La fonction pourINSERT
s'attend à ce que les colonnes de la vue et de la table soient dans le ordre identique . Si vous souhaitez autoriser un ordre arbitraire, ajoutez une liste de définition de colonne àINSERT
commande, comme avecUPDATE
. -
La version mise à jour couvre également les modifications apportées à l'
id
colonne en utilisantOLD
en plus.