Нека започнем с API за таблица. Това е практиката на посредничество за достъп до таблици чрез PL/SQL API. И така, имаме пакет за таблица, който трябва да бъде генериран от речника с данни. Пакетът представя стандартен набор от процедури за издаване на DML срещу таблицата и някои функции за извличане на данни.
За сравнение транзакционният API представлява единица работа. Той изобщо не разкрива никаква информация за основните обекти на базата данни. Транзакционните API предлагат по-добро капсулиране и по-чист интерфейс.
Контрастът е такъв. Помислете за тези бизнес правила за създаване на нов отдел:
- Новият отдел трябва да има име и местоположение
- Новият отдел трябва да има мениджър, който трябва да бъде съществуващ служител
- Други съществуващи служители могат да бъдат прехвърлени в новия отдел
- Нови служители могат да бъдат назначени в новия отдел
- Новият отдел трябва да има поне двама назначени служители (включително мениджъра)
При използване на API за таблица транзакцията може да изглежда така:
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;
/
Докато с 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;
/
И така, защо разликата в извличането на данни? Тъй като подходът на Transactional API обезкуражава общия get()
функции, за да се избегне безсмисленото използване на неефективни изрази SELECT.
Например, ако просто искате заплатата и комисионната за служител, като запитате това ...
select sal, comm
into l_sal, l_comm
from emp
where empno = p_eno;
... е по-добре от изпълнението на това ...
l_emprec := emp_utils.get_whole_row(p_eno);
...особено ако записът на служител има LOB колони.
Освен това е по-ефективен от:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... ако всеки от тези получатели изпълнява отделен оператор SELECT. Което не е неизвестно:това е лоша OO практика, която води до ужасна производителност на базата данни.
Поддръжниците на приложните програмни интерфейси (API) за таблици се аргументират за тях на базата на това, че те предпазват разработчика от необходимостта да мисли за SQL. Хората, които ги отхвърлят, не харесват API за таблици по същата причина . Дори най-добрите API за таблици са склонни да насърчават обработката на RBAR. Ако всеки път пишем собствен SQL, е по-вероятно да изберем подход, базиран на набори.
Използването на Transactional APis не изключва непременно използването на get_resultset()
функции. Все още има голяма стойност в API за заявки. Но е по-вероятно той да бъде изграден от изгледи и функции, внедряващи обединения, отколкото SELECT на отделни таблици.
Между другото, мисля, че изграждането на транзакционни API върху API на таблици не е добра идея:все още имаме затворени SQL изрази вместо внимателно написани присъединявания.
Като илюстрация, ето две различни реализации на транзакционен API за актуализиране на заплатата на всеки служител в даден регион (регионът е голяма част от организацията; отделите са присвоени на региони).
Първата версия няма чист SQL, а просто извиквания на Table API, не мисля, че това е сламка:тя използва вида функционалност, която съм виждал в пакетите API на таблица (въпреки че някои използват динамичен SQL, а не с име 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;
/
Еквивалентната реализация в 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;
/
Това е много по-хубаво от вложените курсорни цикли и едноредовата актуализация на предишната версия. Това е така, защото в SQL е трудно да напишем присъединяването, от което се нуждаем, за да изберем служители по регион. Много по-трудно е използването на API за таблица, тъй като регионът не е ключ за служителите.
За да бъдем честни, ако имаме API за таблица, който поддържа динамичен SQL, нещата са по-добри, но все още не са идеални:
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;
/
последна дума
След като казахме всичко това, има сценарии, при които API за таблици могат да бъдат полезни, ситуации, когато искаме да взаимодействаме само с единични таблици по доста стандартни начини. Очевиден случай може да бъде производството или консумацията на емисии на данни от други системи, напр. ETL.
Ако искате да проучите използването на API за таблици, най-доброто място за начало е помощната програма Quest CodeGen на Steven Feuerstein (преди QNXO). Това е приблизително толкова добро, колкото получават генераторите на TAPI, и е безплатно.