Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PL/SQL Oracle Discussion :

Capture des erreurs levées dans le curseur


Sujet :

PL/SQL Oracle

  1. #1
    Futur Membre du Club
    Capture des erreurs levées dans le curseur
    Bonjour,

    Je souhaite capturer les erreurs levées lors du "fetch" d'un curseur puis continuer à l'itération suivante.

    Dans l'exemple ci-dessous, je crée une table où j'insère les valeurs suivantes : 1, 2, 3, -1 et 4. Ensuite, je fais en sorte que le programme plante sur le nombre négatif au moment de convertir 000-1 en nombre.

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    SET SERVEROUTPUT ON;
     
    -- Creation du jeu de donnees
    DROP TABLE table01;
     
    CREATE TABLE table01 (
      champ_nombre NUMBER
    );
     
    INSERT INTO table01 VALUES(1);
    INSERT INTO table01 VALUES(2);
    INSERT INTO table01 VALUES(3);
    INSERT INTO table01 VALUES(-1);
    INSERT INTO table01 VALUES(4);
     
    -- Procedure
    DECLARE
      TYPE rec_curseur01 IS RECORD (nb NUMBER);
      CURSOR curseur01 RETURN rec_curseur01 IS SELECT LPAD(champ_nombre,5,0) FROM table01;
    BEGIN
     
      FOR ligne IN curseur01 LOOP
        BEGIN
          DBMS_OUTPUT.PUT_LINE(ligne.nb);
     
          EXCEPTION
            WHEN OTHERS THEN
              DBMS_OUTPUT.PUT_LINE('e01 : ' || SQLERRM);
        END;
      END LOOP;
     
      EXCEPTION
            WHEN OTHERS THEN
              DBMS_OUTPUT.PUT_LINE('e02 : ' || SQLERRM);
    END;
    /


    Résultat actuel :
    1
    2
    3
    e02 : ORA-01722: Nombre non valide
    Résultat attendu (affichage de l'erreur puis itération suivante) :
    1
    2
    3
    e01 : ORA-01722: Nombre non valide
    4
    Avez-vous une idée ?

    Cordialement,

  2. #2
    Membre éclairé
    Bonjour,

    Le problème survient dans le parcours du curseur, à l'itération 4 quand Oracle essaie de placer le résultat dans la variable de type number. L'erreur est donc "à l'extérieur" de la boucle, et c'est pour cela que tu tombes dans le bloc d'exception global.
    Si tu veux pouvoir gérer l'exception à l'intérieur de la boucle, alors fais la conversion à l'intérieur de la boucle, pas dans la requête du curseur. Par exemple:

    Code :Sélectionner tout -Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    -- Procedure
    DECLARE
      TYPE rec_curseur01 IS RECORD (nb NUMBER);
      v_num number;
      CURSOR curseur01 RETURN rec_curseur01 IS SELECT champ_nombre FROM table01;
    BEGIN
     
      FOR ligne IN curseur01 LOOP
        BEGIN
          DBMS_OUTPUT.PUT_LINE(ligne.nb);
     
          v_num := LPAD(ligne.nb,5,0);
     
          EXCEPTION
            WHEN value_error THEN
              DBMS_OUTPUT.PUT_LINE('e01 : ' || SQLERRM);
        END;
      END LOOP;
     
      EXCEPTION
            WHEN OTHERS THEN
              DBMS_OUTPUT.PUT_LINE('e02 : ' || SQLERRM);
              raise;
    END;
    /


    Tu verras que j'ai mis 'exception "value_error" car OTHERS n'est à utiliser que pour les exceptions auxquelles tu ne t'attends pas. Je rappelle qu'en PL/SQL on ne gère que les exceptions auxquelles on s'attend. Donc WHEN OTHERS est exclusivement réservé à des fins de logging, puis ensuite on raise.

  3. #3
    Futur Membre du Club
    Oui vanagreg, j'ai créé un exemple simplifié car le programme réel contient beaucoup plus d'opérations. L'idéal serait de capturer n'importe quelle erreur levée dans le curseur sans interrompre la boucle afin de pouvoir réutiliser ce bout de code à tous les curseurs ou encore de prévoir d'éventuelles anomalies introduites lors d'évolutions.

    Cordialement,

  4. #4
    Membre éclairé
    Oui mais si la requête du curseur est susceptible de retourner des erreurs (ce qui ne devrait pas se produire), alors tu ne rentreras pas dans la boucle.
    Tu ne pas pas gérer "n'importe quelle erreur" puis continuer la boucle. Le WHEN OTHERS n'est pas fait pour cela, mais malheureusement il est très souvent mal utilisé. Tu veux continuer la boucle alors qu'une erreur inconnue s'est produite? Ca n'a pas de sens. Quelques exemples:

    - Tablespace plein, donc même si tu continues tu n'arriveras plus à insérer de lignes, mais tu ne le sauras pas tout de suite
    - Une conversion en TO_DATE se passe mal, donc on ignore l'erreur alors que la ligne aurait pu être traitée si la conversion avait bien été faite
    - Erreur indirecte ailleurs dans le code qu'on ne verra pas car avalée par le WHEN OTHERS, et après on se pose mille questions de savoir pourquoi cette ligne du curseur n'a pas été traitée alors qu'elle semble valide
    - etc.

    J'ai eu tellement de mauvaises surprises à cause d'abus de WHEN OTHERS.

  5. #5
    Futur Membre du Club
    Merci de ton retour d'expérience. L'objectif est de traiter les lignes correctes et de recenser dans un fichier spécifique les lignes tombées en erreur pour une analyse a posteriori. Cependant, si ce n'est pas possible (ou mauvaise pratique de développement), je peux envisager de refactoriser le code pour déplacer les opérations dans la boucle au lieu de les effectuer dans le curseur. Malheureusement, si ça plante dans le curseur, toutes les lignes suivantes ne seront pas lues mais je diminue tout de même le risque.

    Cordialement,

  6. #6
    Rédacteur

    Si le contexte s'y prête, ça peut être l'occasion d'utiliser la journalisation des erreurs DML.
    UPDATE... LOG ERRORS

    On fait par exemple un UPDATE qui touche 100 lignes, mais génère une erreur sur 3 d'entre elles.
    Au lieu que la mise à jour échoue dès la première ligne en erreur, elle se poursuit sans interruption en se contentant d'enregistrer les erreurs dans la table de journalisation.
    Donc à la fin on a 97 lignes modifiées, et 3 en erreurs enregistrées dans la table journal.
    On doit ensuite balayer cette table journal et décider ce qu'on fait des erreurs.

    Ce mécanisme est même compatible avec BULK COLLECT ... FORALL.
    Mais il faut alors que l'UPDATE (pour prendre cet exemple), soit la seule opération DML à exécuter à partir de la collection.

    Si vous lisez l'anglais :
    https://oracle-base.com/articles/10g...-logging-10gr2
    https://blogs.oracle.com/oraclemagaz...ng-termination
    Consultant / formateur Oracle indépendant
    Certifié OCP 12c, 11g, 10g ; sécurité 11g

    Ma dernière formation Oracle 19c publiée sur Linkedin : https://fr.linkedin.com/learning/oracle-19c-l-administration

  7. #7
    Membre expérimenté
    loxort, la gestion des erreurs est devenue complexe sous Oracle, un premier lien ici pour appréhender tout cela : http://dbaoraclesql.canalblog.com/archives/2019/08/09/37552845.html
    DBA Oracle
    Rédacteur du blog : dbaoraclesql.canalblog.com

  8. #8
    Futur Membre du Club
    Bonjour,

    Merci pour vos réponses.

    Je vais donc adapter le code pour minimiser le risque d'erreurs (non DML) dans le curseur. Si une erreur arrive malgré tout, alors le parcours du curseur s'interrompt (relancer le curseur en excluant les fetchs non valides serait un peu usine à gaz à mon goût). Pour les erreurs DML, je vais les intercepter avec un bloc imbriqué ou avec le bulk collect/for all si besoin de performances.

    Cordialement,

###raw>template_hook.ano_emploi###