如何在 PL/SQL 具有依赖关系的 Oracle 中执行批量更新
How to Perform Bulk Update in PL/SQL Oracle with Dependencies
目前,我正在 运行 PL/SQL Oracle 12.1 中的以下个别更新,并且需要知道如何通过批量更新提高性能,因为它们需要几个小时才能完成,或者任何其他策略。
问题是我需要从具有几十万的 table (LARGE_TBL
) 更新具有相同条件集(那些 CASE WHEN
语句)的多个列记录(MAIN_TBL
本身也有几十万条记录。table 在 LT_ID
和 MT_ID
上都有索引)。
有多个其他 UPDATES
,LT.IDX_2
和 MT.IDX_2
的值不同(为简洁起见,我排除了它们)并且只显示 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
子句不会被执行满意 (MT.STOP_FLAG IS NULL
)。换句话说,这些 UPDATE
的执行顺序很重要。
我没有使用过 PL/SQL 的批量更新功能,所以我不确定如何处理这个问题。我是否应该创建一个游标以在适当的条件下从 LARGE_TBL
table 中获取所有必要的列,例如IDX_2 = G
或IDX_2 = R
,这是一个比较大的table(几十万条记录),然后用BULK COLLECT
将它们取到几个定义的TYPE
中,最后使用单个 FORALL
和多个单独的 UPDATE
语句?或多个 FORALL
,每个 UPDATE
?
如果要使用游标,我如何处理我的 CASE WHEN
语句中的第一项,我需要确定是否存在记录?
你的UPDATE语句看起来很奇怪,试着重写一下。
如果你有像
这样的更新
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
)
那就和
基本一样了
UPDATE MAIN_TBL MT
SET MT.STOP_FLAG = 'SF01'
WHERE {whatever condition}
您的条件可以有效地重写为检查 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;
目前,我正在 运行 PL/SQL Oracle 12.1 中的以下个别更新,并且需要知道如何通过批量更新提高性能,因为它们需要几个小时才能完成,或者任何其他策略。
问题是我需要从具有几十万的 table (LARGE_TBL
) 更新具有相同条件集(那些 CASE WHEN
语句)的多个列记录(MAIN_TBL
本身也有几十万条记录。table 在 LT_ID
和 MT_ID
上都有索引)。
有多个其他 UPDATES
,LT.IDX_2
和 MT.IDX_2
的值不同(为简洁起见,我排除了它们)并且只显示 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
子句不会被执行满意 (MT.STOP_FLAG IS NULL
)。换句话说,这些 UPDATE
的执行顺序很重要。
我没有使用过 PL/SQL 的批量更新功能,所以我不确定如何处理这个问题。我是否应该创建一个游标以在适当的条件下从 LARGE_TBL
table 中获取所有必要的列,例如IDX_2 = G
或IDX_2 = R
,这是一个比较大的table(几十万条记录),然后用BULK COLLECT
将它们取到几个定义的TYPE
中,最后使用单个 FORALL
和多个单独的 UPDATE
语句?或多个 FORALL
,每个 UPDATE
?
如果要使用游标,我如何处理我的 CASE WHEN
语句中的第一项,我需要确定是否存在记录?
你的UPDATE语句看起来很奇怪,试着重写一下。
如果你有像
这样的更新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
)
那就和
基本一样了UPDATE MAIN_TBL MT
SET MT.STOP_FLAG = 'SF01'
WHERE {whatever condition}
您的条件可以有效地重写为检查 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;