sql >> Base de Datos >  >> RDS >> Oracle

Comprender las diferencias entre las API de tablas y transacciones

Comencemos con Table API. Esta es la práctica de mediar el acceso a las tablas a través de una API PL/SQL. Entonces, tenemos un paquete por tabla, que debe generarse a partir del diccionario de datos. El paquete presenta un conjunto estándar de procedimientos para emitir DML contra la tabla y algunas funciones para recuperar datos.

En comparación, una API transaccional representa una unidad de trabajo. No expone ninguna información sobre los objetos de base de datos subyacentes en absoluto. Las API transaccionales ofrecen una mejor encapsulación y una interfaz más limpia.

El contraste es así. Considere estas reglas comerciales para crear un nuevo Departamento:

  1. El nuevo Departamento debe tener un Nombre y una Ubicación
  2. El nuevo departamento debe tener un gerente, que debe ser un empleado existente
  3. Otros Empleados existentes pueden ser transferidos al nuevo Departamento
  4. Se pueden asignar nuevos empleados al nuevo Departamento
  5. El nuevo Departamento debe tener asignados al menos dos Empleados (incluido el gerente)

Usando Table API, la transacción podría verse así:

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;
/

Mientras que con una API transaccional es mucho más simple:

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;
/

Entonces, ¿por qué la diferencia en la recuperación de datos? Porque el enfoque de la API transaccional desalienta el get() genérico funciones para evitar el uso sin sentido de declaraciones SELECT ineficientes.

Por ejemplo, si solo desea el salario y la comisión de un empleado, consultando esto...

select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;

... es mejor que ejecutar esto ...

l_emprec := emp_utils.get_whole_row(p_eno);

...especialmente si el registro de Empleado tiene columnas LOB.

También es más eficiente que:

l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);

... si cada uno de esos getters ejecuta una declaración SELECT separada. Lo cual no es desconocido:es una mala práctica de OO que conduce a un rendimiento horrible de la base de datos.

Los defensores de las Table API argumentan a favor de ellas sobre la base de que protegen al desarrollador de la necesidad de pensar en SQL. A las personas que las desaprueban no les gustan las API de tabla por la misma razón . Incluso las mejores Table API tienden a fomentar el procesamiento RBAR. Si escribimos nuestro propio SQL cada vez, es más probable que elijamos un enfoque basado en conjuntos.

El uso de API transaccionales no necesariamente descarta el uso de get_resultset() funciones Todavía hay mucho valor en una API de consulta. Pero es más probable que se construya a partir de vistas y funciones que implementen uniones que SELECT en tablas individuales.

Por cierto, creo que construir API transaccionales sobre las API de tabla no es una buena idea:todavía tenemos declaraciones SQL aisladas en lugar de uniones cuidadosamente escritas.

Como ilustración, aquí hay dos implementaciones diferentes de una API transaccional para actualizar el salario de cada empleado en una región (la región es una sección a gran escala de la organización; los departamentos se asignan a las regiones).

La primera versión no tiene SQL puro, solo llamadas de Table API, no creo que esto sea un hombre de paja:usa el tipo de funcionalidad que he visto en los paquetes de Table API (aunque algunos usan SQL dinámico en lugar de procedimientos SET_XXX() con nombre) .

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;
/

La implementación equivalente en 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;
/

Esto es mucho mejor que los bucles de cursor anidados y la actualización de una sola fila de la versión anterior. Esto se debe a que en SQL es pan comido escribir la combinación que necesitamos para seleccionar Empleados por región. Es mucho más difícil usar una API de tabla, porque Region no es una clave de Employees.

Para ser justos, si tenemos una Table API que admita SQL dinámico, las cosas son mejores pero aún no son ideales:

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 palabra

Habiendo dicho todo eso, hay escenarios en los que las API de tablas pueden ser útiles, situaciones en las que solo queremos interactuar con tablas individuales de formas bastante estándar. Un caso obvio podría ser producir o consumir fuentes de datos de otros sistemas, p. ETL.

Si desea investigar el uso de API de tabla, el mejor lugar para comenzar es la utilidad Quest CodeGen de Steven Feuerstein (anteriormente QNXO). Esto es tan bueno como los generadores TAPI, y es gratis.