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
/