Si vous utilisez des requêtes exactes à partir de la question, la 1ère variante est bien sûr plus lente car elle doit compter tous les enregistrements de la table qui satisfont aux critères.
Il doit être écrit comme
SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;
ou
select 1 into row_count from dual where exists (select 1 from foo where bar = 123);
car la vérification de l'existence de l'enregistrement est suffisante pour votre objectif.
Bien sûr, les deux variantes ne garantissent pas que quelqu'un d'autre ne changera pas quelque chose dans foo
entre deux déclarations, mais ce n'est pas un problème si cette vérification fait partie d'un scénario plus complexe. Pensez à la situation où quelqu'un a changé la valeur de foo.a
après avoir sélectionné sa valeur dans var
lors de l'exécution de certaines actions qui font référence à la var
sélectionnée évaluer. Ainsi, dans des scénarios complexes, il est préférable de gérer ces problèmes de concurrence au niveau de la logique de l'application.
Pour effectuer des opérations atomiques, il est préférable d'utiliser une seule instruction SQL.
Chacune des variantes ci-dessus nécessite 2 changements de contexte entre SQL et PL/SQL et 2 requêtes donc s'exécute plus lentement que n'importe quelle variante décrite ci-dessous dans les cas où une ligne est trouvée dans une table.
Il existe d'autres variantes pour vérifier l'existence d'une ligne sans exception :
select max(a), count(1) into var, row_count
from foo
where bar = 123 and rownum < 3;
Si row_count =1 alors une seule ligne satisfait aux critères.
Parfois, il suffit de vérifier uniquement l'existence en raison de la contrainte unique sur le foo
qui garantit qu'il n'y a pas de bar
en double valeurs dans foo
. Par exemple. bar
est la clé primaire.
Dans de tels cas, il est possible de simplifier la requête :
select max(a) into var from foo where bar = 123;
if(var is not null) then
...
end if;
ou utilisez le curseur pour traiter les valeurs :
for cValueA in (
select a from foo where bar = 123
) loop
...
end loop;
La variante suivante provient de lien , fourni par @user272735 dans sa réponse :
select
(select a from foo where bar = 123)
into var
from dual;
D'après mon expérience, toute variante sans exception bloque dans la plupart des cas plus rapidement qu'une variante avec exceptions, mais si le nombre d'exécutions d'un tel bloc est faible, il est préférable d'utiliser un bloc d'exception avec la gestion de no_data_found
et too_many_rows
exceptions pour améliorer la lisibilité du code.
Le bon point pour choisir d'utiliser l'exception ou de ne pas l'utiliser, est de poser une question "Cette situation est-elle normale pour l'application?". Si la ligne n'est pas trouvée et qu'il s'agit d'une situation attendue qui peut être gérée (par exemple, ajouter une nouvelle ligne ou prendre des données à un autre endroit, etc.), il est préférable d'éviter les exceptions. Si c'est inattendu et qu'il n'y a aucun moyen de résoudre une situation, interceptez l'exception pour personnaliser le message d'erreur, écrivez-le dans le journal des événements et relancez-le, ou ne l'attrapez pas du tout.
Pour comparer les performances, faites simplement un cas de test simple sur votre système avec les deux variantes appelées plusieurs fois et comparez.
Dites plus, dans 90 % des applications, cette question est plus théorique que pratique car il existe de nombreuses autres sources de performances questions qui doivent être prises en compte en premier.
Mettre à jour
J'ai reproduit l'exemple de cette page
sur le site SQLFiddle avec quelques corrections (lien
).
Les résultats prouvent cette variante en sélectionnant parmi dual
fonctionne mieux :une légère surcharge lorsque la plupart des requêtes réussissent et une dégradation des performances la plus faible lorsque le nombre de lignes manquantes augmente.
Étonnamment, la variante avec count() et deux requêtes ont donné le meilleur résultat si toutes les requêtes échouaient.
| 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
Vous trouverez ci-dessous un code de configuration pour l'environnement de test et le script de 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 - gestion des exceptions
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 - boucle de curseur
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 - sélectionner comme champ dans sélectionner à partir du double
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 - vérifier count() puis obtenir la valeur
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;
/
Scénario de test :
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
/