Лучший способ инкапсулировать сложную логику курсора Oracle PL / SQL в виде представления? - PullRequest
4 голосов
/ 21 августа 2008

Я написал 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 с для заданий, которые не имеют заранее определенной даты завершения.)

Как вы можете себе представить, эту денормализованную форму гораздо проще запрашивать, но для ее создания - насколько я могу судить - требуется временная таблица для хранения промежуточных результатов (например, записей заданий, для которых активация Строка была найдена, но не окончание ... пока). Использование конвейерной табличной функции для заполнения временной таблицы и последующего возврата ее строк - единственный способ, которым я понял, как это сделать.

Ответы [ 6 ]

4 голосов
/ 19 сентября 2008

Я думаю, что способ подойти к этому - использовать аналитические функции ...

Я настроил ваш тестовый пример, используя:

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;

Я использовал функцию lead , чтобы получить следующую дату, а затем обернул ее как подзапрос, чтобы получить записи «A» и добавить дату окончания, если она есть. 1008 *

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                              
1 голос
/ 19 сентября 2008

Настоящая проблема здесь - дизайн таблицы «только для записи» - я имею в виду, что в нее легко вставить данные, но сложно и неэффективно получить из нее полезную информацию! Ваша «временная» таблица имеет структуру, которую «постоянная» таблица должна была иметь в первую очередь.

Не могли бы вы сделать это:

  • Создать постоянную таблицу с лучшей структурой
  • Заполните его, чтобы соответствовать данным в первой таблице
  • Определите триггер базы данных для исходной таблицы, чтобы с этого момента синхронизировать новую таблицу

Затем вы можете просто выбрать из новой таблицы для составления вашего отчета.

1 голос
/ 18 сентября 2008

Похоже, что здесь вы предоставляете некоторую согласованность чтения, т. Е .: содержимое вашей временной таблицы может быть не синхронизировано с исходными данными, если у вас есть одновременная модификация данных модификации.

Не зная ни требований, ни сложности того, чего вы хотите достичь. Я бы попытался

  1. для определения представления, содержащего (возможно, сложную) логику в SQL, иначе я бы добавил PL / SQL к смеси с;
  2. Конвейерная табличная функция, но с использованием типа коллекции SQL (вместо временной таблицы). Простой пример здесь: http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109

Номер 2 даст вам меньше движущихся частей и решит проблему согласованности.

Мэтью Батлер

1 голос
/ 22 августа 2008

Вместо того, чтобы иметь входной параметр в качестве курсора, у меня была бы табличная переменная (не знаю, есть ли у Oracle такая вещь, я - парень TSQL) или я заполнил бы другую временную таблицу значениями ID и присоединился бы к ней в представлении / функции или где вам нужно.

На мой взгляд, единственное время для курсоров - это когда у вас есть для цикла. И когда вам приходится выполнять цикл, я всегда рекомендую делать это вне базы данных в логике приложения.

0 голосов
/ 18 сентября 2008

Самое простое решение:

  1. Создайте глобальную временную таблицу , содержащую только нужные вам идентификаторы:

    CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER)  
    ON COMMIT DELETE ROWS;
    
  2. Заполните временную таблицу нужными идентификаторами.

  3. Используйте операцию EXISTS в своей процедуре, чтобы выбрать строки, которые есть только в таблице идентификаторов:

      SELECT yt.col1, yt.col2 FROM your\_table yt  
       WHERE EXISTS (  
          SELECT 'X' FROM tab_ids ti  
           WHERE ti.id = yt.id  
       )
    

Вы также можете передать разделенную запятыми строку идентификаторов в качестве параметра функции и проанализировать ее в таблицу. Это выполняется одним SELECT. Хотите узнать больше - спросите меня, как :-) Но это должен быть отдельный вопрос.

0 голосов
/ 22 августа 2008

Я не могу с тобой согласиться, HollyStyles. Я также был парнем по TSQL и нахожу некоторые особенности Oracle более чем немного озадачивающими. К сожалению, временные таблицы не так удобны в Oracle, и в этом случае другая существующая логика SQL ожидает прямого запроса к таблице, поэтому вместо этого я даю ей это представление. В этой системе не существует логики приложения вне базы данных.

Разработчики Oracle, похоже, используют курсоры гораздо охотнее, чем я мог бы подумать. С учетом неволи и дисциплины PL / SQL это тем более удивительно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...