Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

O uso de SELECT COUNT(*) antes de SELECT INTO é mais lento do que o uso de Exceptions?


Se você usar consultas exatas da pergunta, a 1ª variante é mais lenta, porque deve contar todos os registros na tabela que satisfaçam os critérios.

Deve ser escrito como
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);

porque verificar a existência do registro é suficiente para o seu propósito.

Obviamente, ambas as variantes não garantem que outra pessoa não altere algo em foo entre duas instruções, mas não é um problema se essa verificação for parte de um cenário mais complexo. Basta pensar na situação em que alguém alterou o valor de foo.a depois de selecionar seu valor em var ao executar algumas ações que se referem ao var selecionado valor. Portanto, em cenários complexos, é melhor lidar com esses problemas de simultaneidade no nível lógico do aplicativo.
Para executar operações atômicas, é melhor usar uma única instrução SQL.

Qualquer uma das variantes acima requer 2 alternâncias de contexto entre SQL e PL/SQL e 2 consultas, portanto, executa mais lentamente do que qualquer variante descrita abaixo nos casos em que a linha é encontrada em uma tabela.

Existem outras variantes para verificar a existência de linha sem exceção:
select max(a), count(1) into var, row_count 
from foo 
where bar = 123 and rownum < 3;

Se row_count =1, apenas uma linha satisfaz os critérios.

Às vezes, é suficiente verificar apenas a existência devido à restrição exclusiva no foo que garante que não há bar duplicado valores em foo . Por exemplo. bar é chave primária.
Em tais casos é possível simplificar a consulta:
select max(a) into var from foo where bar = 123;
if(var is not null) then 
  ...
end if;

ou use o cursor para processar valores:
for cValueA in ( 
  select a from foo where bar = 123
) loop
  ...  
end loop;

A próxima variante é de link , fornecido por @ user272735 em sua resposta:
select 
  (select a from foo where bar = 123)
  into var 
from dual;

Da minha experiência, qualquer variante sem blocos de exceção na maioria dos casos mais rápido que uma variante com exceções, mas se o número de execuções desse bloco for baixo, é melhor usar o bloco de exceção com manipulação de no_data_found e too_many_rows exceções para melhorar a legibilidade do código.

O ponto certo para optar por usar exceção ou não usá-la, é fazer uma pergunta "Esta situação é normal para aplicação?". Se a linha não for encontrada e for uma situação esperada que possa ser tratada (por exemplo, adicionar nova linha ou obter dados de outro local e assim por diante) é melhor evitar exceções. Se for inesperado e não houver como corrigir uma situação, capture a exceção para personalizar a mensagem de erro, grave-a no log de eventos e jogue novamente ou simplesmente não a capture.

Para comparar o desempenho, basta fazer um caso de teste simples em seu sistema com ambas as variantes chamadas muitas vezes e comparar.
Diga mais, em 90% das aplicações esta questão é mais teórica do que prática porque existem muitas outras fontes de desempenho questões que devem ser consideradas em primeiro lugar.

Atualizar

Reproduzi o exemplo esta página no site SQLFiddle com algumas correções (link ).
Os resultados comprovam essa variante com a seleção de dual tem melhor desempenho:um pouco de sobrecarga quando a maioria das consultas é bem-sucedida e menor degradação de desempenho quando o número de linhas ausentes aumenta.
Surpreendentemente variante com count() e duas consultas mostraram o melhor resultado no caso de todas as consultas falharem.
| 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

Abaixo está um código de configuração para ambiente de teste e script de teste.
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 - manipulação de exceção
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 - ciclo do 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 - 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 - selecione como campo em selecionar de 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 a contagem () e obtenha o 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;
/

Roteiro de teste:
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
/