Om du använder exakta frågor från frågan så är den första varianten naturligtvis långsammare eftersom den måste räkna alla poster i tabellen som uppfyller kriterierna.
Det måste skrivas 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);
eftersom det räcker för ditt syfte att kontrollera att det finns rekord.
Båda varianterna garanterar naturligtvis inte att någon annan inte ändrar något i foo
mellan två påståenden, men det är inte ett problem om denna kontroll är en del av ett mer komplext scenario. Tänk bara på situationen när någon ändrade värdet på foo.a
efter att ha valt dess värde i var
medan du utför några åtgärder som refererar till valda var
värde. Så i komplexa scenarier är det bättre att hantera sådana samtidiga problem på applikationslogiknivå.
För att utföra atomära operationer är det bättre att använda en enda SQL-sats.
Alla varianter ovan kräver 2 kontextväxlar mellan SQL och PL/SQL och 2 frågor så fungerar långsammare än alla varianter som beskrivs nedan i fall då rad hittas i en tabell.
Det finns andra varianter för att kontrollera förekomsten av rad utan undantag:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Om rad_antal =1 så uppfyller endast en rad kriterierna.
Ibland räcker det att bara kontrollera om det finns på grund av unika begränsningar på foo
vilket garanterar att det inte finns några duplicerade bar
värden i foo
. T.ex. bar
är primärnyckel.
I sådana fall är det möjligt att förenkla frågan:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
eller använd markören för att bearbeta värden:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
Nästa variant kommer från länk , tillhandahållen av @user272735 i hans svar:
select
(select a from foo where bar = 123)
into var
from dual;
Enligt min erfarenhet blockerar alla varianter utan undantag i de flesta fall snabbare än en variant med undantag, men om antalet exekveringar av ett sådant block är lågt är det bättre att använda undantagsblock med hantering av no_data_found
och too_many_rows
undantag för att förbättra kodläsbarheten.
Rätt punkt att välja att använda undantag eller inte använda det, är att ställa en fråga "Är den här situationen normal för tillämpning?". Om raden inte hittas och det är en förväntad situation som kan hanteras (t.ex. lägg till ny rad eller ta data från en annan plats och så vidare) är det bättre att undvika undantag. Om det är oväntat och det inte finns något sätt att åtgärda en situation, fånga undantag för att anpassa felmeddelandet, skriv det till händelseloggen och kasta igen, eller bara fånga det inte alls.
För att jämföra prestanda, gör bara ett enkelt testfall på ditt system där båda varianterna kallas många gånger och jämför.
Säg mer, i 90 procent av applikationerna är denna fråga mer teoretisk än praktisk eftersom det finns många andra källor till prestanda frågor som måste beaktas först.
Uppdatera
Jag återgav exempel från denna sida
på SQLFiddle-webbplatsen med lite korrigeringar (länk
).
Resultaten bevisar den varianten med att välja från dual
presterar bäst:lite overhead när de flesta frågor lyckas och lägsta prestandaförsämring när antalet saknade rader ökar.
Overraskande variant med count() och två frågor visade bäst resultat om alla frågor misslyckades.
| 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
Nedan finns en inställningskod för testmiljö och testskript.
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 - undantagshantering
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örslinga
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älj som fält i välj från dubbel
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 - kontrollera count() och få sedan värde
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;
/
Testskript:
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
/