Vyjímky a jejich ošetření v ORACLE

Obsah

Úvodem
   Přehled
   Výhody vyjímek
Vyjímky
   Předdefinované vyjímky
   Uživatelské vyjímky
   Práce s vyjímkami
   Vyvolávání a šíření vyjímek
   Ošetření vyvolaných vyjímek
Užitečné techniky
   Pokračování po vyvolání vyjímky
   Opakování transakce
   Použití vyhledávací proměnné
Přílohy
   Nejčastěji používané vyjímky

Úvodem

V mnoha programovacích jazycích platí, že běhové chyby zastaví program a vrací kontrolu operačnímu systému. Pokud existuje mechanismus ošetřování vyjímek (jako je tomu např. právě v PL/SQL), je možné pokračovat ve výpočtu i po chybě a udělat tak program stabilnější a uživatelsky příznivější.

Běhové chyby lze rozdělit na:

Přehled

Vyjímky v PL/SQL můžeme rozdělit na

Některé z běžných vnitřních vyjímek mají i svá zástupná jména (např. výše zmíněným odpovídají ZERO_DIVIDE a STORAGE_ERROR), případně je lze dodatečně pojmenovat. Uživatelské vyjímky jsou pojmenovány vždy.

V případě, že v průběhu výpočtu programu nastane chyba, je vyvolána příslušná vyjímka (automaticky při vnitřní vyjímce, či explicitně pomocí přikazu RAISE). K ošetření vyvolané vyjímky se používá exception handler, který zastaví právě vykonávaný blok a po svém skončení výpočet pokračuje.

Příklad:
DECLARE
  
pe_ratio NUMBER(3,1);
BEGIN
  SELECT 
price / earnings INTO pe_ratio FROM stocks
    WHERE symbol = 'XYZ';  -- might cause division-by-zero error
    
INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
  COMMIT;
EXCEPTION  -- Exception handler
  
WHEN ZERO_DIVIDE THEN  -- handles 'division by zero' error
    
INSERT INTO stats (symbol, ratio) VALUES ('XYZ'NULL);
    COMMIT;
  ...
  WHEN OTHERS THEN  -- handles all other errors
    
ROLLBACK;
END;   -- exception handlers and block end here

Výhody vyjímek

Využití vyjímek v zachytávání chyb má několik výhod:

Vyjímky

Předdefinované vyjímky

Kdykoli PL/SQL program poruší systémová či Oracle omezení je vyvolána vnitřní vyjímka (např. jestliže příkaz SELECT INTO nevrátí žádný řádek, je vyvolána vyjímka s názvem NO_DATA_FOUND).

Dále je možné využít funkce:

Možnou alternativou je pak direktiva EXCEPTION_INIT (viz "Použití EXCEPTION_INIT").

Tabulku často používaných pojmenovaných vyjímek najdete v příloze.

Uživatelské vyjímky

V PL/SQL má uživatel (programátor) možnost nadefinovat si své vlastní vyjímky.
Vyvolání nadeklarované vyjímky je možné pomocí příkazu RAISE.

Deklarace

Deklarace vyjímky začíná jejím jménem následované klíčovým slovem EXCEPTION. Narozdíl od proměnné chápeme vyjímku jako chybový stav a nikoliv jako datovou položku. Pole působnosti (rozsah platnosti) vyjímek i proměnných je totožné.

DECLARE
   
past_due EXCEPTION;

Rozsah platnosti

Deklarace vyjímek a rozsah platnosti se řídí několika pravidly:

Následující příklad ilustruje rozsah platnosti:
DECLARE
  
past_due EXCEPTION;
  acct_num NUMBER;
BEGIN
  
...
  DECLARE  ---------- sub-block begins
    
past_due EXCEPTION;  -- this declaration prevails
    
acct_num NUMBER;
  BEGIN
  
...
    IF ... THEN
      
RAISE past_due;  -- this is not handled
    
END IF;
    ...
  END;  ------------- sub-block ends
EXCEPTION
  WHEN 
past_due THEN  -- does not handle RAISEd exception
    
...
END;

Poznámka: Přilehlý blok neošetří vyvolanou vyjímku, protože deklarace vyjímky past_due v podbloku převáží. Ačkoli tyto dvě vyjímky sdílejí stejné jméno, jedná se o dvě různé vyjímky, stejně jako dvě proměnné s názvem acct_num jsou dvě rozdílné proměnné. Proto se tedy příkaz RAISE a klauzule WHEN odkazují na rozdílné vyjímky. Aby přilehlý blok mohl ošetřit tuto vyjímku, musela by být odstraněna její deklarace v podbloku, nebo přidán handler OTHERS.

Práce s vyjímkami

Použití EXCEPTION_INIT

Zachytávání nepojmenovaných vnitřních vyjímek lze pomocí

EXCEPTION_INIT spojuje jméno vyjímky s příslušným číslem chyby v Oraclu. Lze se pak odkazovat jménem na libovolnou vnitřní vyjímku a napsat pro ni specifický handler.

Syntaxe:
PRAGMA EXCEPTION_INIT(exception_name, Oracle_error_number);

  Kde exception_name je předem deklarovaná vyjímka.

Použití raise_application_error

Pomocí příkazu raise_application_error (z balíčku DBMS_STANDARD) lze oznámit vzniklou (neošetřenou) chybu aplikaci.

Syntax:
raise_application_error(error_number, message[, {TRUE | FALSE}]);


  Kde error_number je záporné celé číslo (integer) v rozsahu -20000 .. -20999 a message je řetězec maximální délky 2048 bytů. Jestliže je třetí nepovinný parameter TRUE, chyba je uložena na zásobník, pokud je FALSE (default), chyba nahradí všechny dosud uložené chyby. Volání procedury ukončí podprogram a vrátí chybu a zprávu aplikaci.

Informace o vyjímce

Ke zjištění informací jsou k dispozici funkce

Maximální délka chybové zprávy je 512 znaků včetně kódu chyby, vnořených zpráv a vložených informací jako jsou jména tabulek a řádků.
Pokud nebyla vyvolána žádná vyjímka vrací SQLCODE nulu a SQLERRM zprávu "ORA-0000: normal, successful completion".

Funkci SQLERRM lze také předat (záporné) číslo chyby. V takovém případě vrací chybovou zprávu příslušící tomuto číslu.

Předeklarování předdefinovaných vyjímek

PL/SQL deklaruje předdefinované vyjímky globálně v balíčku STANDARD, takže je uživatel (programátor) nemusí deklarovat sám. Předeklarování předdefinovaných vyjímek je náchylné k chybám, protože lokální deklarace převáží deklaraci globální.

Vyvolávání a šíření vyjímek

Vnitřní a pojmenované (spojené pomocí EXCEPTION_INIT) vyjímky jsou vyvolávány implicitně - runtime systémem. Ostatní uživatelsky definované vyjímky musí být vyvolány explicitně pomocí příkazu RAISE.

Příkaz RAISE

Příkaz RAISE lze umístit kdekoli v rozsahu platnosti vyjímky.

Následující příklad ukazuje jak vyvolání uživatelské (out_of_stock) tak vyjímky předdefinované (INVALID_NUMBER):
DECLARE
  
out_of_stock   EXCEPTION;
  number_on_hand NUMBER(4);
  acct_type INTEGER;
  ...
BEGIN
  
...
  IF number_on_hand < THEN
    
RAISE out_of_stock;
  END IF;
  ...
  IF acct_type NOT IN (123THEN
    
RAISE INVALID_NUMBER;  -- raise predefined exception
  
END IF;
  ...
EXCEPTION
  WHEN 
INVALID_NUMBER THEN
    ROLLBACK
;
  WHEN out_of_stock THEN
    
... -- handle the error
END;


Šíření vyjímky

Pokud není vyvoláná vyjímka zachycena (ošetřena) v aktuálním bloku propaguje se do postupně do nadřazených bloků, dokud nenalezne vyhovující handler. V případě, že se tak nestane, vrací PL/SQL chybu unhandled exception.

Základní pravidla pro šíření:


© Oracle

Vyvolaná vyjímka "A" je ošetřena ještě ve vnořeném bloku a výpočet tedy pokračuje za tímto vnořeným blokem.


© Oracle

Vyvolaná vyjímka "B" není ošetřena ve vnořeném bloku, ale až v bloku nadřazeném. Výpočet tedy pokračuje až za nadřazeným blokem.


© Oracle

Vyvolaná vyjímka "C" není ošetřena ani ve vnořeném ani v nadřazeném bloku. Do prostředí je tedy vrácena chyba – neošetřená vyjímka.

Vyjímka se šíří dokonce i za svůj rozsah platnosti, tj. i za blok, v němž byla nadeklarována.
Mimo svůj rozsah platnosti však smí být zachycena pouze pomocí handleru OTHERS.

Opětovné vyvolání vyjímky

Někdy je zapotřebí znovu vyvolat odchycenou vyjímku, tzn. ošetřit ji lokálně a poslat ji dále - do přilehlého bloku.
K opětovnému vyvolání vyjímky stačí jen vložit příkaz RAISE do handleru. V případě, že hadler ošetřuje pouze jedinou vyjímku, je možné vynechat jméno vyjímky.

Ošetření vyvolaných vyjímek

Když je vyvolána vyjímka, je normální průběh programu zastaven a kontrola předána do handleru.
Každý handler se skládá z klauzule WHEN, která specifikuje vyjímku, pro níž je určen (případně více vyjímek oddělených pomocí OR), následovanou sérií příkazů, jež mají být v případě vyjímky vykonány. Jejich provedení ukončí blok či podprogram a výpočet pokračuje za celým blokem.
Nepovinný je pak handler OTHERS, který je (pokud je použit) posledním handlerem v bloku a zachytává libovolnou vyjímku.

Poznámka: Podle platnosti běžných pravidel rozsahu PL/SQL proměnných, lze i v handlerech používat jak lokální tak globální proměnné. Avšak pokud je vyjímka vyvolána uvnitř kurzorového FOR cyklu, je nejdřív kurzor uzavřen.
Také nelze provádět skoky do a z handleru (příkazem GOTO). Nicméně příkaz GOTO lze použít ke skoku z handleru do přilehlého bloku.

Vyjímky vyvolané v deklaracích

Také chyba v inicializaci může zapříčinit vyvolání vyjímky. Pak je taková vyjímka propagováno přímo do přilehlého (nadřízeného) bloku.

Příklad (do limit nelze uložit číslo větší než 999):
DECLARE
   
limit CONSTANT NUMBER(3) := 5000;  -- raises an exception
BEGIN
   
...
EXCEPTION
   WHEN 
OTHERS THEN ...  -- cannot catch the exception

Neošetřené vyjímky

Neošetřené vyjímky vrací chybu unhandled exception. Databázové změny způsobené neúspěšným podprogramem (skončí právě touto chybou) nejsou odvolány (rollback). Lze se jim vyhnout přidáním handleru OTHERS do nejvyšší úrovně každého PL/SQL bloku a podprogramu a provedením případných opravných operací.

Užitečné techniky

Tři techniky zvyšující flexibiltu PL/SQL programu spojené s použitím vyjímek jsou:

Pokračování po vyvolání vyjímky

Při vyvolání vyjímky je výpočet předán handleru a poté nadřízenému bloku. Pokud tedy je potřeba, aby se po zotavení z vyjímky pokračovalo od toho místa, kde k ní došlo, je nutné použít vnořených bloků (podbloků) s vlastními handlery.

Příklad:
DECLARE
  
pe_ratio NUMBER(3,1);
BEGIN
  DELETE FROM 
stats WHERE symbol = 'XYZ';
  BEGIN  ---------- sub-block begins
    
SELECT price / NVL(earnings, 0INTO pe_ratio FROM stocks
      WHERE symbol = 'XYZ';
  EXCEPTION
    WHEN 
ZERO_DIVIDE THEN
      
pe_ratio := 0;
  END;  ---------- sub-block ends
  
INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio);
EXCEPTION 
  
...

Opakování transakce

Někdy je namísto zrušení transakce vhodnější transakci zopakovat. Lze k tomu využít cyklů v kombinaci s příkazy savepoint a rollback.

Příklad:
DECLARE
   
name   CHAR(20);
   ans1   CHAR(3);
   ans2   CHAR(3);
   ans3   CHAR(3);
   suffix NUMBER := 1;
BEGIN
   
...
   LOOP  -- could be FOR i IN 1..10 LOOP to allow ten tries
      
BEGIN  -- sub-block begins
         
SAVEPOINT start_transaction;  -- mark a savepoint
         /* Remove rows from a table of survey results. */
         
DELETE FROM results WHERE answer1 = 'NO';
         /* Add a survey respondent's name and answers. */
         
INSERT INTO results VALUES (name, ans1, ans2, ans3);
            -- raises DUP_VAL_ON_INDEX if two respondents
            -- have the same name (because there is a unique
            -- index on the name column)
         
COMMIT;
         EXIT;
      EXCEPTION
         WHEN 
DUP_VAL_ON_INDEX THEN
            ROLLBACK TO 
start_transaction;  -- undo changes
            
suffix := suffix + 1;             -- try to fix
            
name := name || TO_CHAR(suffix);  -- problem
         
...
      END;  -- sub-block ends
   
END LOOP;
END;



Použití vyhledávací proměnné

Vyvolaná vyjímka může způsobit, že nelze rozpoznat, který příkaz selhal.
Zjistit to lze například pomocí pomocné vyhledávací proměnné.

Příklad:
DECLARE
   
stmt INTEGER := 1;  -- designates 1st SELECT statement
BEGIN
   SELECT 
...
   stmt := 2;  -- designates 2nd SELECT statement
   
SELECT ...
   stmt := 3;  -- designates 3rd SELECT statement
   
SELECT ...
   ...
EXCEPTION
   WHEN 
NO_DATA_FOUND THEN
      INSERT INTO 
errors VALUES ('Error in statement ' || stmt);
      ...
END;


Přílohy

Nejčastěji používané vyjímky

V následující tabulce jsou uvedena jak příslušná jména nejčastěji používaných pojmenovaných vyjímek, jim odpovídající čísla chyb a návratové hodnoty funkce SQLCODE:

Vyjímka  Oracle chyba  Hodnota SQLCODE 
ACCESS_INTO_NULL ORA-06530   -6530  
COLLECTION_IS_NULL ORA-06531   -6531  
CURSOR_ALREADY_OPEN ORA-06511   -6511  
DUP_VAL_ON_INDEX ORA-00001   -1  
INVALID_CURSOR ORA-01001   -1001  
INVALID_NUMBER ORA-01722   -1722  
LOGIN_DENIED ORA-01017   -1017  
NO_DATA_FOUND ORA-01403   +100  
NOT_LOGGED_ON ORA-01012   -1012  
PROGRAM_ERROR ORA-06501   -6501  
ROWTYPE_MISMATCH ORA-06504   -6504  
STORAGE_ERROR ORA-06500   -6500  
SUBSCRIPT_BEYOND_COUNT ORA-06533   -6533  
SUBSCRIPT_OUTSIDE_LIMIT ORA-06532   -6532  
TIMEOUT_ON_RESOURCE ORA-00051   -51  
TOO_MANY_ROWS ORA-01422   -1422  
VALUE_ERROR ORA-06502   -6502  
ZERO_DIVIDE ORA-01476   -1476  

Stručný popis výše uvedených vyjímek:

Vyjímka  Popis 
ACCESS_INTO_NULL Při přiřazování hodnoty do atributu neinicializovaného (null) objektu.
COLLECTION_IS_NULL Při používání jiné sběrné metody (collection method) než EXISTS na neinicializovanou (atomicky null) vnořenou tabulku nebo pole (varray).
Při přiřazování hodnoty do položek neinicializované vnořené tabulky nebo pole
CURSOR_ALREADY_OPEN Při otevření již jednou otevřeného kurzoru (cursor).
DUP_VAL_ON_INDEX Při ukládání totožné (již existující) hodnoty do databázového sloupce, který je označen jako unique.
INVALID_CURSOR Při provádění nepovolené operace s kurzorem (cursor) jakou je například zavření neotevřeného kurzoru.
INVALID_NUMBER Při selhání převodu řetězce na číslo z toho důvodu, že řetězec neobsahuje platné číslo (v SQL).
V procedurálním příkazu je vyvolána vyjímka VALUE_ERROR.
LOGIN_DENIED Při přihlašování k Oracle s neplatným jménem (username) a/nebo heslem (password).
NO_DATA_FOUND V situaci, kdy příkaz SELECT INTO nevrátí žádné řádky, při odkazování na smazaný prvek vnořené tabulky nebo neinicializovaný prvek index-by tabulky.
Od příkazu FETCH se případně dá očekávat navrácení prázdného řádku (no rows) a v tomto případě vyjímka není vyvolána.
Agregační funkce SQL jako AVG či SUM vracejí vždy hodnotu nebo null a proto také příkaz SELECT INTO s agregační funkcí nevyvolává vyjímku NO_DATA_FOUND.
NOT_LOGGED_ON Při pokusu provést databázou operaci bez předchozího připojení k Oracle.
PROGRAM_ERROR Při vnitřní chybě (internal problem) PL/SQL.
ROWTYPE_MISMATCH Při nekompatibilitě hlavní proměnné (host) a PL/SQL kurzorové proměnné.
STORAGE_ERROR Při vyčerpání nebo poškození paměti.
SUBSCRIPT_BEYOND_COUNT Při odkazování na prvek vnořené tabulky (nested table) či pole (varray) s číslem indexu větším než je počet dostupných prvků.
SUBSCRIPT_OUTSIDE_LIMIT Při odkazování na prvek vnořené tabulky (nested table) či pole (varray) s číslem indexu, který je mimo povolený rozsah (např. -1).
TIMEOUT_ON_RESOURCE Při vypršení času, během kterého Oracle čeká na prostředky (resource).
TOO_MANY_ROWS V situaci, kdy příkaz SELECT INTO vrátí více než jednu řádku.
VALUE_ERROR Při chybě aritmetickě, převodní; při zkrácení nebo omezení velikosti.
Například při dosazení hodnoty ze sloupce do znakové proměnné s menší délkou. V procedurálních příkazech je vyjímka vyvolána, když selže převod mezi řetězcem a číslem (v SQL je to vyjímka INVALID_NUMBER).
ZERO_DIVIDE Při pokusu o dělení nulou.


Použitý anglický originál dokumentace je zde.
Jeho nevalný volný překlad je tady.

René Mihula, 19. 3. 2003