Ответ 1
Начнем с API таблиц. Это практика посредничества в доступе к таблицам через API PL/SQL. Итак, у нас есть пакет для таблицы, который должен быть создан из словаря данных. В пакете представлен стандартный набор процедур для выдачи DML для таблицы и некоторые функции для извлечения данных.
Для сравнения, Transactional 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);
... особенно если запись Employee имеет столбцы LOB.
Он также более эффективен, чем:
l_sal := emp_utils.get_sal(p_eno);
l_comm := emp_utils.get_comm(p_eno);
... если каждый из этих геттеров выполняет отдельный оператор SELECT. Что не известно: это плохая практика OO, которая приводит к ужасной производительности базы данных.
Сторонники API таблиц утверждают их на основе того, что они защищают разработчика от необходимости думать о SQL. Люди, которые их осуждают, не любят Table API по той же причине. Даже лучшие API таблицы обычно стимулируют обработку RBAR. Если мы будем писать наш собственный SQL каждый раз, мы с большей вероятностью будем выбирать подход, основанный на наборе.
Использование Transactional APis не обязательно исключает использование функций get_resultset()
. В API запросов все еще очень много. Но он скорее всего будет построен из представлений и функций, реализующих объединения, чем SELECT в отдельных таблицах.
Кстати, я думаю, что создание API транзакций поверх API таблиц не является хорошей идеей: мы все еще заставляем SQL-выражения вместо тщательно написанных объединений.
В качестве иллюстрации здесь представлены две различные реализации транзакционного API для обновления зарплаты каждого сотрудника в регионе (регион - это крупномасштабный раздел организации, отделы - для регионов).
В первой версии нет чистых SQL-вызовов только для Table API, я не думаю, что это соломенный человек: он использует ту функциональность, которую я видел в пакетах Table 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 таблиц намного сложнее, потому что Region не является ключом к Сотрудникам.
Чтобы быть справедливым, если у нас есть 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 Utility (ранее QNXO). Это примерно так же хорошо, как генераторы TAPI, и это бесплатно.