Oracle
 sql >> Base de données >  >> RDS >> Oracle

L'utilisation de SELECT COUNT(*) avant SELECT INTO est-elle plus lente que l'utilisation d'exceptions ?

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
/