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

StackOverflow https://stackoverflow.com/questions/20081

  •  09-06-2019
  •  | 
  •  

Вопрос

Я написал код 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_IDs передается через курсор.Прохождение 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 позволяет NULLs для должностей, у которых нет заранее определенной даты прекращения.)

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

Это было полезно?

Решение

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

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

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 или где вам нужно.

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

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

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

  1. чтобы определить представление, содержащее (возможно, сложную) логику в SQL, иначе я бы добавил в смесь немного PL/SQL;
  2. Конвейерная табличная функция, но использующая тип коллекции 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, это тем более удивительно.

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

  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.Хотите узнать больше - спросите как :-) Но это уже отдельный вопрос.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top