La meilleure façon d'encapsuler la logique complexe du curseur Oracle PL/SQL sous forme de vue ?

StackOverflow https://stackoverflow.com/questions/20081

  •  09-06-2019
  •  | 
  •  

Question

J'ai écrit du code PL/SQL pour dénormaliser une table sous une forme beaucoup plus simple à interroger.Le code utilise une table temporaire pour effectuer une partie de son travail, en fusionnant certaines lignes de la table d'origine.

La logique s'écrit sous la forme d'un fonction de table pipeline, en suivant le modèle de l'article lié.La fonction table utilise un PRAGMA AUTONOMOUS_TRANSACTION déclaration pour permettre la manipulation de la table temporaire, et accepte également un paramètre d'entrée de curseur pour limiter la dénormalisation à certaines valeurs d'ID.

J'ai ensuite créé une vue pour interroger la fonction table, en passant toutes les valeurs d'ID possibles sous forme de curseur (les autres utilisations de la fonction seront plus restrictives).

Ma question:est-ce que tout cela est vraiment nécessaire ?Ai-je complètement raté une manière beaucoup plus simple d’accomplir la même chose ?

Chaque fois que je touche PL/SQL, j'ai l'impression de trop taper.

Mise à jour: J'ajouterai un croquis du tableau auquel je travaille pour donner à chacun une idée de la dénormalisation dont je parle.La table stocke un historique des emplois des employés, chacun avec une ligne d'activation et (éventuellement) une ligne de fin.Il est possible qu'un employé ait plusieurs tâches simultanées, ainsi que le même travail encore et encore dans des plages de dates non contiguës.Par exemple:

| EMP_ID | JOB_ID | STATUS | EFF_DATE    | other columns...
|      1 |     10 | A      | 10-JAN-2008 |
|      2 |     11 | A      | 13-JAN-2008 |
|      1 |     12 | A      | 20-JAN-2008 |
|      2 |     11 | T      | 01-FEB-2008 |
|      1 |     10 | T      | 02-FEB-2008 |
|      2 |     11 | A      | 20-FEB-2008 |

Interroger cela pour savoir qui travaille et dans quel travail n'est pas trivial.Ainsi, ma fonction de dénormalisation remplit la table temporaire avec uniquement les plages de dates pour chaque travail, pour tout EMP_IDest transmis via le curseur.Passage EMP_IDles s 1 et 2 produiraient ce qui suit :

| EMP_ID | JOB_ID | START_DATE  | END_DATE    |
|      1 |     10 | 10-JAN-2008 | 02-FEB-2008 |
|      2 |     11 | 13-JAN-2008 | 01-FEB-2008 |
|      1 |     12 | 20-JAN-2008 |             |
|      2 |     11 | 20-FEB-2008 |             |

(END_DATE permet NULLs pour les emplois qui n'ont pas de date de fin prédéterminée.)

Comme vous pouvez l'imaginer, ce formulaire dénormalisé est beaucoup plus facile à interroger, mais sa création (pour autant que je sache) nécessite une table temporaire pour stocker les résultats intermédiaires (par exemple, les enregistrements de tâches pour lesquels la ligne d'activation a été trouvé, mais pas la terminaison...pour l'instant).Utiliser la fonction de table pipeline pour remplir la table temporaire, puis renvoyer ses lignes est la seule façon pour moi de comprendre comment procéder.

Était-ce utile?

La solution

Je pense qu'une façon d'aborder cela est d'utiliser des fonctions analytiques...

J'ai configuré votre scénario de test en utilisant :

create table employee_job (
    emp_id integer,
    job_id integer,
    status varchar2(1 char),
    eff_date date
    );  

insert into employee_job values (1,10,'A',to_date('10-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('13-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (1,12,'A',to_date('20-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'T',to_date('01-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (1,10,'T',to_date('02-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('20-FEB-2008','DD-MON-YYYY'));

commit;

J'ai utilisé le plomb fonction pour obtenir la date suivante, puis encapsuler le tout sous forme de sous-requête juste pour obtenir les enregistrements "A" et ajouter la date de fin s'il y en a une.

select
    emp_id,
    job_id,
    eff_date start_date,
    decode(next_status,'T',next_eff_date,null) end_date
from
    (
    select
        emp_id,
        job_id,
        eff_date,
        status,
        lead(eff_date,1,null) over (partition by emp_id, job_id order by eff_date, status) next_eff_date,
        lead(status,1,null) over (partition by emp_id, job_id order by eff_date, status) next_status
    from
        employee_job
    )
where
    status = 'A'
order by
    start_date,
    emp_id,
    job_id

Je suis sûr qu'il y a certains cas d'utilisation que j'ai manqués, mais vous voyez l'idée.Les fonctions analytiques sont votre amie :)

EMP_ID   JOB_ID     START_DATE     END_DATE            
  1        10       10-JAN-2008    02-FEB-2008         
  2        11       13-JAN-2008    01-FEB-2008         
  2        11       20-FEB-2008                              
  1        12       20-JAN-2008                              

Autres conseils

Plutôt que d'avoir le paramètre d'entrée comme curseur, j'aurais une variable de table (je ne sais pas si Oracle a une telle chose, je suis un gars de TSQL) ou remplirais une autre table temporaire avec les valeurs d'ID et la rejoindre dans la vue /function ou partout où vous en avez besoin.

À mon avis, le seul moment pour les curseurs est lorsque vous avoir boucler.Et lorsque vous devez effectuer une boucle, je recommande toujours de le faire en dehors de la base de données dans la logique de l'application.

On dirait que vous donnez ici une certaine cohérence de lecture, c'est-à-dire :il sera possible que le contenu de votre table temporaire soit désynchronisé avec les données source, si vous avez une modification simultanée des données.

Sans connaître les exigences, ni la complexité de ce que vous souhaitez réaliser.je tenterais

  1. pour définir une vue, contenant une logique (éventuellement complexe) en SQL, sinon j'ajouterais du PL/SQL au mélange avec ;
  2. Une fonction de table pipeline, mais utilisant un type de collection SQL (au lieu de la table temporaire).Un exemple simple est ici : http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109

Le numéro 2 vous donnerait moins de pièces mobiles et résoudrait votre problème de cohérence.

Mathieu Butler

Le vrai problème ici est la conception de la table « en écriture seule » - c'est-à-dire qu'il est facile d'y insérer des données, mais délicat et inefficace d'en extraire des informations utiles !Votre table "temporaire" a la structure que la table "permanente" aurait dû avoir en premier lieu.

Pourriez-vous peut-être faire ceci :

  • Créez une table permanente avec la meilleure structure
  • Remplissez-le pour qu'il corresponde aux données du premier tableau
  • Définir un déclencheur de base de données sur la table d'origine pour garder la nouvelle table synchronisée à partir de maintenant

Ensuite, vous pouvez simplement sélectionner dans le nouveau tableau pour effectuer votre reporting.

Je ne pourrais pas être plus d'accord avec toi, HollyStyles.J'étais également un gars de TSQL et je trouve certaines particularités d'Oracle plus qu'un peu perplexes.Malheureusement, les tables temporaires ne sont pas aussi pratiques dans Oracle et, dans ce cas, une autre logique SQL existante s'attend à interroger directement une table, je lui donne donc cette vue à la place.Il n'y a vraiment aucune logique d'application qui existe en dehors de la base de données dans ce système.

Les développeurs Oracle semblent utiliser les curseurs avec beaucoup plus d'enthousiasme que je ne l'aurais pensé.Compte tenu de la nature asservissante et disciplinaire du PL/SQL, c'est d'autant plus surprenant.

La solution la plus simple est :

  1. Créer un table temporaire globale contenant uniquement les identifiants dont vous avez besoin :

    CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER)  
    ON COMMIT DELETE ROWS;
    
  2. Remplissez la table temporaire avec les ID dont vous avez besoin.

  3. Utilisez l'opération EXISTS dans votre procédure pour sélectionner les lignes qui se trouvent uniquement dans la table ID :

      SELECT yt.col1, yt.col2 FROM your\_table yt  
       WHERE EXISTS (  
          SELECT 'X' FROM tab_ids ti  
           WHERE ti.id = yt.id  
       )
    

Vous pouvez également transmettre une chaîne d'identifiants séparés par des virgules en tant que paramètre de fonction et l'analyser dans une table.Ceci est effectué par un seul SELECT.Vous voulez en savoir plus – demandez-moi comment :-) Mais cela doit être une question distincte.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top