Hvis du bruger nøjagtige forespørgsler fra spørgsmålet, så er 1. variant selvfølgelig langsommere, fordi den skal tælle alle poster i tabel, som opfylder kriterierne.
Det skal skrives som
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
eller
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
fordi det er nok til dit formål at kontrollere, om der er registreret eksistens.
Begge varianter garanterer naturligvis ikke, at en anden ikke ændrer noget i foo
mellem to udsagn, men det er ikke et problem, hvis denne kontrol er en del af mere komplekse scenarier. Tænk bare på situationen, hvor nogen ændrede værdien af foo.a
efter at have valgt dens værdi i var
mens du udfører nogle handlinger, der refererer til udvalgte var
værdi. Så i komplekse scenarier er det bedre at håndtere sådanne samtidighedsproblemer på applikationslogikniveau.
For at udføre atomoperationer er det bedre at bruge en enkelt SQL-sætning.
Enhver af varianterne ovenfor kræver 2 kontekstskift mellem SQL og PL/SQL og 2 forespørgsler, så den udfører langsommere end enhver variant beskrevet nedenfor i tilfælde, hvor rækken findes i en tabel.
Der er andre varianter til at kontrollere eksistensen af række uden undtagelse:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Hvis række_antal =1, opfylder kun én række kriterierne.
Nogle gange er det nok kun at tjekke for eksistensen på grund af en unik begrænsning på foo
som garanterer, at der ikke er nogen duplikerede bar
værdier i foo
. For eksempel. bar
er primær nøgle.
I sådanne tilfælde er det muligt at forenkle forespørgslen:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
eller brug markøren til at behandle værdier:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Næste variant er fra link , leveret af @user272735 i hans svar:
select
(select a from foo where bar = 123)
into var
from dual;
Fra min erfaring blokerer enhver variant uden undtagelse i de fleste tilfælde hurtigere end en variant med undtagelser, men hvis antallet af udførelser af en sådan blok er lavt, er det bedre at bruge undtagelsesblok med håndtering af no_data_found
og too_many_rows
undtagelser for at forbedre kodelæsbarheden.
Det rigtige punkt at vælge at bruge undtagelse eller ikke bruge det, er at stille et spørgsmål "Er denne situation normal for anvendelse?". Hvis rækken ikke findes, og det er en forventet situation, som kan håndteres (f.eks. tilføje ny række eller tage data fra et andet sted og så videre), er det bedre at undgå undtagelser. Hvis det er uventet, og der ikke er nogen måde at løse en situation på, så fang undtagelse for at tilpasse fejlmeddelelsen, skriv den til hændelsesloggen og smid den igen, eller fang den slet ikke.
For at sammenligne ydeevne skal du bare lave en simpel testcase på dit system, hvor begge varianter kaldes mange gange og sammenligne.
Sig mere, i 90 procent af applikationerne er dette spørgsmål mere teoretisk end praktisk, fordi der er mange andre kilder til ydeevne spørgsmål, som skal tages i betragtning først.
Opdater
Jeg har gengivet eksempel fra denne side
på SQLFiddle-webstedet med lidt rettelser (link
).
Resultater beviser den variant med at vælge fra dual
yder bedst:lidt overhead, når de fleste forespørgsler lykkes og laveste ydeevneforringelse, når antallet af manglende rækker øges.
Overraskende nok viste variant med count() og to forespørgsler det bedste resultat i tilfælde af, at alle forespørgsler mislykkedes.
| 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
Nedenfor er en opsætningskode for testmiljø og testscript.
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 - håndtering af undtagelser
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 - markørsløjfe
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 - vælg som felt i vælg fra dobbelt
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 - tjek count() og få derefter værdi
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;
/
Testscript:
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
/