Pokud použijete přesné dotazy z otázky, pak 1. varianta samozřejmě pomaleji, protože musí počítat všechny záznamy v tabulce, která splňuje kritéria.
Musí být zapsán jako
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
nebo
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
protože pro váš účel postačí kontrola existence záznamu.
Obě varianty samozřejmě nezaručují, že někdo jiný v foo
něco nezmění mezi dvěma příkazy, ale není to problém, pokud je tato kontrola součástí složitějšího scénáře. Jen se zamyslete nad situací, kdy někdo změnil hodnotu foo.a
po výběru její hodnoty do var
při provádění některých akcí, které odkazují na vybraný var
hodnota. Takže ve složitých scénářích je lepší řešit takové problémy souběžnosti na úrovni aplikační logiky.
Pro provádění atomických operací je lepší použít jeden příkaz SQL.
Kterákoli z výše uvedených variant vyžaduje 2 kontextové přepínače mezi SQL a PL/SQL a 2 dotazy, takže v případech, kdy je v tabulce nalezen řádek, funguje pomaleji než jakákoli varianta popsaná níže.
Existují další varianty pro kontrolu existence řádku bez výjimky:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Pokud row_count =1, pak pouze jeden řádek splňuje kritéria.
Někdy stačí zkontrolovat pouze existenci kvůli jedinečnému omezení na foo
což zaručuje, že neexistují žádné duplicitní bar
hodnoty v foo
. Např. bar
je primární klíč.
V takových případech je možné dotaz zjednodušit:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
nebo použijte kurzor pro zpracování hodnot:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Další varianta je z odkaz , kterou poskytl @user272735 ve své odpovědi:
select
(select a from foo where bar = 123)
into var
from dual;
Z mé zkušenosti jakákoliv varianta bez výjimky blokuje ve většině případů rychleji než varianta s výjimkami, ale pokud je počet provedení takového bloku nízký, pak je lepší použít blok výjimky se zpracováním no_data_found
a too_many_rows
výjimky pro zlepšení čitelnosti kódu.
Správným bodem pro volbu použít výjimku nebo ji nepoužít je položit si otázku „Je tato situace pro aplikaci normální?“. Pokud řádek nebyl nalezen a jedná se o očekávanou situaci, kterou lze vyřešit (např. přidat nový řádek nebo vzít data z jiného místa a podobně), je lepší se výjimce vyhnout. Pokud je to neočekávané a neexistuje způsob, jak situaci napravit, pak zachyťte výjimku pro přizpůsobení chybové zprávy, zapište ji do protokolu událostí a znovu ji vyhoďte, nebo ji prostě nezachyťte vůbec.
Chcete-li porovnat výkon, udělejte na svém systému jednoduchý testovací případ s oběma variantami volanými mnohokrát a porovnejte.
Řekněte více, v 90 procentech aplikací je tato otázka spíše teoretická než praktická, protože existuje mnoho dalších zdrojů výkonu problémy, které je třeba vzít v úvahu jako první.
Aktualizovat
Zkopíroval jsem příklad z této stránky
na webu SQLFiddle s malými opravami (odkaz
).
Výsledky dokazují tuto variantu s výběrem z dual
funguje nejlépe:malá režie, když je většina dotazů úspěšná, a nejnižší snížení výkonu, když se zvýší počet chybějících řádků.
Překvapivě varianta s count() a dvěma dotazy vykazovala nejlepší výsledek v případě, že všechny dotazy selhaly.
| 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
Níže je uveden instalační kód pro testovací prostředí a testovací skript.
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 - zpracování výjimek
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 - kurzorová smyčka
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 - vyberte jako pole v select from dual
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 - check count() a pak získat hodnotu
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;
/
Testovací skript:
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
/