2016-05-31 44 views
5

Ho notato uno strano comportamento dell'eccezione NO_DATA_FOUND quando generata dalla funzione utilizzata in PLSQL.NO_DATA_FOUND eccezione non generata quando utilizzata in SELECT INTO

Breve storia: si diffonde dalla funzione quando si utilizza l'assegnazione e non si propaga (o viene gestito in modo silenzioso da qualche parte nel mezzo) quando viene utilizzato in SELECT INTO.

Quindi, data funzione test_me gettando NO_DATA_FOUND eccezione, quando invocato come:

v_x := test_me(p_pk); 

viene generata un'eccezione, mentre quando viene richiamato come:

SELECT test_me(p_pk) INTO v_x FROM dual; 

che non genera un'eccezione. Questo non si verifica con altre eccezioni. Sotto Puoi trovare i miei esempi di test.

Qualcuno potrebbe spiegarmi questo comportamento?

set serveroutput on; 
CREATE OR REPLACE FUNCTION test_me(p_pk NUMBER) RETURN NVARCHAR2 
IS 
    v_ret NVARCHAR2(50 CHAR); 
BEGIN 
    BEGIN 
     SELECT 'PYK' INTO v_ret FROM dual WHERE 1 = 1/p_pk; 
    EXCEPTION WHEN NO_DATA_FOUND THEN 
     dbms_output.put_line(chr(9)||chr(9)||chr(9)||' (test_me NO_DATA_FOUND handled and rerised)'); 
     RAISE; 
    END; 
    RETURN v_ret; 
END; 
/
DECLARE 
    v_x NVARCHAR2(500 CHAR); 
    v_pk NUMBER; 
    PROCEDURE test_example(p_pk NUMBER) 
    IS 
    BEGIN 
     BEGIN 
      dbms_output.put_line(chr(9)||chr(9)||'Test case 1: Select into.'); 
      SELECT test_me(p_pk) INTO v_x FROM dual; 
      dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED')); 
     EXCEPTION 
      WHEN NO_DATA_FOUND THEN 
       dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected'); 
      WHEN OTHERS THEN 
       dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected'); 
     END; 
     dbms_output.put_line(' '); 
     BEGIN 
      dbms_output.put_line(chr(9)||chr(9)||'Test case 2: Assignment.'); 
      v_x := test_me(p_pk); 
      dbms_output.put_line(chr(9)||chr(9)||'Success: '||NVL(v_x,'NULL RETURNED')); 
     EXCEPTION 
      WHEN NO_DATA_FOUND THEN 
       dbms_output.put_line(chr(9)||chr(9)||'Failure: NO_DATA_FOUND detected'); 
      WHEN OTHERS THEN 
       dbms_output.put_line(chr(9)||chr(9)||'Failure: '||SQLCODE||' detected'); 
     END; 
    END; 
BEGIN 
    dbms_output.put_line('START'); 
    dbms_output.put_line(' '); 
    dbms_output.put_line(chr(9)||'Example 1: Function throws some exception, both cases throws exception, everything is working as expected.'); 
    test_example(0); 
    dbms_output.put_line(' '); 
    dbms_output.put_line(chr(9)||'Example 2: Query returns row, there is no exceptions, everything is working as expected.'); 
    test_example(1); 
    dbms_output.put_line(' '); 
    dbms_output.put_line(chr(9)||'Example 3: Query inside function throws NO_DATA_FOUND, strange things happen - one case is throwing exception, the other is not.'); 
    test_example(2); 
    dbms_output.put_line(' '); 
    dbms_output.put_line('END'); 
END; 
/
DROP FUNCTION test_me; 
+1

L'esempio 1 dovrebbe fallire con l'eccezione "ORA-01476: divisore è uguale a zero". – MT0

+0

E lo fa. :) Ho aggiunto una descrizione più chiara degli esempi. –

risposta

6

Un esempio minimo è:

CREATE FUNCTION raise_exception RETURN INT 
IS 
BEGIN 
    RAISE NO_DATA_FOUND; 
END; 
/

Se lo fai:

SELECT raise_exception 
FROM DUAL; 

otterrete una singola riga contenente un valore NULL - Ask Tom stati:

è SEMPRE così in questo modo

e poi seguiti con:

NO_DATA_FOUND non è un errore - si tratta di una "condizioni eccezionali". Tu, il programmatore, decidi se qualcosa è un errore catturando la condizione eccezionale e gestendola (rendendola "non un errore") o ignorandola (facendolo essere un errore).

in sql, nessun dato trovato significa semplicemente "nessun dato trovato", stop.

Sotto le copertine, SQL sta tornando all'applicazione client "hey buddy - no_data_found". Il client in questo caso dice "ah ah, nessun dato trovato significa 'fine dei dati'" e si interrompe.

Così l'eccezione è sollevata nella funzione e il client SQL vede e interpreta questo come non ci sono dati che è un valore NULL e "maniglie" l'eccezione.

Così

DECLARE 
    variable_name VARCHAR2(50); 
BEGIN 
    SELECT raise_exception 
    INTO variable_name 
    FROM DUAL 
END; 
/

succederà alla tabella DUAL ha una singola fila e l'eccezione dalla funzione sarà gestito (silenzio) e la variabile finirà contenente un valore NULL.

Tuttavia,

BEGIN 
    DBMS_OUTPUT.PUT_LINE(raise_exception); 
END; 
/

L'eccezione viene questa volta viene passato dalla funzione di un/SQL portata PL - che non gestisce l'errore e passa l'eccezione al blocco gestore di eccezioni (che non esiste quindi viene passato all'ambito dell'applicazione e termina l'esecuzione del programma.

e chiedere Tom afferma:

Sotto le coperte, PLSQL sta alzando di nuovo all'applicazione client "hey - NO_DATA_FOUND Il cliente, in questo caso dice" uh-oh, non era aspettava che dal PLSQL: sql sicuro, ma non PLSQL. Consente di stampare il testo relativo a questa condizione eccezionale e continuare su "

Si vede - è tutto nel modo in cui il CLIENT interpreta il messaggio ORA-xxxxx. Quel messaggio, quando generato da SQL, viene interpretato dal client come "hai finito." Quel messaggio, quando sollevato da PLSQL e non gestito dal programmatore PLSQL, viene interpretato come "una cosa brutta appena accaduta"

Sia PLSQL che SQL fanno effettivamente la stessa cosa Qui è il cliente che decide di fare qualcosa di diverso

Ora, se cambiamo la funzione per sollevare un'eccezione diversa:

CREATE OR REPLACE FUNCTION raise_exception RETURN INT 
IS 
BEGIN 
    RAISE ZERO_DIVIDE; 
END; 
/

Poi entrambi:

SELECT raise_exception 
FROM DUAL; 

e:

BEGIN 
    DBMS_OUTPUT.PUT_LINE(raise_exception); 
END; 
/

non sanno come gestire l'eccezione e termina con ORA-01476 divisor is equal to zero.

+0

Ok, il mio pensiero era inizialmente lo stesso - nessun_data_found viene gestito da qualche parte in silenzio. Questo spiegherebbe il funzionamento come selezione da SQL Developer per esempio. Ma non vedo il cambiamento di CLIENT tra select in e assignent quando entrambi vengono eseguiti da PLSQL ... O forse SELECT INTO richiama un interprete SQL separato che gestisce l'eccezione e che restituisce il risultato in PLSQL? –

+0

@ TomaszZieleśkiewicz L'assegnazione 'BEGIN v_x: = RAISE_EXCEPTION; END; 'è interamente interpretato in PL/SQL. Il codice 'BEGIN SELECT RAISE_EXCEPTION INTO v_x FROM DUAL; END; 'inizia a essere interpretato in PL/SQL e poi in context-switch quando sta analizzando l'istruzione' SELECT' interpretata in SQL - una volta che l'istruzione è stata analizzata le variabili impostate dalla clausola 'INTO' vengono passate tra gli ambiti indietro in PL/SQL. – MT0

+0

Ok, allora ha un senso. Ma devo ammettere che mi sembra ancora un po 'incoerente. ;) Grazie per la tua risposta, è stato molto utile. –

Problemi correlati