Oracle游标sql语句代码块的优化
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle游标sql语句代码块的优化相关的知识,希望对你有一定的参考价值。
参考技术A游标操作的优化:
-- 有一个表格,存储的是用户的名字,将 emp 表中所有的用户名转换成小写,再写入这个表格
create table ename_emp(
ename varchar2(50)
);
-- 下面这个游标的操作,需要 43s时间才能完成
declare
begin
for i in (select ename from emp_liebiao) loop
insert into ename_emp values(lower(i.ename));
commit;
end loop;
end;
-- 因为游标是以行为单位进行数据的操作的,所有游标的效率是比较慢的,而且游标需要消耗的内存也是比较多的,我们需要将游标以行进行操作的方式,修改成一次性操作所有数据的批量的方式。
批量操作在游标里面 叫做 bulk collect
第一步,创建一个表类型
type 表类型的名字 is table of 表名.列名 %type;
变量名 表类型的名字;
第三步,创建一个游标,读取某个查询的结果
cursor 游标名字 is select 查询语句 ;
第四步,打开游标
open 游标名字;
第五步,捕获游标的数据,将内容给到表类型的变量进行保存
fetch 游标名字 bulk collect into 变量名字 ;
第六步,使用 forall 语句,对数据进行批量的操作
forall i in 变量 .first .. 变量 .last DML 语句操作 ;
第七步,关闭游标
close 游标名字 ;
declare
-- 创建表类型
type biao is table of emp_liebiao.ename%type;
b biao;
-- 创建游标
cursor m is select ename from emp_liebiao;
begin
-- 打开游标
open m;
-- 将游标的内容批量的给到变量
fetch m bulk collect into b;
-- 使用forall批量修改数据
forall i in b.first .. b.last insert into ename_emp values(lower(b(i)));
commit;
-- 关闭游标
close m;
end;
练习:批量修改用户的编号,让编号+工资等级+部门,形成一个新的编号,存入下面的表格中,使用 bulk collect 来实现。
2000以下是 C ,2000-3000是 B ,3000以上是 A ,
例如 SMITH , 新编号应该是 7369_C_20
create table empno_emp(
empno varchar2(50)
);
declare
type biao is table of emp_liebiao%rowtype;
b biao;
cursor m is select * from emp_liebiao;
begin
open m;
fetch m bulk collect into b;
forall i in b.first..b.last
insert into empno_emp values(
b(i).empno||\'_\'||decode(sign(b(i).sal-2000)+sign(b(i).sal-3000),-2,\'C\',2,\'A\',\'B\')||\'_\'||b(i).deptno
);
commit;
close m;
end;
如何优化使用游标的 PL/SQL 代码
【中文标题】如何优化使用游标的 PL/SQL 代码【英文标题】:How to optimize the PL/SQL code that uses cursor 【发布时间】:2021-01-20 20:08:29 【问题描述】:我正在尝试优化运行大约 3 小时的查询以获取/更新 280 万条记录。该代码使用游标,我们需要更新整个 280 万条记录集。我尝试使用批量收集修改查询,但我认为它没有多大帮助。有人可以告诉我我还能做些什么来优化代码。老实说,我认为即使批量收集也不会更好......我正在使用 oracle19c
旧代码是:
DECLARE
CURSOR PROVNUMCURSOR
IS
SELECT DISTINCT RCRD_NBR, ID, Entity FROM SPSMDMRW.GTT_SCW_PROFILE_INTRM ;
v_FirstRow SPSMDMRW.GTT_SCW_PROFILE_INTRM.RCRD_NBR%type;
v_ID SPSMDMRW.GTT_SCW_PROFILE_INTRM.ID%type;
v_Entity SPSMDMRW.GTT_SCW_PROFILE_INTRM.Entity%type;
BEGIN
OPEN PROVNUMCURSOR;
--v_FirstRow:=0;v_ID:=0;v_Entity:='0';
FETCH PROVNUMCURSOR
INTO v_FirstRow,
v_ID,
v_Entity;
WHILE PROVNUMCURSOR%FOUND
LOOP
SELECT
CASE
WHEN v_ID = v_EndRow
THEN v_MaxRow
ELSE
(SELECT RCRD_NBR-1 FROM SPSMDMRW.GTT_SCW_PROFILE_INTRM WHERE ID = v_ID+1
)
END
INTO v_LastRow
FROM dual;
IF v_Entity = '3' THEN
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
||'I'
WHERE RCRD_NBR BETWEEN v_FirstRow AND v_LastRow;
ELSE
IF v_Entity = '4' THEN
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
|| 'II'
WHERE RCRD_NBR BETWEEN v_FirstRow AND v_LastRow;
ELSE
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
WHERE RCRD_NBR BETWEEN v_FirstRow AND v_LastRow;
END IF;
END IF;
FETCH PROVNUMCURSOR INTO v_FirstRow, v_ID, v_Entity;
EXIT
WHEN PROVNUMCURSOR%NOTFOUND;
END LOOP;
--END;
CLOSE PROVNUMCURSOR;
END;
我修改的代码如下:
DECLARE
CURSOR PROVNUMCURSOR
IS
SELECT DISTINCT RCRD_NBR,
ID,
Entity
FROM SPSMDMRW.GTT_SCW_PROFILE_INTRM_TEST ;
type v_ID_type
IS
TABLE OF SPSMDMRW.GTT_SCW_PROFILE_INTRM_TEST.ID%type;
type v_Entity_type
IS
TABLE OF SPSMDMRW.GTT_SCW_PROFILE_INTRM_TEST.Entity%type;
type v_FirstRow_type
IS
TABLE OF SPSMDMRW.GTT_SCW_PROFILE_INTRM_TEST.RCRD_NBR%type;
v_FirstRow v_FirstRow_type;
v_ID v_ID_type;
v_Entity v_Entity_type;
c_limit PLS_INTEGER := 100;
BEGIN
OPEN PROVNUMCURSOR;
--v_FirstRow:=0;v_ID:=0;v_Entity:='0';
LOOP
FETCH PROVNUMCURSOR bulk collect INTO v_FirstRow, v_ID,v_Entity LIMIT C_LIMIT;
EXIT
WHEN V_FIRSTROW.COUNT = 0;
--dbms_output.put_line(V_FIRSTROW.COUNT);
FOR INDX IN 1..V_FIRSTROW.COUNT
LOOP
-- dbms_output.put_line(1);
SELECT
CASE
WHEN v_ID(INDX) = v_EndRow
THEN v_MaxRow
ELSE
(SELECT RCRD_NBR-1
FROM SPSMDMRW.GTT_SCW_PROFILE_INTRM_TEST
WHERE ID = v_ID(INDX)+1
)
END
INTO v_LastRow
FROM dual;
IF v_Entity(INDX) = '3' THEN
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF_TEST a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
||'I'
WHERE RCRD_NBR BETWEEN v_FirstRow(INDX) AND v_LastRow;
ELSIF v_Entity(INDX) = '4' THEN
--DBMS_OUTPUT.PUT_LINE(v_entity || ' First Row '|| v_FirstRow || ' Last row '||v_LastRow);
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF_TEST a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
|| 'II'
WHERE RCRD_NBR BETWEEN v_FirstRow(INDX) AND v_LastRow;
ELSE
-- DBMS_OUTPUT.PUT_LINE(v_entity || ' First Row '|| v_FirstRow || ' Last row '||v_LastRow);
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF_TEST a
SET ProvNum = LTRIM(RTRIM(TO_CHAR(SUBSTR(Column0, 1, 13))))
WHERE RCRD_NBR BETWEEN v_FirstRow(INDX) AND v_LastRow;
END IF;
END LOOP;
END LOOP;
CLOSE PROVNUMCURSOR;
END;
Sample data:
RCRD_NBR FILE_NM PROVNUM ID ENTITY
225184 sample.txt 1681 2
225241 sample.txt 1682 3
225352 sample.txt 1683 4
225436 sample.txt 1684 5
225493 sample.txt 1685 2
【问题讨论】:
请提供一些示例数据。我假设您可以在一个 UPDATE 语句中完成所有操作。没有游标,没有循环,没有批量获取。 除此之外,您是否进行了任何分析以查看实际花费的时间?例如,您完全有可能缺少索引/表上有一个触发器/有一个未索引的外键实际上导致性能变慢。 什么是v_MaxRow
?它没有在您的代码中声明,即它根本不应该工作。
你有GTT_SCW_SSB_SC_PDF_TEST.RCRD_NBR
的索引吗?
@JustinCave 表在 rcrd_nbr 上建立索引,表上没有触发器。此外,没有未索引的外键。我能够找到的问题是代码中的更新语句需要花费大量时间。我在蟾蜍中观察到了这一点
【参考方案1】:
如果没有任何示例数据并且不知道逻辑,很难提供解决方案。我认为这可以通过一个单一的声明来完成,该声明的方向如下:
UPDATE SPSMDMRW.GTT_SCW_SSB_SC_PDF a SET ProvNum =
with t as
(SELECT DISTINCT
RCRD_NBR as FirstRow,
LEAD(RCRD_NBR, 1) -1 OVER (ORDER BY ID) AS LastRow,
CASE Entity
when '3' then TRIM(SUBSTR(Column0, 1, 13))||'I'
when '4' then TRIM(SUBSTR(Column0, 1, 13))||'II'
else TRIM(SUBSTR(Column0, 1, 13))
end case as ProvNum
FROM SPSMDMRW.GTT_SCW_PROFILE_INTRM)
SELECT t.ProvNum
from t
where a.RCRD_NBR BETWEEN FirstRow AND LastRow;
【讨论】:
以上是关于Oracle游标sql语句代码块的优化的主要内容,如果未能解决你的问题,请参考以下文章