sql >> Database >  >> RDS >> Oracle

De verschillen tussen tabel- en transactie-API's begrijpen

Laten we beginnen met de Table API. Dit is de praktijk van het bemiddelen bij toegang tot tabellen via een PL/SQL API. We hebben dus een pakket per tabel, dat moet worden gegenereerd vanuit de datadictionary. Het pakket bevat een standaard set procedures voor het uitgeven van DML tegen de tabel en enkele functies voor het ophalen van gegevens.

Ter vergelijking:een Transactionele API vertegenwoordigt een werkeenheid. Het onthult helemaal geen informatie over de onderliggende database-objecten. Transactionele API's bieden een betere inkapseling en een schonere interface.

Het contrast is zo. Houd rekening met deze bedrijfsregels voor het maken van een nieuwe afdeling:

  1. De nieuwe afdeling moet een naam en locatie hebben
  2. De nieuwe afdeling moet een manager hebben, die een bestaande werknemer moet zijn
  3. Andere bestaande werknemers kunnen worden overgeplaatst naar de nieuwe afdeling
  4. Nieuwe medewerkers kunnen worden toegewezen aan de nieuwe afdeling
  5. Aan de nieuwe afdeling moeten ten minste twee medewerkers zijn toegewezen (inclusief de manager)

Met behulp van tabel-API's kan de transactie er ongeveer zo uitzien:

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

Terwijl het met een Transactionele API veel eenvoudiger is:

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

Dus waarom het verschil in het ophalen van gegevens? Omdat de Transactional API-benadering generieke get() . ontmoedigt functies om het hersenloze gebruik van inefficiënte SELECT-instructies te voorkomen.

Als u bijvoorbeeld alleen het salaris en de commissie voor een werknemer wilt, kunt u dit opvragen ...

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

... is beter dan dit uitvoeren ...

l_emprec := emp_utils.get_whole_row(p_eno);

...vooral als het werknemersrecord LOB-kolommen heeft.

Het is ook efficiënter dan:

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

... als elk van die getters een afzonderlijk SELECT-statement uitvoert. Wat niet onbekend is:het is een slechte OO-praktijk die leidt tot vreselijke databaseprestaties.

De voorstanders van Table API's pleiten voor hen omdat ze de ontwikkelaar afschermen van de noodzaak om over SQL na te denken. De mensen die ze afkeuren, hebben een hekel aan Table API's om dezelfde reden . Zelfs de beste Table-API's hebben de neiging om RBAR-verwerking aan te moedigen. Als we elke keer onze eigen SQL schrijven, is de kans groter dat we een set-gebaseerde benadering kiezen.

Het gebruik van Transactionele API's sluit het gebruik van get_resultset() niet noodzakelijk uit functies. Er zit nog steeds veel waarde in een query-API. Maar het is waarschijnlijker dat het is opgebouwd uit views en functies die joins implementeren dan SELECT's op afzonderlijke tabellen.

Overigens denk ik dat het geen goed idee is om transactie-API's bovenop tabel-API's te bouwen:we hebben nog steeds SQL-instructies in silo's in plaats van zorgvuldig geschreven joins.

Ter illustratie, hier zijn twee verschillende implementaties van een transactie-API om het salaris van elke werknemer in een regio bij te werken (regio is een grootschalig onderdeel van de organisatie; afdelingen worden toegewezen aan regio's).

De eerste versie heeft geen pure SQL, alleen Table API-aanroepen, ik denk niet dat dit een stroman is:het gebruikt het soort functionaliteit dat ik heb gezien in Table API-pakketten (hoewel sommigen dynamische SQL gebruiken in plaats van SET_XXX()-procedures genoemd) .

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

De equivalente implementatie 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;
/

Dit is veel leuker dan de geneste cursorlussen en een enkele rij-update van de vorige versie. Dit komt omdat het in SQL een makkie is om de join te schrijven die we nodig hebben om werknemers per regio te selecteren. Het is een stuk lastiger om een ​​Table API te gebruiken, omdat Regio geen sleutel is van Medewerkers.

Om eerlijk te zijn, als we een Table API hebben die dynamische SQL ondersteunt, is het beter, maar nog steeds niet ideaal:

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

laatste woord

Dat gezegd hebbende, zijn er scenario's waarin tabel-API's nuttig kunnen zijn, situaties waarin we alleen op vrij standaard manieren met enkele tabellen willen communiceren. Een voor de hand liggend geval is het produceren of consumeren van datafeeds van andere systemen, b.v. ETL.

Als u het gebruik van Table API's wilt onderzoeken, kunt u het beste beginnen met Steven Feuerstein's Quest CodeGen Utility (voorheen QNXO). Dit is ongeveer net zo goed als TAPI-generatoren krijgen, en het is gratis.



  1. Waarschuwing:kan header-informatie niet wijzigen - headers zijn al per fout verzonden

  2. Een Java-toepassing maken in Oracle JDeveloper, deel 2

  3. Gegevens gebruiken die zijn beveiligd met een aangepaste sleutelopslag van Linux

  4. PL/SQL Strong-referentiecursor met door de gebruiker gedefinieerd recordgegevenstype