Se utilizzi query esatte dalla domanda, la prima variante ovviamente è più lenta perché deve contare tutti i record nella tabella che soddisfa i criteri.
Deve essere scritto come
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
o
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
perché controllare l'esistenza dei record è sufficiente per il tuo scopo.
Ovviamente, entrambe le varianti non garantiscono che qualcun altro non modifichi qualcosa in foo
tra due affermazioni, ma non è un problema se questo controllo fa parte di uno scenario più complesso. Pensa solo alla situazione in cui qualcuno ha cambiato il valore di foo.a
dopo aver selezionato il suo valore in var
durante l'esecuzione di alcune azioni che fanno riferimento a var
selezionato valore. Quindi, in scenari complessi è meglio gestire tali problemi di concorrenza a livello di logica dell'applicazione.
Per eseguire operazioni atomiche è preferibile utilizzare una singola istruzione SQL.
Qualsiasi delle varianti precedenti richiede 2 cambi di contesto tra SQL e PL/SQL e 2 query, quindi ha prestazioni più lente rispetto a qualsiasi variante descritta di seguito nei casi in cui una riga viene trovata in una tabella.
Esistono altre varianti per verificare l'esistenza di una riga senza eccezioni:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Se row_count =1, solo una riga soddisfa i criteri.
A volte è sufficiente controllarne solo l'esistenza a causa del vincolo univoco sul foo
che garantisce che non ci siano bar
duplicate valori in foo
. Per esempio. bar
è la chiave primaria.
In questi casi è possibile semplificare la query:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
oppure usa il cursore per elaborare i valori:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
La prossima variante è da link , fornito da @user272735 nella sua risposta:
select
(select a from foo where bar = 123)
into var
from dual;
In base alla mia esperienza, qualsiasi variante senza blocchi di eccezioni nella maggior parte dei casi è più veloce di una variante con eccezioni, ma se il numero di esecuzioni di tale blocco è basso, è meglio utilizzare il blocco di eccezioni con la gestione di no_data_found
e too_many_rows
eccezioni per migliorare la leggibilità del codice.
Il punto giusto per scegliere se usare l'eccezione o non usarla, è porre una domanda "Questa situazione è normale per l'applicazione?". Se la riga non è stata trovata ed è una situazione prevista che può essere gestita (ad esempio aggiungere una nuova riga o prendere dati da un altro posto e così via) è meglio evitare eccezioni. Se è imprevisto e non c'è modo di risolvere una situazione, cattura l'eccezione per personalizzare il messaggio di errore, scrivilo nel registro eventi e lancialo di nuovo, o semplicemente non catturarlo affatto.
Per confrontare le prestazioni, fai un semplice test case sul tuo sistema con entrambe le varianti chiamate più volte e confronta.
Dì di più, nel 90% delle applicazioni questa domanda è più teorica che pratica perché ci sono molte altre fonti di prestazioni problemi che devono essere presi in considerazione per primi.
Aggiorna
Ho riprodotto un esempio da questa pagina
sul sito SQLFiddle con alcune correzioni (link
).
I risultati dimostrano quella variante selezionando da dual
offre prestazioni migliori:un po' di sovraccarico quando la maggior parte delle query riesce e un degrado delle prestazioni più basso quando aumenta il numero di righe mancanti.
Sorprendentemente la variante con count() e due query hanno mostrato il miglior risultato nel caso in cui tutte le query fallissero.
| FNAME | LOOP_COUNT | ALL_FAILED | ALL_SUCCEED | variant name |
----------------------------------------------------------------
| f1 | 2000 | 2.09 | 0.28 | exception |
| f2 | 2000 | 0.31 | 0.38 | cursor |
| f3 | 2000 | 0.26 | 0.27 | max() |
| f4 | 2000 | 0.23 | 0.28 | dual |
| f5 | 2000 | 0.22 | 0.58 | count() |
-- FNAME - tested function name
-- LOOP_COUNT - number of loops in one test run
-- ALL_FAILED - time in seconds if all tested rows missed from table
-- ALL_SUCCEED - time in seconds if all tested rows found in table
-- variant name - short name of tested variant
Di seguito è riportato un codice di configurazione per l'ambiente di test e lo script di test.
create table t_test(a, b)
as
select level,level from dual connect by level<=1e5
/
insert into t_test(a, b) select null, level from dual connect by level < 100
/
create unique index x_text on t_test(a)
/
create table timings(
fname varchar2(10),
loop_count number,
exec_time number
)
/
create table params(pstart number, pend number)
/
-- loop bounds
insert into params(pstart, pend) values(1, 2000)
/
-- f1 - gestione delle eccezioni
create or replace function f1(p in number) return number
as
res number;
begin
select b into res
from t_test t
where t.a=p and rownum = 1;
return res;
exception when no_data_found then
return null;
end;
/
-- f2 - ciclo del cursore
create or replace function f2(p in number) return number
as
res number;
begin
for rec in (select b from t_test t where t.a=p and rownum = 1) loop
res:=rec.b;
end loop;
return res;
end;
/
-- f3 - max()
create or replace function f3(p in number) return number
as
res number;
begin
select max(b) into res
from t_test t
where t.a=p and rownum = 1;
return res;
end;
/
-- f4 - seleziona come campo in seleziona da doppio
create or replace function f4(p in number) return number
as
res number;
begin
select
(select b from t_test t where t.a=p and rownum = 1)
into res
from dual;
return res;
end;
/
-- f5 - controlla count() quindi ottieni valore
create or replace function f5(p in number) return number
as
res number;
cnt number;
begin
select count(*) into cnt
from t_test t where t.a=p and rownum = 1;
if(cnt = 1) then
select b into res from t_test t where t.a=p;
end if;
return res;
end;
/
Script di prova:
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f1(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f1', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f2(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f2', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f3(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f3', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f4(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f4', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
declare
v integer;
v_start integer;
v_end integer;
vStartTime number;
begin
select pstart, pend into v_start, v_end from params;
--v_end := v_start + trunc((v_end-v_start)*2/3);
vStartTime := dbms_utility.get_cpu_time;
for i in v_start .. v_end loop
v:=f5(i);
end loop;
insert into timings(fname, loop_count, exec_time)
values ('f5', v_end-v_start+1, (dbms_utility.get_cpu_time - vStartTime)/100) ;
end;
/
select * from timings order by fname
/