使用 BULK 集合将 SQL 代码重写为 PL/SQL

Posted

技术标签:

【中文标题】使用 BULK 集合将 SQL 代码重写为 PL/SQL【英文标题】:Rewrite SQL code to PL/SQL using BULK collection 【发布时间】:2014-10-31 11:16:24 【问题描述】:

我在开始学习如何优化 PL/SQL 查询。因此,在我的过程中有两个 FOR LOOPS 和 SQL,如 INSERT 语句。我想用BULK 集合重写它。

CURSOR cur0 IS
    SELECT cal.id, cal.dt, cal.id_period, per.period_name
    FROM xxkkbi_dim_calendar cal, xxkkbi_dim_period per
    WHERE (cal.dt BETWEEN p_start_date AND p_end_date)
    AND per.id = cal.id_period
    ORDER BY cal.dt;

row0          cur0%ROWTYPE;
l_period_name VARCHAR2(20);
l_id_period   NUMBER;
l_id_date     NUMBER;
l_id_budget   NUMBER;
l_budget_code VARCHAR2(2 CHAR);

BEGIN

FOR row0 IN cur0 LOOP
    l_date        := row0.dt;
    l_period_name := row0.period_name;
    l_id_period   := row0.id_period;
    l_id_date     := row0.id;

    IF TO_CHAR(row0.dt, 'dd.mm') = '01.01' THEN
        l_period_name := 'КОР-' || SUBSTR(TO_NUMBER(TO_CHAR(row0.dt, 'YYYY')) - 1, 3);
    END IF;

    FOR row2 IN 
        (SELECT id id_budget, code
        FROM xxkkbi_dim_budget_type
        WHERE id > -1
        AND marker_127 = 'N') 
    LOOP
        l_id_budget   := row2.id_budget;
        l_budget_code := row2.code;

        DELETE FROM fct_5_52
        WHERE id_date = l_id_date
        AND id_budget_type = l_id_budget
        AND id_period = l_id_period;

        INSERT INTO fct_5_52
            (id_date,
            id_period,
            id_gu,
            id_region,
            id_budget_type,
            id_financing_source,
            id_expense_fk,
            id_specific,
            amount_period,
            amount_year)
        SELECT 
            l_id_date AS id_date,
            l_id_period,
            (SELECT gu.id
                FROM xxkkbi_dim_gu gu
                WHERE gu.code = cc.segment6) id_gu,
            (SELECT region.id
                FROM xxkkbi_dim_region region
                WHERE region.code = cc.segment2) id_region,
            (SELECT budget.id
                FROM xxkkbi_dim_budget_type budget
                WHERE budget.code = cc.segment1) id_budget_type,
            (SELECT dfs.id
                FROM xxkkbi_dim_financing_source dfs
                WHERE dfs.code = cc.segment7) id_financing_source,
            (SELECT kbk.id
                FROM xxkkbi_dim_expense_fk kbk
                WHERE kbk.code = cc.segment3) id_expense_fk,
            (SELECT ds.id
                FROM xxkkbi_dim_specific ds
                WHERE ds.code = cc.segment4) id_specific,
            SUM(NVL(jljh.entered, 0)) circle,
            SUM(NVL(bal.begin_balance_dr, 0) - NVL(bal.begin_balance_cr, 0) + NVL(jljh.entered, 0)) saldo
        FROM apps_gl_balances bal
        JOIN apps_gl_code_combinations cc 
        ON (bal.code_combination_id = cc.code_combination_id 
            AND bal.currency_code = 'KZT' 
            AND bal.actual_flag = 'A' 
            AND bal.ledger_id = 2021 
            AND NVL(bal.translated_flag, 'x') IN ('Y', 'N', 'x') 
            AND cc.chart_of_accounts_id = 50408 
            AND cc.template_id IS NULL 
            AND segment5 = '0' 
            AND segment1 between '01' and '03' 
            AND segment3 between '100000000' and '999999999' 
            AND segment4 BETWEEN '000111' AND'000999' 
            AND bal.period_name = l_period_name 
            AND (
                    (NVL(bal.period_net_dr, 0) - NVL(bal.period_net_cr, 0)) != 0 OR
                    (NVL(bal.begin_balance_dr, 0) - NVL(bal.begin_balance_cr, 0)) != 0
                )
            )
        LEFT OUTER JOIN 
            (SELECT 
                jl.code_combination_id,
                SUM(NVL(jl.entered_dr, 0) - NVL(jl.entered_cr, 0)) entered
            FROM 
                gl_gl_je_lines jl, 
                gl_gl_je_headers jh
            WHERE jl.period_name = l_period_name
            AND jl.effective_date <= l_date
            AND jh.je_header_id = jl.je_header_id
            AND jh.period_name = jl.period_name
            AND jh.currency_code = 'KZT'
            AND jh.ledger_id = 2021
            AND jh.actual_flag = 'A'
            GROUP BY jl.code_combination_id) jljh 
        ON 
            (jljh.code_combination_id = cc.code_combination_id)
        GROUP BY 
            cc.segment1,
            cc.segment2,
            cc.segment3,
            cc.segment4,
            cc.segment6,
            cc.segment7;
    COMMIT;
    END LOOP;
END LOOP;

而且我知道我需要将 PL/SQL NVL 函数更改为 SQL COALESCE 函数以减少上下文切换。

【问题讨论】:

【参考方案1】:

您的代码可以在没有循环的情况下在单个 sql 查询中执行 - 一个 INSERT 和一个 DELETE。如果您的 INSERT 语句处理大量数据,您可以按部分划分数据并减少 INSERTs - 据我所知,这正是您所做的。但是在这里使用集合对您没有帮助。带有集合的查询可能与纯 SQL 查询一样有效,但仅此而已。如果您在这里遇到性能问题,则需要在其他地方搜索问题。尝试优化您的 SELECT 子查询。我认为问题就在这里。

您还需要测量执行一个循环的时间。它有多大?如果您有许多(数百或数千)个快速循环(一个循环不到 1 秒),请尝试更改数据部分的大小。

另外,NVL - 是 SQL 函数 (http://docs.oracle.com/cd/B28359_01/server.111/b28286/functions001.htm#SQLRF51171),它不必切换上下文(据我所知)。

【讨论】:

下面的查询执行大约 5 小时

以上是关于使用 BULK 集合将 SQL 代码重写为 PL/SQL的主要内容,如果未能解决你的问题,请参考以下文章

PL/SQL批处理语句BULK COLLECT

带有 BULK COLLECT 的 Oracle PL/SQL 6504

Oracle PL/SQL BULK 从数组更新

为啥 PL/SQL Bulk DML 对具有父子约束表的大型数据集运行缓慢?

在 PL/SQL 中重写 java 代码的挑战

如何在函数中使用 Pl/SQL 集合