Настройка производительности с помощью CONNECT BY LEVEL в запросе - PullRequest
1 голос
/ 26 марта 2012

У меня есть таблица с именем RULE_TABLE, в которой есть столбцы RULE_SEG1 и RULE_SEG2

RULE_SEG1  | RULE_SEG2
-----------------------
????       | 0100?
0200       | 02*
484?       | ????

COST_CENTRE_TABLE

COST_CENTRE
----------- 
0000       
0100
0199
0200        
4841
4842
4842 

NATURAL_ACCOUNT_TABLE

NATURAL_ACCOUNT
---------------
01001
01002
01005
01009
02001
02334
02611
12345
12347
12378
19999

Каждое правило в RULE_SEG1 и RULE_SEG2 должно быть расширено таким образом, если его ????, то оно должно быть расширено с 0000 до 9999;если его 484?, то он должен быть расширен с 4840 до 4849;если его 02*, то он должен быть расширен с 02000 до 02999. Объединенное значение, сгенерированное из RULE_SEG1 и RULE_SEG2, должно быть вставлено в MY_TABLE.Кроме того, значения, сгенерированные из RULE_SEG1 и RULE_SEG2, должны сравниваться со значениями в таблицах COST_CENTRE и NATURAL_ACCOUNT соответственно, только если значение, возвращаемое функцией FV_SEGMENT_DESCRIPTION, равно COST_CENTRE или NATURAL_ACCOUNT.Functon FN_SEGMENT_LENGTH возвращает длину, до которой RULE_SEG1 / RULE_SEG2 должен быть расширен.

Вот фрагмент кода, который вызывает серьезные проблемы с производительностью в Oracle 11g.

        for rec_rule in (select rule_seg1, rule_seg2 from rule_table)   loop                                               
    ln_seg1_len       number := fn_segment_length(rule_seg1);
    ln_seg2_len       number := fn_segment_length(rule_seg2);
    ln_seg1_len_power number := power(10, ln_seg1_len);
    ln_seg2_len_power number := power(10, ln_seg2_len);
    lv_seg_desc1      varchar2(100) := fv_segment_description(rule_seg1);
    lv_seg_desc2      varchar2(100) := fv_segment_description(rule_seg2);

    begin
    for rec_1 in (select b.num seg1
                   from (select a.num
                           from (select lpad(level - 1, ln_seg1_len, '0') as num
                                   from dual
                                 connect by level <= ln_seg1_len_power 
                                 ) a
                          where a.num like replace(rec_rule.rule_seg1, '?', '_')) b
                  where ((lv_seg_desc1 = 'COST_CENTRE' and exists
                         (select 1
                             from cost_centre_tbl c
                            where c.cost_centre = b.num
                              and rownum = 1)) or
                        (lv_seg_desc1 = 'NATURAL_ACCOUNT' and exists
                         (select 1
                             from natural_account_tbl n
                            where n.natural_account = b.num
                              and rownum = 1)) or
                        (lv_seg_desc1 <> 'COST_CENTRE' and
                        lv_seg_desc1 <> 'NATURAL_ACCOUNT'))) loop

     if lv_seg2 is not null then
       for rec_2 in (select b.num seg2
                       from (select a.num
                               from (select lpad(level - 1, ln_seg2_len, '0') as num
                                       from dual
                                     connect by level <= ln_seg2_len_power
                                     ) a
                              where a.num like
                                    replace(replace(rec_rule.rule_seg2, '?', '_'),
                                            '*',
                                            '%')) b
                      where ((lv_seg_desc2 = 'COST_CENTRE' and exists
                             (select 1
                                 from cost_centre_tbl c
                                where c.cost_centre = b.num
                                  and rownum = 1)) or
                            (lv_seg_desc2 = 'NATURAL_ACCOUNT' and exists
                             (select 1
                                 from natural_account_tbl n
                                where n.natural_account = b.num
                                  and rownum = 1)) or
                            (lv_seg_desc2 <> 'COST_CENTRE' and
                            lv_seg_desc3 <> 'NATURAL_ACCOUNT'))) loop

         lv_sourcekey := rec_1.seg1 || rec_2.seg2;

         ltab_map_level_2(l_cntr_level_2).sourcekey := lv_sourcekey;

         l_cntr_level_2 := l_cntr_level_2 + 1;

       end loop; -- rec_2
     end if;
    end loop;

    forall j in l_cntr_level_2 .first .. l_cntr_level_2 .last

    -- insert into staging table
     insert into my_table
     values
       (my_table_s.nextval,
        ltab_map_level_2                (j).sourcekey,
        );
    exception
    when others then
     dbms_output.put_line(sqlerrm);
    end loop;

RULE_TABLE имеет 9800 строк, COST_CENTRE_TABLE имеет около 230 строк.NATURAL_ACCOUNT_TABLE имеет 936 строк.Общее количество строк, которые нужно вставить в MY_TABLE, равно 220000. Существует индекс для COST_CENTRE в COST_CENTRE_TABLE и NATURAL_ACCOUNT в NATURAL_ACCOUNT_TABLE.Запуск программы в экземпляре разработки занимает 11,16 часа.База данных - Oracle 11g Enterprise Edition.Пожалуйста, предложите идеи для настройки кода.Объяснить план не очень помогает, за исключением того факта, что горлышко бутылки, вероятно, связано с УСТАНОВКОЙ УРОВНЯ.

ПОСЛЕ ТОГО, КАК После анализа метки времени данных, вставленных в MY_TABLE, я обнаружил, чтоВыяснилось, что максимальное время для следующих двух случаев:

Случай 1 , когда RULE_SEG1 равно ????, и его необходимо увеличить с 0000 до 9999 Случай 2, когда RULE_SEG2 равно * и его необходимо расширить с 00000 до 99999

    for rec_1 in (select b.num seg1
                   from (select a.num
                           from (select lpad(level - 1, ln_seg1_len, '0') as num
                                   from dual
                                 connect by level <= ln_seg1_len_power 
                                 ) a
                          where a.num like replace(rec_rule.rule_seg1, '?', '_')) b
                  where ((lv_seg_desc1 = 'COST_CENTRE' and exists
                         (select 1
                             from cost_centre_tbl c
                            where c.cost_centre = b.num
                              and rownum = 1)) or
                        (lv_seg_desc1 = 'NATURAL_ACCOUNT' and exists
                         (select 1
                             from natural_account_tbl n
                            where n.natural_account = b.num
                              and rownum = 1)) or
                        (lv_seg_desc1 <> 'COST_CENTRE' and
                        lv_seg_desc1 <> 'NATURAL_ACCOUNT'))) 

Этот цикл расширяет RULE_SEG1 и проверяет, существуют ли результирующие значения в COST_CENTRE_TABLE (еслиlv_seg_desc1 = 'COST_CENTRE').Можно ли спроектировать запрос CONNECT BY LEVEL так, чтобы сначала он проверял значения COST_CENTRE, а затем расширялся.Пожалуйста, предложите !!

1 Ответ

1 голос
/ 03 апреля 2012

Я не думаю, что у нас здесь достаточно информации, чтобы действительно оценивать производительность. Нам нужно было бы объяснить планы и автотрассы и тому подобное, чтобы действительно покопаться. Тем не менее, вот мое сумасшедшее предложение:

Создайте фактическую таблицу со значениями от «0000» до «9999» и от «00000» до «99999», чтобы исключить connect by level часть. Если вы думаете, что это ограничивающий фактор, избавьтесь от него. Создать таблицу ALL_ACCOUNTS с ожидаемыми результатами по вашему запросу. Это всего 110000 записей из 4-5 строк символов. Абсолютно TINY.

Тогда упростите свою логику. Ваш код выглядит очень процедурным, что может повлиять на производительность. Вероятно, вы могли бы сделать большую часть этого в одном SQL. У меня нет базы данных, с которой можно было бы дурачиться, но это может служить руководством:

insert into my_table( id, sourcekey )   
select 
    my_table_s.nextval,
    a.num seg1 || b.num seg2 as lv_sourcekey
from 
    (select num from ALL_ACCOUNTS where num like translate(rec_rule.rule_seg1, '?*', '_%')) a,
    (select num from ALL_ACCOUNTS where num like translate(rec_rule.rule_seg2, '?*', '_%')) b
where
    exists ( -- Conditions for seg1
        select 1
        from (
            select  'COST_CENTRE' as seg_desc,
                    cost_centre as acct
            from    cost_centre_tbl
            union
            select  'NATURAL_ACCOUNT' as seg_desc,
                    natural_account as acct
            from    natural_account_tbl ) comb1
        where lv_seg_desc1 = comb1.seg_desc and
            a.num = comb1.acct
    )
    AND exists ( -- Conditions for seg2
        select 1
        from (
            select  'COST_CENTRE' as seg_desc,
                    cost_centre as acct
            from    cost_centre_tbl
            union
            select  'NATURAL_ACCOUNT' as seg_desc,
                    natural_account as acct
            from    natural_account_tbl ) comb1
        where lv_seg_desc1 = comb1.seg_desc and
            a.num = comb1.acct
    )
;
...