sql >> Base de Datos >  >> RDS >> Oracle

¿El uso de SELECT COUNT(*) antes de SELECT INTO es más lento que el uso de Excepciones?

Si usa consultas exactas de la pregunta, entonces la primera variante, por supuesto, es más lenta porque debe contar todos los registros en la tabla que satisface los criterios.

Debe escribirse como

SELECT COUNT(*) INTO row_count FROM foo WHERE bar = 123 and rownum = 1;

o

select 1 into row_count from dual where exists (select 1 from foo where bar = 123);

porque verificar la existencia de registros es suficiente para su propósito.

Por supuesto, ambas variantes no garantizan que otra persona no cambie algo en foo entre dos declaraciones, pero no es un problema si esta verificación es parte de un escenario más complejo. Solo piense en la situación en la que alguien cambió el valor de foo.a después de seleccionar su valor en var mientras realiza algunas acciones que se refieren a var seleccionado valor. Por lo tanto, en escenarios complejos, es mejor manejar tales problemas de concurrencia en el nivel de lógica de la aplicación.
Para realizar operaciones atómicas, es mejor usar una declaración SQL única.

Cualquiera de las variantes anteriores requiere 2 cambios de contexto entre SQL y PL/SQL y 2 consultas, por lo que funciona más lento que cualquier variante descrita a continuación en los casos en que se encuentra una fila en una tabla.

Hay otras variantes para verificar la existencia de una fila sin excepción:

select max(a), count(1) into var, row_count 
from foo 
where bar = 123 and rownum < 3;

Si row_count =1, solo una fila cumple los criterios.

En algún momento, es suficiente verificar solo la existencia debido a la restricción única en el foo lo que garantiza que no hay bar duplicadas valores en foo . P.ej. bar es la clave principal.
En tales casos, es posible simplificar la consulta:

select max(a) into var from foo where bar = 123;
if(var is not null) then 
  ...
end if;

o use el cursor para procesar valores:

for cValueA in ( 
  select a from foo where bar = 123
) loop
  ...  
end loop;

La siguiente variante es de enlace , proporcionado por @user272735 en su respuesta:

select 
  (select a from foo where bar = 123)
  into var 
from dual;

Según mi experiencia, cualquier variante sin bloques de excepción en la mayoría de los casos es más rápida que una variante con excepciones, pero si el número de ejecuciones de dicho bloque es bajo, entonces es mejor usar el bloque de excepción con manejo de no_data_found y too_many_rows excepciones para mejorar la legibilidad del código.

El punto correcto para elegir usar una excepción o no usarla es hacer una pregunta "¿Esta situación es normal para la aplicación?". Si no se encuentra la fila y es una situación esperada que se puede manejar (por ejemplo, agregar una nueva fila o tomar datos de otro lugar, etc.), es mejor evitar la excepción. Si es inesperado y no hay forma de solucionar una situación, entonces detecte la excepción para personalizar el mensaje de error, escríbalo en el registro de eventos y vuelva a generarlo, o simplemente no lo detecte en absoluto.

Para comparar el rendimiento, simplemente haga un caso de prueba simple en su sistema con ambas variantes llamadas muchas veces y compare. Cuestiones que deben tenerse en cuenta en primer lugar.

Actualizar

Reproduje un ejemplo de esta página en el sitio de SQLFiddle con algunas correcciones (enlace ).
Los resultados prueban esa variante con la selección de dual funciona mejor:un poco de sobrecarga cuando la mayoría de las consultas tienen éxito y la menor degradación del rendimiento cuando aumenta el número de filas faltantes.
Sorprendentemente, la variante con count() y dos consultas mostraron el mejor resultado en caso de que todas las consultas fallaran.

| 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

A continuación se muestra un código de configuración para el entorno de prueba y el script de prueba.

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 - manejo de excepciones

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 - bucle de cursor

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 - máx()

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 - seleccionar como campo en select from dual

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 - verifique el conteo () y luego obtenga el valor

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;
/

Guión de prueba:

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
/