Oracle
 sql >> Datenbank >  >> RDS >> Oracle

Verstehen der Unterschiede zwischen Tabellen- und Transaktions-APIs

Beginnen wir mit der Tabellen-API. Dies ist die Praxis, den Zugriff auf Tabellen über eine PL/SQL-API zu vermitteln. Wir haben also ein Paket pro Tabelle, das aus dem Data Dictionary generiert werden soll. Das Paket enthält einen Standardsatz von Prozeduren zum Ausgeben von DML für die Tabelle und einige Funktionen zum Abrufen von Daten.

Im Vergleich dazu stellt eine Transaktions-API eine Arbeitseinheit dar. Es stellt überhaupt keine Informationen über die zugrunde liegenden Datenbankobjekte bereit. Transaktions-APIs bieten eine bessere Kapselung und eine sauberere Schnittstelle.

Der Kontrast ist so. Beachten Sie diese Geschäftsregeln zum Erstellen einer neuen Abteilung:

  1. Die neue Abteilung muss einen Namen und einen Standort haben
  2. Die neue Abteilung muss einen Manager haben, der ein bestehender Mitarbeiter sein muss
  3. Andere bestehende Mitarbeiter können in die neue Abteilung versetzt werden
  4. Neue Mitarbeiter können der neuen Abteilung zugewiesen werden
  5. Der neuen Abteilung müssen mindestens zwei Mitarbeiter zugewiesen sein (einschließlich des Managers)

Bei Verwendung von Tabellen-APIs könnte die Transaktion etwa so aussehen:

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

Während es mit einer Transaktions-API viel einfacher ist:

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

Warum also der Unterschied beim Abrufen von Daten? Weil der Transaktions-API-Ansatz vom generischen get() abrät Funktionen, um die sinnlose Verwendung ineffizienter SELECT-Anweisungen zu vermeiden.

Wenn Sie beispielsweise nur das Gehalt und die Provision für einen Mitarbeiter möchten, fragen Sie dies ab ...

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

... ist besser, als dies auszuführen ...

l_emprec := emp_utils.get_whole_row(p_eno);

...insbesondere wenn der Employee-Datensatz LOB-Spalten hat.

Es ist auch effizienter als:

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

... wenn jeder dieser Getter eine separate SELECT-Anweisung ausführt. Was nicht unbekannt ist:Es ist eine schlechte OO-Praxis, die zu einer schrecklichen Datenbankleistung führt.

Die Befürworter von Tabellen-APIs argumentieren dafür, dass sie den Entwickler davon abhalten, über SQL nachdenken zu müssen. Die Leute, die sie ablehnen, mögen Tabellen-APIs aus demselben Grund nicht . Selbst die besten Tabellen-APIs neigen dazu, die RBAR-Verarbeitung zu fördern. Wenn wir jedes Mal unser eigenes SQL schreiben, entscheiden wir uns eher für einen mengenbasierten Ansatz.

Die Verwendung von Transaktions-APs schließt die Verwendung von get_resultset() nicht unbedingt aus Funktionen. Es gibt immer noch viel Wert in einer abfragenden API. Aber es ist wahrscheinlicher, dass es aus Views und Funktionen aufgebaut ist, die Joins implementieren, als SELECTs auf einzelne Tabellen.

Übrigens denke ich, dass es keine gute Idee ist, Transaktions-APIs auf Tabellen-APIs aufzubauen:Wir haben immer noch isolierte SQL-Anweisungen anstelle von sorgfältig geschriebenen Joins.

Zur Veranschaulichung sind hier zwei verschiedene Implementierungen einer Transaktions-API zur Aktualisierung des Gehalts jedes Mitarbeiters in einer Region (Region ist ein großer Teil der Organisation; Abteilungen sind Regionen zugeordnet).

Die erste Version hat kein reines SQL, sondern nur Tabellen-API-Aufrufe. Ich glaube nicht, dass dies ein Strohmann ist:Sie verwendet die Art von Funktionalität, die ich in Tabellen-API-Paketen gesehen habe (obwohl einige dynamisches SQL anstelle von benannten SET_XXX()-Prozeduren verwenden). .

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

Die äquivalente Implementierung 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;
/

Das ist viel schöner als die verschachtelten Cursorschleifen und die Einzelzeilenaktualisierung der vorherigen Version. Dies liegt daran, dass es in SQL ein Kinderspiel ist, den Join zu schreiben, den wir benötigen, um Mitarbeiter nach Region auszuwählen. Es ist viel schwieriger, eine Tabellen-API zu verwenden, da Region kein Schlüssel von Employees ist.

Um fair zu sein, wenn wir eine Tabellen-API haben, die dynamisches SQL unterstützt, sind die Dinge besser, aber immer noch nicht ideal:

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

letztes Wort

Abgesehen davon gibt es Szenarien, in denen Tabellen-APIs nützlich sein können, Situationen, in denen wir nur auf ziemlich standardmäßige Weise mit einzelnen Tabellen interagieren möchten. Ein naheliegender Fall könnte das Produzieren oder Konsumieren von Datenfeeds aus anderen Systemen sein, z. ETL.

Wenn Sie die Verwendung von Tabellen-APIs untersuchen möchten, ist der beste Ausgangspunkt Steven Feuersteins Quest CodeGen Utility (ehemals QNXO). Das ist ungefähr so ​​gut wie TAPI-Generatoren, und es ist kostenlos.