如何在具有依赖关系的PL / SQL Oracle中执行批量更新

时间:2018-10-22 02:45:22

标签: oracle plsql sql-update oracle12c database-performance

当前,我正在PL / SQL Oracle 12.1中运行以下单个更新,并且需要知道如何通过批量更新来提高性能,因为它们需要花费几个小时才能完成,或者需要其他解决方法。

问题是我需要从具有几十万条记录({{1})的表(CASE WHEN)中更新具有相同条件集(那些LARGE_TBL语句)的多列}本身也有几十万条记录。两个表在MAIN_TBLLT_ID上都有索引。

还有多个其他MT_ID,它们的UPDATESLT.IDX_2的值不同(为简便起见,我将它们排除在外),仅显示MT.IDX_2(还有其他相同的{{ 1}}和其他IDX_2 = G值)。

UPDATE

问题是,例如在上述情况下,第二个IDX_2依赖于第一个UPDATE MAIN_TBL MT SET MT.STOP_FLAG = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'SF01' ELSE MT.STOP_FLAG END FROM DUAL ), MT.ES = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'E' ELSE MT.ES END FROM DUAL ), MT.PW = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'W' ELSE MT.PW END FROM DUAL ), MT.UPDATE_DT = SYSDATE WHERE MT.STOP_FLAG IS NULL AND MT.IDX_2 = 'G' AND MT.ES IS NULL AND MT.SS = 'C' AND MT.PW = 'A'; UPDATE MAIN_TBL MT SET MT.STOP_FLAG = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'SF02' ELSE MT.STOP_FLAG END FROM DUAL ), MT.ES = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'E' ELSE MT.ES END FROM DUAL ), MT.PW = ( SELECT CASE WHEN NOT EXISTS (SELECT 1 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') IS NULL OR (SELECT LT.COL_2 FROM LARGE_TBL LT WHERE LT.LT_ID = MT.MT_ID AND LT.IDX_2 = 'G') <> 'Y' THEN 'W' ELSE MT.PW END FROM DUAL ), MT.UPDATE_DT = SYSDATE WHERE MT.STOP_FLAG IS NULL AND MT.IDX_2 = 'G' AND MT.ES IS NULL AND MT.SS = 'C' AND MT.PW = 'A'; ,因为第二个UPDATE仅在UPDATE时才被执行。因此,如果将UPDATE与第一个MT.STOP_FLAG IS NULL(与MT.STOP_FLAG一起设置),则不应执行第二个UPDATE,因为不会MT.STOP_FLAG = SF01子句满意(UPDATE)。换句话说,这些WHERE的执行顺序很重要。

我还没有使用PL / SQL的批量更新功能,所以我不确定该如何处理。我是否应该创建一个游标以在适当条件下从MT.STOP_FLAG IS NULL表中获取所有必要的列,例如UPDATELARGE_TBL,这是一个相对较大的表(数十万条记录),然后使用IDX_2 = G将它们提取到几个已定义的IDX_2 = R中,最后使用一个BULK COLLECT和多个单独的TYPE语句?还是多个FORALL,每个UPDATE一个?

如果要使用游标,如何处理FORALL语句中的第一项,需要确保是否存在记录?

2 个答案:

答案 0 :(得分:2)

您的条件可以有效地重写为检查lt.col_1字段的值不等于'Y'(即lt.col_1 is null or lt.col_1 != 'Y')。我用您以前的检查方法和新的检查方法,给出了一个快速的测试案例来证明是这种情况。

WITH t1 AS (SELECT 1 mt_id, 10 val FROM dual UNION ALL
            SELECT 2 mt_id, 20 val FROM dual UNION ALL
            SELECT 3 mt_id, 30 val FROM dual UNION ALL
            SELECT 4 mt_id, 40 val FROM dual UNION ALL
            SELECT 5 mt_id, 50 val FROM dual),
     t2 AS (SELECT 2 lt_id, 'F' idx_2, NULL col_1 FROM dual UNION ALL
            SELECT 3 lt_id, 'G' idx_2, NULL col_1 FROM dual UNION ALL
            SELECT 4 lt_id, 'G' idx_2, 'N' col_1 FROM dual UNION ALL
            SELECT 5 lt_id, 'G' idx_2, 'Y' col_1 FROM dual)
SELECT 'new_way' qry,
       t1.mt_id,
       t1.val,
       CASE WHEN t2.col_1 is null or t2.col_1 != 'Y' THEN 'SF01' END new_stop_val
FROM   t1
       LEFT OUTER JOIN t2 ON t1.mt_id = t2.lt_id AND idx_2 = 'G'
UNION ALL
SELECT 'old_way' qry,
       t1.mt_id,
       t1.val,
       CASE WHEN NOT EXISTS (SELECT 1 FROM t2 WHERE t2.LT_ID = t1.MT_ID AND t2.IDX_2 = 'G')
                 OR (SELECT t2.COL_1 FROM t2 WHERE t2.LT_ID = t1.MT_ID AND t2.IDX_2 = 'G') IS NULL
                 OR (SELECT t2.COL_1 FROM t2 WHERE t2.LT_ID = t1.MT_ID AND t2.IDX_2 = 'G') <> 'Y'
                 THEN 'SF01'
       END new_stop_val
FROM   t1
ORDER BY mt_ID, qry;

QRY          MT_ID        VAL NEW_STOP_VAL
------- ---------- ---------- ------------
new_way          1         10 SF01
old_way          1         10 SF01
new_way          2         20 SF01
old_way          2         20 SF01
new_way          3         30 SF01
old_way          3         30 SF01
new_way          4         40 SF01
old_way          4         40 SF01
new_way          5         50 
old_way          5         50 

现在,我们可以将large_table上的检查折叠为单个检查,然后我们可以在单个case语句中检查large_table中的其他列。这意味着您不再需要单独的更新语句。您可以像这样在单个合并语句中完成此操作:

MERGE INTO main_table tgt
USING (SELECT mt.rowid, r_id,
              CASE WHEN lt.col_1 is null or lt.col_1 != 'Y' THEN 'SF01'
                   WHEN lt.col_2 is null or lt.col_2 != 'Y' THEN 'SF02'
                   ELSE mt.stop_flag -- null
              END new_stop_flag,
              CASE WHEN NVL(lt.col1, 'N') != 'Y' THEN 'E'
                   WHEN NVL(lt.col2, 'N') != 'Y' THEN 'E'
                   ELSE mt.es -- null
              END new_es,
              CASE WHEN NVL(lt.col_1, 'N') != 'Y' THEN 'W'
                   WHEN NVL(lt.col_2, 'N') != 'Y' THEN 'W'
                   ELSE mt.pw
              END new_pw
       FROM   main_table mt
              LEFT JOIN large_table lt ON (mt.mt_id = lt.lt_id AND lt.idx_2 = 'G')
       WHERE  mt.stop_flag IS NULL
       AND    mt.idx_2 = 'G'
       AND    mt.es IS NULL
       AND    mt.ss = 'C'
       AND    mt.pw = 'A') src
  ON (tgt.rowid = src.r_id)
WHEN MATCHED THEN
  UPDATE tgt.stop_flag = src.new_stop_flag,
         tgt.es = src.es,
         tgt.pw = src.pw;

答案 1 :(得分:1)

您的UPDATE语句看起来很奇怪,请尝试重新编写它。

如果您有类似的更新

order

然后基本上就像

UPDATE MAIN_TBL MT
SET MT.STOP_FLAG = (  
    SELECT 
        CASE 
            WHEN {whatever condition}
            THEN 'SF01'
            ELSE MT.STOP_FLAG
        END
    FROM DUAL
)

以下示例很可能不是有效的解决方案,但它们应为您提供提示,说明如何更好地编写此类更新。

UPDATE MAIN_TBL MT
SET MT.STOP_FLAG = 'SF01'
WHERE {whatever condition}

如果您有类似的更新

UPDATE MAIN_TBL MT
SET
    MT.STOP_FLAG = 'SF01',
    MT.ES = 'E',
    MT.PW = 'W'
    MT.UPDATE_DT = SYSDATE
WHERE 
    MT.STOP_FLAG IS NULL
    AND MT.IDX_2 = 'G'
    AND MT.ES IS NULL
    AND MT.SS = 'C'
    AND MT.PW = 'A'
    AND NOT EXISTS (
        SELECT 1 
        FROM LARGE_TBL LT 
        WHERE LT.LT_ID = MT.MT_ID 
           AND (LT.IDX_2 = 'G' OR LT.COL_1 <> 'Y' OR LT.COL_1 IS NULL)
        );


UPDATE 
    (SELECT MT.*
    FROM MAIN_TBL MT
        JOIN LARGE_TBL LT ON LT.LT_ID = MT.MT_ID
    WHERE LT.IDX_2 = 'G' OR LT.COL_1 <> 'Y' OR LT.COL_1 IS NULL)
SET
    MT.STOP_FLAG = 'SF01',
    MT.ES = 'E',
    MT.PW = 'W'
    MT.UPDATE_DT = SYSDATE
WHERE 
    MT.STOP_FLAG IS NULL
    AND MT.IDX_2 = 'G'
    AND MT.ES IS NULL
    AND MT.SS = 'C'
    AND MT.PW = 'A'

然后基本上与

相同
UPDATE MAIN_TBL MT
SET MT.STOP_FLAG = (  
    SELECT 
        CASE 
            WHEN {whatever condition}
            THEN 'SF01'
            ELSE MT.STOP_FLAG
        END
    FROM DUAL
)