Oracle
 sql >> Database >  >> RDS >> Oracle

Comprendere le differenze tra le API delle tabelle e delle transazioni

Iniziamo con l'API Table. Questa è la pratica di mediare l'accesso alle tabelle tramite un'API PL/SQL. Quindi, abbiamo un pacchetto per tabella, che dovrebbe essere generato dal dizionario dei dati. Il pacchetto presenta un insieme standard di procedure per l'emissione di DML rispetto alla tabella e alcune funzioni per il recupero dei dati.

In confronto, un'API transazionale rappresenta un'unità di lavoro. Non espone alcuna informazione sugli oggetti del database sottostanti. Le API transazionali offrono un migliore incapsulamento e un'interfaccia più pulita.

Il contrasto è così. Considera queste regole aziendali per la creazione di un nuovo dipartimento:

  1. Il nuovo Dipartimento deve avere un Nome e una Sede
  2. Il nuovo Dipartimento deve avere un manager, che deve essere un Dipendente esistente
  3. Altri Dipendenti esistenti possono essere trasferiti al nuovo Dipartimento
  4. Nuovi dipendenti possono essere assegnati al nuovo Dipartimento
  5. Il nuovo Dipartimento deve avere almeno due Dipendenti assegnati (incluso il manager)

Utilizzando le API di tabella, la transazione potrebbe essere simile a questa:

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

Mentre con un'API transazionale è molto più semplice:

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

Allora perché la differenza nel recupero dei dati? Poiché l'approccio dell'API transazionale scoraggia il generico get() funzioni al fine di evitare l'uso sconsiderato di istruzioni SELECT inefficienti.

Ad esempio, se vuoi solo lo stipendio e la commissione per un Dipendente, interroga questo ...

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

... è meglio che eseguire questo ...

l_emprec := emp_utils.get_whole_row(p_eno);

...soprattutto se il record Impiegato ha colonne LOB.

È anche più efficiente di:

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

... se ciascuno di questi getter esegue un'istruzione SELECT separata. Il che non è sconosciuto:è una cattiva pratica OO che porta a prestazioni orribili del database.

I sostenitori delle API di tabella li sostengono sulla base del fatto che proteggono lo sviluppatore dal dover pensare all'SQL. Le persone che le deprecano non amano le API Table per lo stesso motivo . Anche le migliori API di tabella tendono a incoraggiare l'elaborazione RBAR. Se scriviamo il nostro SQL ogni volta, è più probabile che scegliamo un approccio basato su insiemi.

L'uso delle API transazionali non esclude necessariamente l'uso di get_resultset() funzioni. C'è ancora molto valore in un'API di query. Ma è più probabile che sia costruito da viste e funzioni che implementano join rispetto a SELECT su singole tabelle.

Per inciso, penso che la creazione di API transazionali sopra le API di tabella non sia una buona idea:abbiamo ancora istruzioni SQL in silos invece di join scritti con cura.

A titolo illustrativo, ecco due diverse implementazioni di un'API transazionale per aggiornare lo stipendio di ogni dipendente in una regione (la regione è una sezione dell'organizzazione su larga scala; i dipartimenti sono assegnati alle regioni).

La prima versione non ha chiamate SQL pure all'API Table, non penso che questo sia un uomo di paglia:usa il tipo di funzionalità che ho visto nei pacchetti dell'API Table (sebbene alcuni utilizzino procedure SQL dinamiche piuttosto che denominate 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'implementazione equivalente in 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;
/

Questo è molto più bello dei cicli del cursore nidificati e dell'aggiornamento a riga singola della versione precedente. Questo perché in SQL è un gioco da ragazzi scrivere il join di cui abbiamo bisogno per selezionare Dipendenti per regione. È molto più difficile utilizzare un'API di tabella, perché la regione non è una chiave di dipendenti.

Ad essere onesti, se disponiamo di un'API Table che supporta SQL dinamico, le cose sono migliori ma non ancora ideali:

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

ultima parola

Detto questo, ci sono scenari in cui le API di tabella possono essere utili, situazioni in cui vogliamo interagire solo con singole tabelle in modi abbastanza standard. Un caso ovvio potrebbe essere la produzione o il consumo di feed di dati da altri sistemi, ad es. ETL.

Se vuoi studiare l'uso delle API delle tabelle, il miglior punto di partenza è Quest CodeGen Utility di Steven Feuerstein (precedentemente QNXO). Questo è buono quanto i generatori TAPI ed è gratuito.