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

Como o Oracle processa chamadas de função armazenadas no SQL?


É uma pergunta muito boa.

Eu tentei primeiro criar tabela e inserir dados de exemplo (apenas cinco linhas):
create table my_table(value number);
insert into my_table(value) values(1);
insert into my_table(value) values(2);
insert into my_table(value) values(3);
insert into my_table(value) values(4);
insert into my_table(value) values(5);

Eu fiz um pacote de teste simples para testar isso.
create or replace package my_package is
  g_counter_SELECT PLS_INTEGER := 0; -- counter for SELECT statement
  g_counter_WHERE  PLS_INTEGER := 0; -- counter for WHERE clause
  function my_function(number_in in number, type_in in varchar2) return number;
  procedure reset_counter;
end;
/

E corpo...
create or replace package body my_package is
  function my_function(number_in in number, type_in in varchar2) return number is
  begin
    IF(type_in = 'SELECT') THEN
        g_counter_SELECT := g_counter_SELECT + 1;
    ELSIF(type_in = 'WHERE') THEN
        g_counter_WHERE := g_counter_WHERE + 1;
    END IF;
    return mod(number_in, 2);
  end;
  procedure reset_counter is
  begin
    g_counter_SELECT := 0;
    g_counter_WHERE := 0;
  end;
end;
/

Agora, podemos executar o teste no Oracle 9i (no 11g são os mesmos resultados):
-- reset counter
exec my_package.reset_counter();

-- run query
select t.value, my_package.my_function(t.value, 'SELECT')
  from my_table t
 where my_package.my_function(t.value, 'WHERE') = 1;

-- print result
exec dbms_output.put_line('Count (SELECT) = ' || my_package.g_counter_SELECT);
exec dbms_output.put_line('Count (WHERE) = ' || my_package.g_counter_WHERE);

Resultado é:
DBMS Output (Session: [1] [email protected] at: 08.09.2010 01:50:04): 
-----------------------------------------------------------------------
Count (SELECT) = 3
Count (WHERE) = 5

Aqui está a tabela de planos:
--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
--------------------------------------------------------------------

O que significa que a função (em WHERE calues) é chamada para cada linha da tabela (no caso de FULL TABLE SCAN). Na instrução SELECT é lançada tantas vezes em conformidade com a condição WHERE my_function =1

Agora... teste sua segunda consulta (mesmos resultados no Oracle9i e 11g)

Resultado é:
DBMS Output (Session: [1] [email protected] at: 08.09.2010 02:08:04): 
-----------------------------------------------------------------------
Count (SELECT) = 8
Count (WHERE) = 0

Explique a aparência simples assim (para o modo de otimizador CHOOSE):
--------------------------------------------------------------------
| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
--------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |       |       |       |
|*  1 |  TABLE ACCESS FULL   | MY_TABLE    |       |       |       |
--------------------------------------------------------------------

A PERGUNTA É:Por que contar (SELECT) =8?

Como o Oracle primeiro executa a subconsulta (no meu caso com FULL TABLE SCAN, são 5 linhas =5 chamadas my_function na instrução SELECT):
select t.value, my_package.my_function(t.value, 'SELECT') func_value from my_table t

E para esta visão (subconsulta é como visão) execute 3 vezes (devido à condição onde subquery.func_value =1) novamente chame a função my_function.

Pessoalmente não recomendo usar função na cláusula WHERE, mas admito que às vezes isso é inevitável.

Como o pior exemplo possível disso é ilustrado pelo seguinte:
select t.value, my_package.my_function(t.value, 'SELECT')
  from my_table t
 where my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE')
   and my_package.my_function(t.value, 'WHERE') = my_package.my_function(t.value, 'WHERE');

Onde está o resultado no Oracle 9i :
Count (SELECT) = 5
Count (WHERE) = 50

E no Oracle 11g é :
Count (SELECT) = 5
Count (WHERE) = 5

O que neste caso mostra que algumas vezes o uso de funções pode ser crítico para o desempenho. Em outros casos (11g) resolve o próprio banco de dados.