Лучший способ инкапсулировать сложную логику курсора Oracle PL/SQL в виде представления?
Вопрос
Я написал код PL/SQL для денормализации таблицы в форму, удобную для запроса.Код использует временную таблицу для выполнения части своей работы, объединяя вместе некоторые строки исходной таблицы.
Логика записана в виде конвейерная табличная функция, следуя образцу из связанной статьи.Табличная функция использует PRAGMA AUTONOMOUS_TRANSACTION
объявление, позволяющее манипулировать временной таблицей, а также принимает входной параметр курсора, чтобы ограничить денормализацию определенными значениями идентификаторов.
Затем я создал представление для запроса табличной функции, передав все возможные значения идентификатора в качестве курсора (другие варианты использования функции будут более ограничительными).
Мой вопрос:действительно ли это все необходимо?Неужели я полностью упустил гораздо более простой способ добиться того же?
Каждый раз, когда я прикасаюсь к PL/SQL, у меня создается впечатление, что я печатаю слишком много.
Обновлять: Я добавлю эскиз таблицы, с которой имею дело, чтобы дать всем представление о денормализации, о которой я говорю.В таблице хранится история заданий сотрудников, каждая из которых имеет строку активации и (возможно) строку завершения.Сотрудник может выполнять несколько работ одновременно, а также выполнять одну и ту же работу снова и снова в несмежных диапазонах дат.Например:
| EMP_ID | JOB_ID | STATUS | EFF_DATE | other columns...
| 1 | 10 | A | 10-JAN-2008 |
| 2 | 11 | A | 13-JAN-2008 |
| 1 | 12 | A | 20-JAN-2008 |
| 2 | 11 | T | 01-FEB-2008 |
| 1 | 10 | T | 02-FEB-2008 |
| 2 | 11 | A | 20-FEB-2008 |
Запросить это, чтобы выяснить, кто и на какой работе работает, нетривиально.Итак, моя функция денормализации заполняет временную таблицу только диапазонами дат для каждого задания, для любого EMP_ID
s передается через курсор.Прохождение EMP_ID
s 1 и 2 дадут следующее:
| EMP_ID | JOB_ID | START_DATE | END_DATE |
| 1 | 10 | 10-JAN-2008 | 02-FEB-2008 |
| 2 | 11 | 13-JAN-2008 | 01-FEB-2008 |
| 1 | 12 | 20-JAN-2008 | |
| 2 | 11 | 20-FEB-2008 | |
(END_DATE
позволяет NULL
s для должностей, у которых нет заранее определенной даты прекращения.)
Как вы можете себе представить, эту денормализованную форму гораздо проще запрашивать, но для ее создания, насколько я могу судить, требуется временная таблица для хранения промежуточных результатов (например, записей о заданиях, для которых строка активации была нашел, но не прекращение...пока).Использование конвейерной табличной функции для заполнения временной таблицы и последующего возврата ее строк — единственный способ, которым я понял, как это сделать.
Решение
Я думаю, что способ приблизиться к этому - использовать аналитические функции...
Я настроил ваш тестовый пример, используя:
create table employee_job (
emp_id integer,
job_id integer,
status varchar2(1 char),
eff_date date
);
insert into employee_job values (1,10,'A',to_date('10-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('13-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (1,12,'A',to_date('20-JAN-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'T',to_date('01-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (1,10,'T',to_date('02-FEB-2008','DD-MON-YYYY'));
insert into employee_job values (2,11,'A',to_date('20-FEB-2008','DD-MON-YYYY'));
commit;
Я использовал вести функцию, чтобы получить следующую дату, а затем обернуть все это как подзапрос, просто чтобы получить записи «A» и добавить дату окончания, если она есть.
select
emp_id,
job_id,
eff_date start_date,
decode(next_status,'T',next_eff_date,null) end_date
from
(
select
emp_id,
job_id,
eff_date,
status,
lead(eff_date,1,null) over (partition by emp_id, job_id order by eff_date, status) next_eff_date,
lead(status,1,null) over (partition by emp_id, job_id order by eff_date, status) next_status
from
employee_job
)
where
status = 'A'
order by
start_date,
emp_id,
job_id
Я уверен, что есть некоторые варианты использования, которые я пропустил, но вы поняли.Аналитические функции — ваш друг :)
EMP_ID JOB_ID START_DATE END_DATE
1 10 10-JAN-2008 02-FEB-2008
2 11 13-JAN-2008 01-FEB-2008
2 11 20-FEB-2008
1 12 20-JAN-2008
Другие советы
Вместо того, чтобы использовать входной параметр в качестве курсора, я бы использовал табличную переменную (не знаю, есть ли такая вещь в Oracle, я парень из TSQL) или заполнил бы другую временную таблицу значениями идентификаторов и присоединился бы к ней в представлении. /function или где вам нужно.
По моему честному мнению, курсоры можно использовать только тогда, когда вы иметь зациклить.И когда вам нужно выполнить цикл, я всегда рекомендую делать это вне базы данных в логике приложения.
Похоже, вы здесь демонстрируете некоторую согласованность чтения, т.е.:Содержимое вашей временной таблицы может не синхронизироваться с исходными данными, если у вас есть одновременная модификация данных.
Не зная ни требований, ни сложности того, чего вы хотите достичь.я бы попробовал
- чтобы определить представление, содержащее (возможно, сложную) логику в SQL, иначе я бы добавил в смесь немного PL/SQL;
- Конвейерная табличная функция, но использующая тип коллекции SQL (вместо временной таблицы).Простой пример здесь: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109
Номер 2 даст вам меньше движущихся частей и решит проблему согласованности.
Мэтью Батлер
Настоящей проблемой здесь является конструкция таблицы «только для записи» — я имею в виду, что в нее легко вставлять данные, но получить из нее полезную информацию сложно и неэффективно!Ваша «временная» таблица имеет структуру, которую изначально должна была иметь «постоянная» таблица.
Не могли бы вы сделать это:
- Создайте постоянную таблицу с лучшей структурой.
- Заполните его, чтобы он соответствовал данным в первой таблице.
- Определите триггер базы данных для исходной таблицы, чтобы теперь синхронизировать новую таблицу.
Затем вы можете просто выбрать из новой таблицы данные для создания отчетов.
Я полностью с тобой согласен, HollyStyles.Я также раньше занимался TSQL и находил некоторые особенности Oracle более чем озадачивающими.К сожалению, временные таблицы не так удобны в Oracle, и в этом случае другая существующая логика SQL ожидает прямого запроса к таблице, поэтому вместо этого я предоставляю ей это представление.На самом деле в этой системе нет никакой прикладной логики, которая существовала бы вне базы данных.
Разработчики Oracle, похоже, используют курсоры гораздо охотнее, чем я мог бы подумать.Учитывая связанную и дисциплинирующую природу PL/SQL, это тем более удивительно.
Самое простое решение:
Создать глобальная временная таблица содержащий только нужные вам идентификаторы:
CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER) ON COMMIT DELETE ROWS;
Заполните временную таблицу нужными вам идентификаторами.
Используйте операцию EXISTS в своей процедуре, чтобы выбрать строки, которые есть только в таблице идентификаторов:
SELECT yt.col1, yt.col2 FROM your\_table yt WHERE EXISTS ( SELECT 'X' FROM tab_ids ti WHERE ti.id = yt.id )
Вы также можете передать строку идентификаторов, разделенных запятыми, в качестве параметра функции и проанализировать ее в таблицу.Это выполняется одним SELECT.Хотите узнать больше - спросите как :-) Но это уже отдельный вопрос.