web-dev-qa-db-fra.com

Dans une fonction de déclenchement, comment savoir quels champs sont mis à jour

Est-ce possible? Je souhaite savoir quelles colonnes ont été spécifiées dans la demande UPDATE, indépendamment du fait que la nouvelle valeur envoyée peut être ou ne pas être celle déjà stockée dans la base de données.

La raison pour laquelle je veux faire cela est parce que nous avons une table qui peut recevoir des mises à jour de plusieurs sources. Auparavant, nous n'enregistrions pas la source de la mise à jour. Maintenant, la table stocke quelle source a effectué la mise à jour la plus récente. Nous pouvons changer certaines sources pour envoyer un identifiant, mais ce n'est pas une option pour tout. J'aimerais donc pouvoir reconnaître le moment où une demande UPDATE ne possède pas d'identifiant afin de pouvoir lui substituer une valeur par défaut.

25
EvilAmarant7x

Si une "source" ne "envoie pas d'identifiant", la colonne reste inchangée. Ensuite, vous ne pouvez pas déterminer si la UPDATE actuelle a été créée par la même source que la dernière ou par une source qui n'a pas changé la colonne du tout. En d'autres termes: cela ne fonctionne pas correctement.

Si la "source" est identifiable par une fonction d'information session , vous pouvez l'utiliser. Comme:

NEW.column = session_user;

Inconditionnellement pour chaque mise à jour.

Solution générale

J'ai trouvé un moyen de résoudre le problème initial. La colonne sera définie sur une valeur par défaut dans any update où la colonne est non mis à jour (pas dans la liste SET de la UPDATE).

L'élément clé est un déclencheur par colonne introduit dans PostgreSQL 9.0 - un déclencheur spécifique à la colonne utilisant la clause UPDATE OFcolumn_name.

Le déclencheur ne se déclenchera que si au moins une des colonnes répertoriées est mentionnée comme cible de la commande UPDATE.

C'est le seul moyen simple que j'ai trouvé pour distinguer si une colonne a été mise à jour avec une nouvelle valeur identique à l'ancienne, par rapport à pas du tout mise à jour.

Un pourrait analyser également le texte renvoyé par current_query() . Mais cela semble difficile et peu fiable.

Fonctions de déclenchement

Je suppose une colonne col défini NOT NULL.

Étape 1: Définissez col sur NULL si inchangé:

CREATE OR REPLACE FUNCTION trg_tbl_upbef_step1()
  RETURNS trigger AS
$func$
BEGIN
   IF OLD.col = NEW.col THEN
      NEW.col := NULL;      -- "impossible" value
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

Étape 2: Revenir à l'ancienne valeur. Le déclencheur sera ne sera déclenché que si la valeur a été réellement mise à jour (voir ci-dessous):

CREATE OR REPLACE FUNCTION trg_tbl_upbef_step2()
  RETURNS trigger AS
$func$
BEGIN
   IF NEW.col IS NULL THEN
      NEW.col := OLD.col;
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

Étape 3: Nous pouvons maintenant identifier la mise à jour manquante et définir une valeur par défaut à la place:

CREATE OR REPLACE FUNCTION trg_tbl_upbef_step3()
  RETURNS trigger AS
$func$
BEGIN
   IF NEW.col IS NULL THEN
      NEW.col := 'default value';
   END IF;

   RETURN NEW;
END
$func$  LANGUAGE plpgsql;

Déclencheurs

Le déclencheur de étape 2 est déclenché par colonne!

CREATE TRIGGER upbef_step1
  BEFORE UPDATE ON tbl
  FOR EACH ROW
  EXECUTE PROCEDURE trg_tbl_upbef_step1();

CREATE TRIGGER upbef_step2
  BEFORE UPDATE OF col ON tbl                -- key element!
  FOR EACH ROW
  EXECUTE PROCEDURE trg_tbl_upbef_step2();

CREATE TRIGGER upbef_step3
  BEFORE UPDATE ON tbl
  FOR EACH ROW
  EXECUTE PROCEDURE trg_tbl_upbef_step3();

Les noms de déclencheurs sont pertinents, car ils sont déclenchés par ordre alphabétique (tous étant BEFORE UPDATE)!

La procédure peut être simplifiée avec quelque chose comme "Déclencheurs par colonne" ou tout autre moyen de vérifier la liste de cibles d'une UPDATE dans un déclencheur. Mais je ne vois aucune poignée pour cela.

Si col peut être NULL, utilisez une autre valeur intermédiaire "impossible" et recherchez également NULL dans la fonction de déclenchement 1:

IF OLD.col IS NOT DISTINCT FROM NEW.col THEN
    NEW.col := '#impossible_value#';
END IF;

Adapter le reste en conséquence.

20
Erwin Brandstetter

Dans plpgsql, vous pourriez faire quelque chose comme ceci dans votre fonction de déclencheur:

IF NEW.column IS NULL THEN
  NEW.column = 'default value';
END IF;
2
Frank Heikens

J'ai obtenu une autre solution à un problème similaire presque naturellement, car mon tableau contenait une colonne avec la sémantique de 'last update timestamp' (appelons-le UPDT).

J'ai donc décidé d'inclure de nouvelles valeurs de source et UPDT dans toute mise à jour uniquement à la fois (ou aucune d'entre elles). Étant donné que l'UPDT doit changer à chaque mise à jour, une telle stratégie permet d'utiliser la condition new.UPDT = old.UPDT pour déduire qu'aucune source n'a été spécifiée avec la mise à jour actuelle et remplacer celle par défaut.

Si on a déjà la colonne 'dernière mise à jour' dans sa table, cette solution sera plus simple que de créer trois déclencheurs. Pas sûr qu'il soit préférable de créer un UPDT, alors que ce n'est pas déjà nécessaire. Si les mises à jour sont si fréquentes qu'il existe un risque de similitude d'horodatage, un séquenceur peut être utilisé à la place de l'horodatage.

0
mas.morozov