2010-09-14 7 views
2

Je dois faire un peu d'audit sur la mise à jour d'une ligne. J'ai donc une fonction qui reçoit un paramètre de type some_table% ROWTYPE, contenant les nouvelles valeurs à sauvegarder pour cette ligne.Récupère la propriété de variable Oracle PL/SQl par son nom (réflexion en PL/SQL)

J'ai également besoin d'enregistrer quelques informations dans un tableau d'historique concernant les valeurs des colonnes qui ont été modifiées. Je pensais obtenir les noms de colonne pour some_table à partir de all_tab_columns, puis iter sur ceux-ci pour comparer les anciennes et les nouvelles valeurs et voir si elle a été changée. Le problème est une fois que j'ai le nom de la colonne, je ne sais pas comment accéder à la valeur de ma variable ROWTYPE. Quelque chose comme var.getProperty (columnName). Je voulais le faire de cette façon pour éviter d'avoir un tas d'IF, une pour chaque champ, et cela fonctionnerait aussi directement sur l'ajout d'une nouvelle colonne à la table.

De même, je ne peux pas utiliser de déclencheurs parce que les plus hauts ont dit "Aucun déclencheur!". (Si c'est effectivement le seul moyen, je pourrais essayer de leur parler à nouveau à ce sujet).

+4

Il y a, bien sûr, un certain nombre de façons de gérer cela, mais à mon avis un élément déclencheur est la meilleure approche pour maintenir une table d'histoire. –

+0

@BobJarvis - un déclencheur est un mécanisme simple pour appeler du code. Il n'y a rien que nous puissions faire dans un déclencheur que nous ne pouvons pas faire dans une fonction comme décrit ici Andrei. – APC

+0

@APC: Je soumets respectueusement que la différence est qu'un déclencheur fournit une exécution automatique dans des conditions données, par ex. SUR INSERER, SUR SUPPRIMER, etc.Bien que la même fonctionnalité puisse être implémentée dans une fonction, tout le code qui maintient la table de base en question doit maintenant être écrit pour appeler la fonction 'mise à jour de table d'historique', et mon expérience est que ces types de choses sont oubliés, oubliés ou contourné. Si la direction de @ Andrei a décrété "NO TRIGGERS!" la mise à jour de la table d'historique est mieux implémentée en tant que procédure, mais IMO un déclencheur serait mieux. YMMV. –

Répondre

4

Il peut être intéressant de découvrir pourquoi il existe une règle «sans déclencheur».

Il y a beaucoup de bons arguments contre les déclencheurs - en particulier pour mettre les règles métier dans les déclencheurs - mais la journalisation est généralement acceptée comme un bon argument en faveur de leur utilisation.

Il est également intéressant de regarder le versionnement de table intégré d'Oracle (qui enregistre une ligne par mise à jour) - cela permet de garder la forme de l'historique en ligne avec la forme de la table actuelle. Il ne vous donne pas l'historique «ce qui a changé», mais il est probablement préférable de faire «ce qui a changé» au moment où vous regardez l'historique, plutôt que d'ajouter le coût à chaque mise à jour. La seule façon que j'ai trouvée de faire quelque chose comme ce que vous voulez - accéder dynamiquement à une propriété d'un% ROWTYPE, est de placer une variable sur un en-tête de paquet (visible publiquement) puis de faire du PL/SQL dynamique . Vous pouvez encapsuler la variable de ligne, à condition que votre bloc pl/sql dynamique contienne une copie locale avant chaque vérification. imaginez-le comme votre modèle pour l'exécution immédiate.

DECLARE 
    lNew myTab%ROWTYPE; 
    lOld myTab%ROWTYPE; 
    lReturn PLS_INTEGER := 0; 
BEGIN 
    lNew := pStatefulPackage.NewRow; 
    lOld := pStatefulPackage.OldRow; 
    IF NVL(lNew.<variable>,'~') != NVL(lOld..<variable>,'~') THEN 
     :lReturn := 1; 
    END IF; 
END; 

Ce qui est beaucoup de tracas à travailler autour du fait que vous ne pouvez pas lier des variables ou booléens dans SQL dynamique enregistrer.

Il ajoute également beaucoup de frais généraux par colonne.

Enfin, j'ai trouvé que ALL_TAB_COLUMNS est trop lent à utiliser pour ce genre de chose - vous auriez besoin de mettre en cache les métadonnées dans la mémoire locale pl/sql.

+0

Par "Oracle version de table intégrée" Je présume que vous voulez dire le Total Recall (aka Flashback Archive) introduit dans 11g. Je suis d'accord que c'est une solution cool et la plus élégante, mais elle n'est disponible que comme un supplément à la licence Enterprise Edition. – APC

+0

Merci, j'ai fini par faire quelque chose de similaire. –

2

Il pourrait être plus facile de générer le corps de la fonction, en lisant les noms de colonnes de USER_TAB_COLUMNS, par ex. (Simplifié, ne vérifie pas les valeurs NULL):

select 'if :old.'||column_name||' <> :new.'||column_name||' then log('''||column_name||''',:old.'||column_name||',:new.'||column_name||'); end if;' 
    from user_tab_columns 
    where table_name='MYTABLE'; 
+0

J'ai essayé de faire un EXECUTE sur quelque chose comme ça mais je ne peux pas mettre mes variables pl/sql dans la clause USING pour remplacer: old et: new. Il donne une erreur en disant qu'ils ne sont pas des types SQL. –

+0

Ah, désolé, les anciens et les nouveaux sont typiques des déclencheurs. Ma faute. Remplacez-les par les noms des paramètres de votre fonction (bien sûr, sans les deux-points) –

Questions connexes