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

Entendendo as diferenças entre APIs de Tabela e de Transação


Vamos começar com a API de tabela. Essa é a prática de mediar o acesso a tabelas por meio de uma API PL/SQL. Assim, temos um pacote por tabela, que deve ser gerado a partir do dicionário de dados. O pacote apresenta um conjunto padrão de procedimentos para emissão de DML em relação à tabela e algumas funções para recuperação de dados.

Por comparação, uma API Transacional representa uma Unidade de Trabalho. Ele não expõe nenhuma informação sobre os objetos de banco de dados subjacentes. APIs transacionais oferecem melhor encapsulamento e uma interface mais limpa.

O contraste é assim. Considere estas regras de negócios para criar um novo departamento:
  1. O novo departamento deve ter um nome e um local
  2. O novo Departamento deve ter um gerente, que deve ser um Funcionário existente
  3. Outros funcionários existentes podem ser transferidos para o novo departamento
  4. Novos funcionários podem ser designados para o novo departamento
  5. O novo Departamento deve ter pelo menos dois Funcionários designados (incluindo o gerente)

Usando APIs de tabela, a transação pode ser algo assim:
DECLARE
    dno pls_integer;
    emp_count pls_integer;
BEGIN
    dept_utils.insert_one_rec(:new_name, :new_loc, dno);
    emp_utils.update_one_rec(:new_mgr_no ,p_job=>'MGR’ ,p_deptno=>dno);
    emp_utils.update_multi_recs(:transfer_emp_array, p_deptno=>dno);
    FOR idx IN :new_hires_array.FIRST..:new_hires_array.LAST LOOP
        :new_hires_array(idx).deptno := dno;
    END LOOP;
    emp_utils.insert_multi_recs(:new_hires_array);
    emp_count := emp_utils.get_count(p_deptno=>dno); 
    IF emp_count < 2 THEN
        raise_application_error(-20000, ‘Not enough employees’);
    END IF;
END;
/

Considerando que com uma API Transacional é muito mais simples:
DECLARE
    dno subtype_pkg.deptno;
BEGIN
    dept_txns.create_new_dept(:new_name
                                , :new_loc
                                , :new_mgr_no
                                , :transfer_emps_array
                                , :new_hires_array
                                , dno);
END;
/

Então, por que a diferença na recuperação de dados? Como a abordagem da API Transacional desencoraja o get() genérico funções para evitar o uso insensato de instruções SELECT ineficientes.

Por exemplo, se você quer apenas o salário e comissão de um Funcionário, consultando isso...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;

... é melhor do que executar isso ...
l_emprec := emp_utils.get_whole_row(p_eno);

...especialmente se o registro Employee tiver colunas LOB.

Também é mais eficiente do que:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);

... se cada um desses getters executar uma instrução SELECT separada. O que não é desconhecido:é uma má prática OO que leva a um desempenho horrível do banco de dados.

Os proponentes das APIs de tabela argumentam a seu favor com base no fato de que elas protegem o desenvolvedor da necessidade de pensar em SQL. As pessoas que as desaprovam não gostam das APIs de tabela pelo mesmo motivo . Mesmo as melhores APIs de Tabela tendem a encorajar o processamento RBAR. Se escrevermos nosso próprio SQL a cada vez, é mais provável que escolhamos uma abordagem baseada em conjuntos.

Usar APIs transacionais não exclui necessariamente o uso de get_resultset() funções. Ainda há muito valor em uma API de consulta. Mas é mais provável que seja construído a partir de exibições e funções que implementam junções do que SELECTs em tabelas individuais.

Aliás, acho que construir APIs transacionais em cima de APIs de tabela não é uma boa ideia:ainda temos instruções SQL em silos em vez de junções cuidadosamente escritas.

Como ilustração, aqui estão duas implementações diferentes de uma API transacional para atualizar o salário de cada funcionário em uma região (a região é uma seção de grande escala da organização; os departamentos são atribuídos às regiões).

A primeira versão não tem SQL puro, apenas chamadas de API de tabela, não acho que isso seja um espantalho:ele usa o tipo de funcionalidade que vi nos pacotes de API de tabela (embora alguns usem SQL dinâmico em vez de procedimentos SET_XXX() nomeados) .
create or replace procedure adjust_sal_by_region
    (p_region in dept.region%type
           , p_sal_adjustment in number )
as
    emps_rc sys_refcursor;
    emp_rec emp%rowtype;
    depts_rc sys_refcursor;
    dept_rec dept%rowtype;
begin
    depts_rc := dept_utils.get_depts_by_region(p_region);

    << depts >>
    loop
        fetch depts_rc into dept_rec;
        exit when depts_rc%notfound;
        emps_rc := emp_utils.get_emps_by_dept(dept_rec.deptno);

        << emps >>
        loop
            fetch emps_rc into emp_rec;
            exit when emps_rc%notfound;
            emp_rec.sal := emp_rec.sal * p_sal_adjustment;
            emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
        end loop emps;

    end loop depts;

end adjust_sal_by_region;
/

A implementação equivalente em SQL:
create or replace procedure adjust_sal_by_region
    (p_region in dept.region%type
           , p_sal_adjustment in number )
as
begin
    update emp e
    set e.sal = e.sal * p_sal_adjustment
    where e.deptno in ( select d.deptno 
                        from dept d
                        where d.region = p_region );
end adjust_sal_by_region;
/

Isso é muito melhor do que os loops de cursor aninhados e a atualização de linha única da versão anterior. Isso ocorre porque no SQL é fácil escrever a junção que precisamos selecionar Funcionários por Região. É muito mais difícil usar uma API de tabela, porque a região não é uma chave de funcionários.

Para ser justo, se tivermos uma API de tabela que suporte SQL dinâmico, as coisas são melhores, mas ainda não são ideais:
create or replace procedure adjust_sal_by_region
    (p_region in dept.region%type
           , p_sal_adjustment in number )
as
    emps_rc sys_refcursor;
    emp_rec emp%rowtype;
begin
    emps_rc := emp_utils.get_all_emps(
                    p_where_clause=>'deptno in ( select d.deptno 
                        from dept d where d.region = '||p_region||' )' );

    << emps >>
    loop
        fetch emps_rc into emp_rec;
        exit when emps_rc%notfound;
        emp_rec.sal := emp_rec.sal * p_sal_adjustment;
        emp_utils.set_sal(emp_rec.empno, emp_rec.sal);
    end loop emps;

end adjust_sal_by_region;
/

última palavra

Dito tudo isso, existem cenários em que as APIs de tabela podem ser úteis, situações em que queremos interagir apenas com tabelas únicas de maneiras bastante padrão. Um caso óbvio pode ser produzir ou consumir feeds de dados de outros sistemas, por exemplo. ETL.

Se você quiser investigar o uso de APIs de tabela, o melhor lugar para começar é o Quest CodeGen Utility de Steven Feuerstein (anteriormente QNXO). Isso é tão bom quanto os geradores TAPI, e é grátis.