Oracle Ref Cursor Vs Seleziona in con Gestione eccezioni
Domanda
Ho un paio di scenari:
-
È necessario leggere il valore di una colonna da tre diverse tabelle in un ordine predefinito e solo 1 tabella avrà i dati
-
Leggi i dati dalla tabella1 se sono presenti record per i criteri forniti altrimenti leggi i dati dalla tabella 2 per i criteri indicati
In Oracle Stored Procedures
Il modo in cui questi vengono gestiti in questo momento è innanzitutto ottenere il conteggio per una determinata query in una variabile e se il conteggio > 0, quindi eseguiamo la stessa query per leggere i dati effettivi come in:
select count(*) from table1 into v_count
if v_count > 0
then
select data into v_data from table1
end if;
Return v_data
Questo viene fatto per evitare l'eccezione no_data_found, altrimenti avrei bisogno di tre blocchi del gestore eccezioni per catturare l'eccezione no_data_found per ogni accesso alla tabella.
Attualmente sto reimplementando questo con i cursori in modo da avere qualcosa del genere:
cursor C1 is
select data from table1;
Open C1
Fetch C1 into v_data
if C1%FOUND
then
Close C1
Return v_data
End If
Volevo scoprire quale è meglio dal punto di vista delle prestazioni: quello con i cursori o quello che fa una selezione in una variabile e ha tre blocchi di eccezioni no_data_found. Non voglio utilizzare il processo di query in due fasi che abbiamo attualmente.
Soluzione
Non so perché sei così desideroso di evitare l'eccezione? Cosa c'è di sbagliato in:
begin
begin
select data into v_data from table1;
exception
when no_data_found then
begin
select data into v_data from table2;
exception
when no_data_found then
begin
select data into v_data from table3;
exception
when no_data_found then
v_data := null;
end;
end;
end;
return v_data;
end;
Credo che questo funzionerà meglio dell'altra soluzione perché fa il minimo lavoro possibile per ottenere il risultato desiderato.
Vedi Quanto è grave ignorare l'eccezione Oracle DUP_VAL_ON_INDEX? dove I dimostrare che l'utilizzo delle eccezioni funziona meglio del conteggio per vedere se ci sono dati.
Altri suggerimenti
select count(*) from table1 into v_count
if v_count > 0 then
select data into v_data from table1;
else
v_data := null;
end if;
return v_data;
NON è equivalente a
begin
select data into v_data from table1;
return v_data;
exception
when no_data_found then
return null;
end;
in un ambiente multiutente. Nel primo caso, qualcuno potrebbe aggiornare la tabella tra i punti in cui si verifica l'esistenza e quando si leggono i dati.
Dal punto di vista delle prestazioni, non ho idea di quale sia il migliore, ma so che la prima opzione fa due cambi di contesto al motore sql e il secondo fa solo un cambio di contesto.
Il modo in cui stai gestendo lo scenario 1 ora non è buono. Non solo stai eseguendo due query quando una sarà sufficiente, ma come ha sottolineato Erik, si apre la possibilità di cambiare i dati tra le due query (a meno che non si utilizzi una transazione di sola lettura o serializzabile).
Dato che dici che in questo caso i dati saranno esattamente in una delle tre tabelle, che ne dici di questo?
SELECT data
INTO v_data FROM
(SELECT data FROM table1
UNION ALL
SELECT data FROM table2
UNION ALL
SELECT data FROM table3
)
Un altro "trucco" puoi usare per evitare di scrivere più gestori senza dati trovati:
SELECT MIN(data) INTO v_data FROM table1;
IF v_data IS NOT NULL THEN
return v_data;
END IF;
SELECT MIN(data) INTO v_data FROM table2;
...etc...
ma non vedo davvero alcun motivo che sia meglio che avere tre gestori di eccezioni.
Per il tuo secondo scenario, penso che intendi dire che potrebbero esserci dei dati in entrambe le tabelle e che vuoi usare i dati della tabella1 se presenti, altrimenti usa i dati della tabella 2. Ancora una volta potresti farlo in un singolo query:
SELECT data
INTO v_data FROM
(SELECT data FROM
(SELECT 1 sort_key, data FROM table1
UNION ALL
SELECT 2 sort_key, data FROM table2
)
ORDER BY sort_key ASC
)
WHERE ROWNUM = 1
Una versione migliorata dell'opzione MIN di "Dave Costa" ...
SELECT COUNT(1), MIN(data) INTO v_rowcount, v_data FROM table2;
Ora v_rowcount
può essere verificato per i valori 0, > 1 (maggiore di 1) in cui la query di selezione normale genererà NO_DATA_FOUND
o TOO_MANY_ROWS
eccezione. Valore "1" indicherà che esiste una riga esatta e servirà al nostro scopo.
DECLARE
A VARCHAR(35);
B VARCHAR(35);
BEGIN
WITH t AS
(SELECT OM_MARCA, MAGAZIA FROM ifsapp.AKER_EFECTE_STOC WHERE (BARCODE = 1000000491009))
SELECT
(SELECT OM_MARCA FROM t) OM_MARCA,
(SELECT MAGAZIA FROM t) MAGAZIA
INTO A, B
FROM DUAL;
IF A IS NULL THEN
dbms_output.put_line('A este null');
END IF;
dbms_output.put_line(A);
dbms_output.put_line(B);
END;
/
Utilizza " per riga nel cursore " forma di un ciclo e il ciclo non verrà elaborato se non ci sono dati:
declare cursor
t1Cur is
select ... from table1;
t2Cur is
select ... from table2;
t3Cur is
select ... from table3;
t1Flag boolean FALSE;
t2Flag boolean FALSE;
t3Flag boolean FALSE;
begin
for t1Row in t1Cur loop
... processing, set t1Flag = TRUE
end loop;
for t2Row in t2Cur loop
... processing, set t2Flag = TRUE
end loop;
for t3Row in t3Cur loop
... processing, set t3Flag = TRUE
end loop;
... conditional processing based on flags
end;