【发布时间】:2014-12-31 15:29:30
【问题描述】:
我正在创建一个循环遍历表(有数千行)的存储过程,并且对于每一行都有一个 CLOB 列,我想从中获取正则表达式的所有匹配项(sa“FNR”)。此后,我想在新表中插入每个 distinct 匹配项。单个 CLOB 列可能包含数千个匹配项,但通常它是在 CLOB 中重复的相同“FNR” - 即,不同的正则表达式匹配项要少得多,而这些是我唯一感兴趣的。 然而,我所做的过程花费了非常长的时间,我怀疑循环遍历所有匹配项是最耗时的部分。
我的程序如下所示:
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR AS
BEGIN
DECLARE
v_n NUMBER;
v_cnt NUMBER;
v_mtrid NUMBER;
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_doc CLOB;
v_fnr VARCHAR2(11);
BEGIN
-- Get all rows from table --
SELECT COUNT(*) INTO v_n FROM TABLE;
IF v_n > 0 THEN
-- Loop over all rows --
FOR i IN 1..v_n LOOP
SELECT doc, mtrid
INTO v_doc, v_mtrid
FROM (SELECT DOC doc, ID mtrid, ROWNUM rnum
FROM TABLE
WHERE ROWNUM <=i)
WHERE rnum >= i;
IF v_doc IS NOT NULL THEN
SELECT REGEXP_COUNT(v_doc, v_regex_fnr) INTO v_cnt FROM DUAL;
IF v_cnt >= 1 THEN
-- For each regex match - time consuming, right? --
FOR j IN 1..v_cnt LOOP
SELECT REGEXP_SUBSTR(v_doc, v_regex_fnr, 1, j, 'm') INTO v_fnr FROM DUAL;
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
INSERT INTO TABLE2(MTR_ID, FNR)
SELECT v_mtrid, v_fnr FROM DUAL;
END IF;
END LOOP;
END IF;
END IF;
COMMIT;
END LOOP;
END IF;
END LOOP;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
/
有人知道如何优化这个过程吗?
我使用的是 Oracle 11.2.0.3.0。 (顺便说一句,我知道 ctx_entity-package,但在这个版本上它被禁用了。不过,我正在考虑启用它。)
更新
在应用 nop77svk 提供的非常有用的性能优化技术后,我可以肯定地说 regexp_substr() 超过 CLOB 是瓶颈,因为不幸的是没有性能改进。
但是,我想出了一个“hack/workaround”,我将regexp_substr() 调用的数量降到最低,并带来了巨大的性能提升。首先,我想制作一个增量“训练”的正则表达式,不包括以前的匹配,但由于 Oracle 不支持负前瞻,所以这不起作用。我最终保存了CLOB,并使用regexp_replace() 删除了所有匹配项。由于 CLOB 中出现了很多相同的情况,这使该过程免于大量 regexp_substr() 调用,并且同时处理了 distinct 要求。
下面是我的结果,基于 nop77svk 的贡献。是的,我又在MERGE 语句中使用DUAL,但是这里有什么办法吗?
CREATE OR REPLACE PROCEDURE SP_MTV_FINN_FNR2 AS
BEGIN
DECLARE
v_regex_fnr VARCHAR2(54) := '(((0[1-9]|[12]\d|3[01])(0[1-9]|1[012])(\d{2}))(\d{5}))';
v_fnr VARCHAR2(11);
v_doc CLOB;
type rec_table2 is record (
mtr_id table2.mtr_id%type,
fnr table2.fnr%type
);
type arr_table2 is table of rec_table2 index by simple_integer;
table2_bulk arr_table2;
table2_row rec_table2;
BEGIN
FOR rec IN (
select doc, MTR_ID as mtrid
from TABLE
where DOC is not null
) LOOP
v_doc := rec.doc;
loop
v_fnr := REGEXP_SUBSTR(v_doc, v_regex_fnr, 1, 1, 'm');
exit when v_fnr is null;
v_vedlegg := REGEXP_REPLACE(v_doc, v_fnr , '' , 1 , 0); -- Incrementally remove all occurences of match from doc --
IF CHECK_FNR(v_fnr) = 'TRUE' THEN
table2_row.mtr_id := rec.mtrid;
table2_row.fnr := v_fnr;
table2_bulk(table2_bulk.count+1) := table2_row;
END IF;
END LOOP;
END LOOP;
forall i in indices of table2_bulk
MERGE INTO TABLE2 T
USING (SELECT table2_bulk(i).mtr_id mtrid, table2_bulk(i).fnr fnr FROM DUAL) B
ON (T.MTR_ID = B.mtrid AND T.FNR = B.fnr)
WHEN NOT MATCHED THEN INSERT (T.MTR_ID, T.FNR)
VALUES (B.mtrid, B.fnr);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error - rollback');
DBMS_OUTPUT.PUT_LINE('The error code is ' || SQLCODE || '- ' || SQLERRM);
ROLLBACK;
END;
END;
/
【问题讨论】:
-
从删除
DUAL表调用开始。 例如:v_cnt := REGEXP_COUNT(v_doc, v_regex_fnr) -
不要怀疑循环是慢的部分,检查一下。尽管嵌套循环通常是个坏主意。您还进行了不必要的连接切换。您实际上插入了多少行?
check_fnr在做什么?您可以将值放入一个集合中以消除重复项,然后在一个批量操作中插入所有内容,或者使用连接循环来提取匹配项等;但您需要测试阻塞点的实际位置。 -
谢谢你们俩。插入的行数取决于正则表达式匹配的数量,但总共可能有几千个。 Check_fnr 正在对“fnr”进行校验和检查,因为正则表达式不足以识别匹配是否实际上是国家识别号。 check_fnr 过程通常需要 0.001-0.003s。
标签: regex oracle performance loops distinct