Oracle
 sql >> Base de données >  >> RDS >> Oracle

Comprendre les différences entre les API Table et Transaction

Commençons par l'API Table. Il s'agit de la pratique de la médiation de l'accès aux tables via une API PL/SQL. Nous avons donc un package par table, qui doit être généré à partir du dictionnaire de données. Le package présente un ensemble standard de procédures pour l'émission de DML par rapport à la table et certaines fonctions de récupération de données.

Par comparaison, une API transactionnelle représente une unité de travail. Il n'expose aucune information sur les objets de base de données sous-jacents. Les API transactionnelles offrent une meilleure encapsulation et une interface plus propre.

Le contraste est comme ça. Tenez compte de ces règles commerciales pour créer un nouveau service :

  1. Le nouveau service doit avoir un nom et un lieu
  2. Le nouveau service doit avoir un responsable, qui doit être un employé existant
  3. D'autres employés existants peuvent être transférés dans le nouveau service
  4. De nouveaux employés peuvent être affectés au nouveau service
  5. Le nouveau service doit avoir au moins deux employés affectés (y compris le responsable)

En utilisant les API de table, la transaction pourrait ressembler à ceci :

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

Alors qu'avec une API Transactionnelle c'est beaucoup plus 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;
/

Alors pourquoi la différence dans la récupération des données ? Parce que l'approche de l'API transactionnelle décourage les get() génériques afin d'éviter l'utilisation inconsidérée d'instructions SELECT inefficaces.

Par exemple, si vous voulez juste le salaire et la commission d'un Employé, interrogez ceci...

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

... est mieux que d'exécuter ceci ...

l_emprec := emp_utils.get_whole_row(p_eno);

...surtout si l'enregistrement Employé comporte des colonnes LOB.

C'est aussi plus efficace que :

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

... si chacun de ces getters exécute une instruction SELECT distincte. Ce qui n'est pas inconnu :c'est une mauvaise pratique OO qui conduit à des performances de base de données horribles.

Les partisans des API de table les défendent sur la base qu'elles évitent au développeur d'avoir à penser à SQL. Les personnes qui les déprécient n'aiment pas les API Table pour la même raison . Même les meilleures API de table ont tendance à encourager le traitement RBAR. Si nous écrivons notre propre SQL à chaque fois, nous sommes plus susceptibles de choisir une approche basée sur les ensembles.

L'utilisation d'API transactionnelles n'exclut pas nécessairement l'utilisation de get_resultset() les fonctions. Il y a encore beaucoup de valeur dans une API de requête. Mais il est plus susceptible d'être construit à partir de vues et de fonctions implémentant des jointures que de SELECT sur des tables individuelles.

Incidemment, je pense que construire des API transactionnelles au-dessus des API de table n'est pas une bonne idée :nous avons toujours des instructions SQL cloisonnées au lieu de jointures soigneusement écrites.

À titre d'illustration, voici deux implémentations différentes d'une API transactionnelle pour mettre à jour le salaire de chaque employé d'une région (la région étant une section à grande échelle de l'organisation ; les départements sont affectés aux régions).

La première version n'a pas de SQL pur, juste des appels d'API de table, je ne pense pas que ce soit un homme de paille :il utilise le type de fonctionnalité que j'ai vu dans les packages d'API de table (bien que certains utilisent du SQL dynamique plutôt que des procédures nommées SET_XXX()) .

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

L'implémentation équivalente 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;
/

C'est beaucoup plus agréable que les boucles de curseur imbriquées et la mise à jour d'une seule ligne de la version précédente. En effet, en SQL, il est facile d'écrire la jointure dont nous avons besoin pour sélectionner les employés par région. Il est beaucoup plus difficile d'utiliser une API de table, car la région n'est pas une clé des employés.

Pour être juste, si nous avons une API Table qui prend en charge le SQL dynamique, les choses sont meilleures mais toujours pas idéales :

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

dernier mot

Cela dit, il existe des scénarios dans lesquels les API de table peuvent être utiles, des situations dans lesquelles nous ne voulons interagir qu'avec des tables uniques de manière assez standard. Un cas évident pourrait être la production ou la consommation de flux de données provenant d'autres systèmes, par ex. ETL.

Si vous souhaitez étudier l'utilisation des API de table, le meilleur endroit pour commencer est l'utilitaire Quest CodeGen de Steven Feuerstein (anciennement QNXO). C'est à peu près aussi bon que les générateurs TAPI, et c'est gratuit.