在查询中使用CONNECT BY LEVEL进行性能调整

时间:2012-03-26 15:48:16

标签: sql plsql hierarchical-data

我有一个名为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_SEG1RULE_SEG2中的每个规则必须以????的方式进行扩展,然后必须从0000扩展到9999;如果它是484?,那么它必须从4840扩展到4849;如果它的02*则必须从02000扩展到02999.从RULE_SEG1和RULE_SEG2生成的连接值将被插入MY_TABLE。此外,RULE_SEG1RULE_SEG2生成的值必须分别与COST_CENTRE表和NATURAL_ACCOUNT表中的值进行比较,前提是函数FV_SEGMENT_DESCRIPTION返回的值是等于'COST_CENTRE'或'NATURAL_ACCOUNT'。 Functon FN_SEGMENT_LENGTH返回必须扩展RULE_SEG1 / RULE_SEG2的lenth。

以下是导致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_TABLENATURAL_ACCOUNT中的NATURAL_ACCOUNT_TABLE都有索引。该程序在开发实例中运行需要11.16小时。数据库是Oracle 11g企业版。请提出调整代码的建议。解释计划没有多大帮助,除了瓶颈可能是由于CONNECT BY LEVEL。

事后 在分析插入MY_TABLE的数据的时间戳后,我发现最多花费的时间是以下两种情况:

RULE_SEG1????并且必须从0000扩展到9999时

案例1 RULE_SEG2*并且必须从00000扩展到99999 案例2

    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 个答案:

答案 0 :(得分:1)

我认为我们在这里没有足够的信息来真正判断性能。 我们需要解释计划和autotraces等真正深入挖掘。那就是说,这是我疯狂的建议:

创建一个实际的表,其值为'0000'到'9999'和'00000'到'99999'以消除 connect by level部分。如果您认为这是限制因素,请摆脱它。创建一个表 ALL_ACCOUNTS包含查询的预期结果。它只有110000条4-5个字符串的记录。 绝对有点。

然后,简化你的逻辑。您的代码看起来非常程序化,这可能会影响性能。 您可以在一个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
    )
;