рефакторинг запросов большого курсора путем разбиения на несколько курсоров - PullRequest
6 голосов
/ 17 мая 2011

Еще один вопрос рефакторинга PL / SQL!

У меня есть несколько курсоров, которые имеют общую упрощенную форму:

cursor_1 is
  with X as (select col1, col2 from TAB where col1 = '1'),
       Y as (select col1, col2 from TAB where col2 = '3'),
  /*main select*/
  select count(X.col1), ...
  from X inner join Y on...
  group by rollup (X.col1, ...

cursor_2 is
  with X as (select col1, col2 from TAB where col1 = '7' and col2 = '9' and col3 = 'TEST'),
       Y as (select col1, col2 from TAB where col3 = '6'),
  /*main select*/
  select count(X.col1), ...
  from X inner join Y on...
  group by rollup (X.col1, ...


cursor_2 is
  with X as (select col1, col2 from TAB where col1 IS NULL ),
       Y as (select col1, col2 from TAB where col2 IS NOT NULL ),
  /*main select*/
  select count(X.col1), ...
  from X inner join Y on...
  group by rollup (X.col1, ...


...
begin
    for r in cursor_1 loop
       print_report_results(r);
    end loop;

    for r in cursor_2 loop
       print_report_results(r);
    end loop;
    ...
end;

По сути, все эти курсоры (их более 3)те же сводные / отчетные запросы.Разница в факторизованных подзапросах.Всегда есть 2 факторизованных подзапроса, «X» и «Y», и они всегда выбирают одни и те же столбцы для подачи в основной отчетный запрос.

Проблема в том, что основной запрос отчетов ОЧЕНЬ большой, около 70линий.Само по себе это не так уж и плохо, но оно было скопировано для ВСЕХ запросов на отчеты (я думаю, их более десятка).

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

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

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


Полагаю, что мне в конечном итоге понравится то, что выглядит примерно так (я просто не уверен, каксделать это так, чтобы он действительно скомпилировался):

cursor main_report_cursor (in_X, in_Y) is
    with X as (select * from in_X),
         Y as (select * from in_Y)
  /*main select*/
  select count(X.col1), ...
  from X inner join Y on...
  group by rollup (X.col1, ...

cursor x_1 is
     select col1, col2 from TAB where col1 = '1';
cursor y_1 is
     select col1, col2 from TAB where col2 = '3'

...
begin
for r in main_report_cursor(x_1,y_1) loop
   print_report_results(r);
end loop;

for r in main_report_cursor(x_2,y_2) loop
   print_report_results(r);
end loop;
...

(с использованием Oracle 10g)

Ответы [ 4 ]

4 голосов
/ 17 мая 2011

Используйте конвейерную функцию. Например:

drop table my_tab;
create table my_tab
(
col1 number,
col2 varchar2(10),
col3 char(1)
);
insert into my_tab values (1, 'One', 'X');
insert into my_tab values (1, 'One', 'Y');
insert into my_tab values (2, 'Two', 'X');
insert into my_tab values (2, 'Two', 'Y');
insert into my_tab values (3, 'Three', 'X');
insert into my_tab values (4, 'Four', 'Y');
commit;

-- define types
create or replace package refcur_pkg is
    --type people_tab is table of people%rowtype;
    type my_subquery_tab is table of my_tab%rowtype;
end refcur_pkg;

Создать конвейерную функцию

-- create pipelined function
create or replace function get_tab_data(p_cur_num in number, p_cur_type in char)
return REFCUR_PKG.my_subquery_tab pipelined
IS
    v_ret  REFCUR_PKG.my_subquery_tab;
begin
    if (p_cur_num = 1) then
        if (upper(p_cur_type) = 'X') then
            for rec in (select * from my_tab where col1=1 and col3='X')
            loop
                pipe row(rec);
            end loop;
        elsif (upper(p_cur_type) = 'Y') then
            for rec in (select * from my_tab where col1=1 and col3='Y')
            loop
                pipe row(rec);
            end loop;
        else
            return;
        end if;
    elsif (p_cur_num = 2) then
        if (upper(p_cur_type) = 'X') then
            for rec in (select * from my_tab where col1=2 and col3='X')
            loop
                pipe row(rec);
            end loop;
        elsif (upper(p_cur_type) = 'Y') then
            for rec in (select * from my_tab where col1=2 and col3='Y')
            loop
                pipe row(rec);
            end loop;
        else
            return;
        end if;
    end if;
    return;
end;

Пример основной процедуры

-- main procedure/usage
declare

  cursor sel_cur1 is
    with X as (select * from table(get_tab_data(1, 'x'))),
           Y as (select * from table(get_tab_data(1, 'y')))
    select X.col1, Y.col2 from X,Y where X.col1 = Y.col1;

begin
    for rec in sel_cur1
    loop
        dbms_output.put_line(rec.col1 || ',' ||  rec.col2);
    end loop;
end;

Все ваши различные подзапросы сводятся к вызову одной конвейерной функции, которая определяет возвращаемые строки.

EDIT:

Чтобы объединить все необходимые типы и функции в одну процедуру, а также использовать переменные для параметров функции подзапроса, я добавляю следующий пример:

create or replace procedure my_pipe
IS
    -- define types
    type my_subquery_tab is table of my_tab%rowtype;
    type ref_cur_t is ref cursor;
    v_ref_cur ref_cur_t; 

    -- define vars
    v_with_sql varchar2(4000);
    v_main_sql varchar2(32767);
    v_x1 number;
    v_x2 char;
    v_y1 number;
    v_y2 char;
    v_col1 my_tab.col1%type;
    v_col2 my_tab.col2%type;

    -- define local functions/procs
    function get_tab_data(p_cur_num in number, p_cur_type in char)
    return my_subquery_tab pipelined
    IS
        v_ret  my_subquery_tab;
    begin
        if (p_cur_num = 1) then
            if (upper(p_cur_type) = 'X') then
                for rec in (select * from my_tab where col1=1 and col3='X')
                loop
                    pipe row(rec);
                end loop;
            elsif (upper(p_cur_type) = 'Y') then
                for rec in (select * from my_tab where col1=1 and col3='Y')
                loop
                    pipe row(rec);
                end loop;
            else
                return;
            end if;
        elsif (p_cur_num = 2) then
            if (upper(p_cur_type) = 'X') then
                for rec in (select * from my_tab where col1=2 and col3='X')
                loop
                    pipe row(rec);
                end loop;
            elsif (upper(p_cur_type) = 'Y') then
                for rec in (select * from my_tab where col1=2 and col3='Y')
                loop
                    pipe row(rec);
                end loop;
            else
                return;
            end if;
        end if;
        return;
    end;

BEGIN
    ---------------------------------
    -- Setup SQL for cursors
    ---------------------------------
    -- this will have different parameter values for subqueries
    v_with_sql := q'{
    with X as (select * from table(get_tab_data(:x1, :x2))),
           Y as (select * from table(get_tab_data(:y1, :y2)))
    }';
    -- this will stay the same for all cursors
    v_main_sql := q'{
    select X.col1, Y.col2 from X,Y where X.col1 = Y.col1
    }';

    ---------------------------------
    -- set initial subquery parameters
    ---------------------------------
    v_x1 := 1;
    v_x2 := 'x';
    v_y1 := 1;
    v_y2 := 'y';
    open v_ref_cur for v_with_sql || v_main_sql using v_x1, v_x2, v_y1, v_y2;
    loop
        fetch v_ref_cur into v_col1, v_col2;
        exit when v_ref_cur%notfound;
        dbms_output.put_line(v_col1 || ',' ||  v_col2);
    end loop;
    close v_ref_cur;
    ---------------------------------
    -- change subquery parameters
    ---------------------------------
    v_x1 := 2;
    v_x2 := 'x';
    v_y1 := 2;
    v_y2 := 'y';
    open v_ref_cur for v_with_sql || v_main_sql using v_x1, v_x2, v_y1, v_y2;
    loop
        fetch v_ref_cur into v_col1, v_col2;
        exit when v_ref_cur%notfound;
        dbms_output.put_line(v_col1 || ',' ||  v_col2);
    end loop;
    close v_ref_cur;
end;

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

Приветствия

2 голосов
/ 18 мая 2011
--Create views that will be replaced by common table expressions later.
--The column names have to be the same, the actual content doesn't matter.
create or replace view x as select 'wrong' col1, 'wrong' col2 from dual;
create or replace view y as select 'wrong' col1, 'wrong' col2 from dual;

--Put the repetitive logic in one view
create or replace view main_select as
select count(x.col1) total, x.col2
from X inner join Y on x.col1 = y.col1
group by rollup (x.col1);

--Just querying the view produces the wrong results
select * from main_select;

--But when you add the common table expressions X and Y they override
--the dummy views and produce the real results.
declare
    cursor cursor_1 is
    with X as (select 'right' col1, 'right' col2 from dual),
         Y as (select 'right' col1, 'right' col2 from dual)
    select total, col2 from main_select;
    --... repeat for each cursor, just replace X and Y as necessary
begin
    for r in cursor_1 loop
        dbms_output.put_line(r.col2);
    end loop;
    null;
end;
/

Это решение немного страннее, чем конвейерный подход, и требует 3 новых объектов для представлений, но, вероятно, оно будет работать быстрее поскольку между SQL и PL / SQL меньше переключения контекста.

2 голосов
/ 17 мая 2011

Одной из возможностей, которую вы могли бы рассмотреть, является использование 2 глобальных временных таблиц (GTT) для X и Y. Тогда вам просто нужен один курсор, но вам нужно очистить и повторно заполнить 2 GTT несколько раз - и если объемы данных большие вы также можете каждый раз получать статистику оптимизатора на GTT.

Это то, что я имею в виду:

cursor_gtt is
  select count(X.col1), ...
  from GTT_X inner join GTT_Y on...
  group by rollup (X.col1, ...

begin
    insert into gtt_x select col1, col2 from TAB where col1 = '1';
    insert into gtt_y select col1, col2 from TAB where col2 = '3';
    -- maybe get stats for gtt_x and gtt_y here
    for r in cursor_gtt loop
       print_report_results(r);
    end loop;

    delete gtt_x;
    delete gtt_y;
    insert into gtt_x select col1, col2 from TAB where col1 = '7' and col2 = '9' and col3 = 'TEST';
    insert into gtt_y select col1, col2 from TAB where col3 = '6'
    -- maybe get stats for gtt_x and gtt_y here
    for r in cursor_gtt loop
       print_report_results(r);
    end loop;
    ...
end;

Таким образом, одни и те же 2 GTT повторно заполняются, и каждый раз используется один и тот же курсор.

1 голос
/ 17 мая 2011

А как насчет создания представления для основного запроса?Это настраивает ваш код и централизует основной запрос для загрузки.

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