Als u exacte zoekopdrachten uit de vraag gebruikt, dan is de 1e variant natuurlijk langzamer omdat deze alle records in de tabel moet tellen die aan de criteria voldoen.
Het moet worden geschreven als
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
of
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
omdat controleren op het bestaan van records voldoende is voor uw doel.
Beide varianten garanderen natuurlijk niet dat iemand anders iets niet verandert in foo
tussen twee instructies, maar het is geen probleem als deze controle deel uitmaakt van een complexer scenario. Denk maar aan de situatie waarin iemand de waarde van foo.a
veranderde na het selecteren van de waarde in var
tijdens het uitvoeren van enkele acties die verwijzen naar geselecteerde var
waarde. Dus in complexe scenario's is het beter om dergelijke gelijktijdigheidsproblemen op applicatielogica-niveau af te handelen.
Atomaire bewerkingen uitvoeren is beter om een enkele SQL-instructie te gebruiken.
Elk van bovenstaande varianten vereist 2 contextwisselingen tussen SQL en PL/SQL en 2 query's, dus presteert langzamer dan elke variant die hieronder wordt beschreven in gevallen waarin een rij in een tabel wordt gevonden.
Er zijn nog andere varianten om het bestaan van een rij zonder uitzondering te controleren:
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Als row_count =1 dan voldoet slechts één rij aan de criteria.
Soms is het voldoende om alleen op het bestaan te controleren vanwege de unieke beperking op de foo
wat garandeert dat er geen dubbele bar
. is waarden in foo
. bijv. bar
is de primaire sleutel.
In dergelijke gevallen is het mogelijk om de zoekopdracht te vereenvoudigen:
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
of gebruik de cursor voor het verwerken van waarden:
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
De volgende variant is van link , geleverd door @user272735 in zijn antwoord:
select
(select a from foo where bar = 123)
into var
from dual;
Vanuit mijn ervaring blokkeert elke variant zonder uitzondering in de meeste gevallen sneller dan een variant met uitzonderingen, maar als het aantal uitvoeringen van zo'n blok laag is, is het beter om een uitzonderingsblok te gebruiken met afhandeling van no_data_found
en too_many_rows
uitzonderingen om de leesbaarheid van de code te verbeteren.
Het juiste punt om ervoor te kiezen om uitzondering te gebruiken of niet, is om een vraag te stellen "Is deze situatie normaal voor toepassing?". Als de rij niet wordt gevonden en het is een verwachte situatie die kan worden afgehandeld (bijvoorbeeld een nieuwe rij toevoegen of gegevens van een andere plaats nemen, enzovoort), is het beter om uitzonderingen te voorkomen. Als het onverwacht is en er geen manier is om een situatie op te lossen, vang dan een uitzondering om het foutbericht aan te passen, schrijf het naar het gebeurtenislogboek en gooi het opnieuw, of vang het gewoon helemaal niet.
Om de prestaties te vergelijken, maakt u gewoon een eenvoudige testcase van uw systeem, waarbij beide varianten vele malen worden aangeroepen en vergelijkt. zaken waarmee eerst rekening moet worden gehouden.
Bijwerken
Ik heb een voorbeeld gereproduceerd van deze pagina
op de SQLFiddle-site met een kleine correctie (link
).
Resultaten bewijzen die variant met het selecteren uit dual
presteert het beste:een beetje overhead wanneer de meeste zoekopdrachten slagen en de laagste prestatievermindering wanneer het aantal ontbrekende rijen toeneemt.
Verrassend is dat de variant met count() en twee zoekopdrachten het beste resultaat liet zien in het geval dat alle zoekopdrachten mislukten.
| 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
Hieronder vindt u een installatiecode voor de testomgeving en het 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 - afhandeling van uitzonderingen
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 - cursorlus
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 - selecteer als veld in selecteer uit 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 - controleer count() en krijg waarde
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
/