Я написал код PL / SQL для денормализации таблицы в более удобную для запросов форму. Код использует временную таблицу для выполнения некоторых задач, объединяя вместе несколько строк из исходной таблицы.
Логика написана как конвейерная табличная функция, следуя шаблону из связанной статьи. Табличная функция использует объявление PRAGMA AUTONOMOUS_TRANSACTION, чтобы разрешить манипуляции с временной таблицей, а также принимает входной параметр курсора, чтобы ограничить денормализацию определенными значениями ID.
Затем я создал представление для запроса табличной функции, передав все возможные значения идентификатора в качестве курсора (другие варианты использования функции будут более ограничительными).
Мой вопрос: действительно ли все это необходимо? Неужели я полностью пропустил гораздо более простой способ сделать то же самое?
Каждый раз, когда я касаюсь 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, переданных через курсор. Передача EMP_IDs 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 для заданий, для которых нет заранее установленной даты завершения.)
Как вы понимаете, эту денормализованную форму гораздо проще запросить, но для ее создания - насколько я могу судить - требуется временная таблица для хранения промежуточных результатов (например, записей о вакансиях, для которых была введена строка активации. нашел, но не окончание ... пока). Использование конвейерной табличной функции для заполнения временной таблицы и последующего возврата ее строк - единственный способ, которым я понял, как это сделать.


Вместо того, чтобы иметь входной параметр в качестве курсора, у меня была бы табличная переменная (не знаю, есть ли у Oracle такая вещь, я - парень TSQL) или заполнить другую временную таблицу значениями идентификатора и присоединиться к ней в представлении / function или где угодно.
Единственное время для курсоров, по моему честному мнению, - это когда вы имеют зацикливаете. И когда вам нужно зацикливаться, я всегда рекомендую делать это вне базы данных в логике приложения.
Я не могу больше с тобой согласиться, 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. Хотите узнать больше - спросите как :-) Но это должен быть отдельный вопрос.
Временная таблица, о которой я упоминал, по существу состоит из идентификаторов (четыре столбца, составляющих первичный ключ исходной таблицы). Проблема в том, что некоторые исходные строки необходимо объединить вместе, поэтому это не так просто, как вы предлагаете.
Похоже, вы здесь демонстрируете некоторую согласованность чтения, то есть: содержимое вашей временной таблицы может не синхронизироваться с исходными данными, если у вас есть одновременная модификация данных.
Не зная ни требований, ни сложности того, чего вы хотите достичь. Я бы попытался
Номер 2 даст вам меньше движущихся частей и решит проблему согласованности.
Мэтью Батлер
Спасибо за чаевые. Я не собираюсь особо беспокоиться о согласованности чтения, поскольку представление и его базовая табличная функция предназначены только для отчетов, а согласованность с точностью до секунды не является обязательным требованием.
Я думаю, что способ подойти к этому - использовать аналитические функции ...
Я настроил ваш тестовый пример, используя:
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
Очень круто. Я никогда не слышал об аналитических функциях, но обязательно их проверю. Это выглядит намного проще, чем то, что я пробовал.
Оказывается, это сработает нормально. Возникла сложность, заключающаяся в том, что можно иметь несколько активаций для одного завершения, но, изменив строку декодирования на: decode (next_status, 'A', next_eff_date - 1, next_eff_date), проблема была прекрасно решена. Огромное спасибо.
Настоящая проблема здесь заключается в дизайне таблицы «только для записи» - я имею в виду, что в нее легко вставлять данные, но сложно и неэффективно получить из нее полезную информацию! Ваша «временная» таблица имеет структуру, которую «постоянная» таблица должна иметь изначально.
Не могли бы вы сделать это:
Затем вы можете просто выбрать из новой таблицы для составления отчета.
К сожалению, исходная таблица лежит в основе системы, поставляемой поставщиком. Я сам думал об идее триггера, хотя он должен ссылаться на исходную таблицу, а это вызывает другие проблемы. У всей системы есть ощущение "хрупкости", поэтому я подумал, что лучше пойти с обзором
Обратите внимание, что вам нужно создать временную таблицу только один раз.