Normalmente, eu apenas inseriria e interceptaria a exceção DUP_VAL_ON_INDEX, pois essa é a mais simples de codificar. Isso é mais eficiente do que verificar a existência antes de inserir. Eu não considero isso um "cheiro ruim" (frase horrível!) porque a exceção que tratamos é levantada pelo Oracle - não é como levantar suas próprias exceções como um mecanismo de controle de fluxo.
Graças ao comentário de Igor, agora executei dois benchmarks diferentes sobre isso:(1) onde todas as tentativas de inserção, exceto a primeira, são duplicadas, (2) onde todas as inserções não são duplicadas. A realidade estará em algum lugar entre os dois casos.
Nota:testes realizados no Oracle 10.2.0.3.0.
Caso 1:principalmente duplicatas
Parece que a abordagem mais eficiente (por um fator significativo) é verificar a existência AO inserir:
prompt 1) Check DUP_VAL_ON_INDEX
begin
for i in 1..1000 loop
begin
insert into hasviewed values(7782,20);
exception
when dup_val_on_index then
null;
end;
end loop
rollback;
end;
/
prompt 2) Test if row exists before inserting
declare
dummy integer;
begin
for i in 1..1000 loop
select count(*) into dummy
from hasviewed
where objectid=7782 and userid=20;
if dummy = 0 then
insert into hasviewed values(7782,20);
end if;
end loop;
rollback;
end;
/
prompt 3) Test if row exists while inserting
begin
for i in 1..1000 loop
insert into hasviewed
select 7782,20 from dual
where not exists (select null
from hasviewed
where objectid=7782 and userid=20);
end loop;
rollback;
end;
/
Resultados (depois de executar uma vez para evitar sobrecargas de análise):
1) Check DUP_VAL_ON_INDEX
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.54
2) Test if row exists before inserting
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.59
3) Test if row exists while inserting
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.20
Caso 2:sem duplicatas
prompt 1) Check DUP_VAL_ON_INDEX
begin
for i in 1..1000 loop
begin
insert into hasviewed values(7782,i);
exception
when dup_val_on_index then
null;
end;
end loop
rollback;
end;
/
prompt 2) Test if row exists before inserting
declare
dummy integer;
begin
for i in 1..1000 loop
select count(*) into dummy
from hasviewed
where objectid=7782 and userid=i;
if dummy = 0 then
insert into hasviewed values(7782,i);
end if;
end loop;
rollback;
end;
/
prompt 3) Test if row exists while inserting
begin
for i in 1..1000 loop
insert into hasviewed
select 7782,i from dual
where not exists (select null
from hasviewed
where objectid=7782 and userid=i);
end loop;
rollback;
end;
/
Resultados:
1) Check DUP_VAL_ON_INDEX
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.15
2) Test if row exists before inserting
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.76
3) Test if row exists while inserting
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.71
Neste caso, DUP_VAL_ON_INDEX ganha por uma milha. Observe que "selecionar antes de inserir" é o mais lento em ambos os casos.
Portanto, parece que você deve escolher a opção 1 ou 3 de acordo com a probabilidade relativa de as inserções serem ou não duplicadas.