sql >> Databasteknik >  >> RDS >> Oracle

Förstå skillnaderna mellan tabell- och transaktions-API:er

Låt oss börja med Table API. Detta är praxis att förmedla åtkomst till tabeller genom ett PL/SQL API. Så vi har ett paket per tabell, som bör genereras från dataordboken. Paketet presenterar en standarduppsättning procedurer för att utfärda DML mot tabellen och några funktioner för att hämta data.

Som jämförelse representerar ett Transactional API en Unit Of Work. Det visar inte någon information om de underliggande databasobjekten alls. Transaktionella API:er erbjuder bättre inkapsling och ett renare gränssnitt.

Kontrasten är så här. Tänk på dessa affärsregler för att skapa en ny avdelning:

  1. Den nya avdelningen måste ha ett namn och en plats
  2. Den nya avdelningen måste ha en chef som måste vara en befintlig anställd
  3. Andra befintliga anställda kan flyttas till den nya avdelningen
  4. Nya medarbetare kan tilldelas den nya avdelningen
  5. Den nya avdelningen måste ha minst två anställda tilldelade (inklusive chefen)

Med tabell-API:er kan transaktionen se ut ungefär så här:

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

Medan det är mycket enklare med ett Transactional API:

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

Så varför är skillnaden i att hämta data? Eftersom Transactional API-metoden avskräcker generisk get() funktioner för att undvika tanklös användning av ineffektiva SELECT-satser.

Till exempel, om du bara vill ha lön och provision för en anställd, fråga detta ...

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

... är bättre än att utföra detta ...

l_emprec := emp_utils.get_whole_row(p_eno);

...speciellt om anställningsposten har LOB-kolumner.

Det är också mer effektivt än:

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

... om var och en av dessa getters kör en separat SELECT-sats. Vilket inte är okänt:det är en dålig OO-praxis som leder till fruktansvärda databasprestanda.

Förespråkarna för tabell-API:er argumenterar för dem utifrån att de skyddar utvecklaren från att behöva tänka på SQL. Människorna som fasar ut dem ogillar tabell-API:er av samma anledning . Även de bästa tabell-API:erna tenderar att uppmuntra RBAR-bearbetning. Om vi ​​skriver vår egen SQL varje gång är det mer sannolikt att vi väljer ett set-baserat tillvägagångssätt.

Att använda Transactional APis utesluter inte nödvändigtvis användningen av get_resultset() funktioner. Det finns fortfarande mycket värde i ett förfrågnings-API. Men det är mer sannolikt att det bygger på vyer och funktioner som implementerar kopplingar än SELECT på enskilda tabeller.

För övrigt tror jag att det inte är en bra idé att bygga Transaktions-API:er ovanpå Tabell-API:er:vi har fortfarande silade SQL-satser istället för noggrant skrivna kopplingar.

Som en illustration, här är två olika implementeringar av ett transaktions-API för att uppdatera lönen för varje anställd i en region (regionen är en storskalig del av organisationen; avdelningar är tilldelade regioner).

Den första versionen har ingen ren SQL bara Table API-anrop, jag tror inte att det här är en halmgubbe:den använder den typ av funktionalitet som jag har sett i Table API-paket (även om vissa använder dynamisk SQL snarare än SET_XXX()-procedurer) .

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

Motsvarande implementering i 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;
/

Detta är mycket trevligare än de kapslade markörslingorna och enradsuppdateringen av den tidigare versionen. Detta beror på att det i SQL är enkelt att skriva anslutningen som vi behöver för att välja anställda per region. Det är mycket svårare att använda ett tabell-API, eftersom Region inte är en nyckel för anställda.

För att vara rättvis, om vi har ett tabell-API som stöder dynamisk SQL, är saker och ting bättre men fortfarande inte idealiska:

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

sista ordet

Med det sagt finns det scenarier där tabell-API:er kan vara användbara, situationer då vi bara vill interagera med enstaka tabeller på ganska standardmässiga sätt. Ett uppenbart fall kan vara att producera eller konsumera dataflöden från andra system t.ex. ETL.

Om du vill undersöka användningen av tabell-API:er är det bästa stället att börja med Steven Feuersteins Quest CodeGen Utility (tidigare QNXO). Det här är ungefär lika bra som TAPI-generatorer blir, och det är gratis.



  1. Hur man får värden som inte innehåller siffror i MariaDB

  2. Hur skickar jag Java List of Objects till Oracle Stored Procedur med MyBatis?

  3. SQL Full Join

  4. Tabellfiltrering i IRI Workbench