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语句代码块的优化的主要内容,如果未能解决你的问题,请参考以下文章

oracle 游标操作 怎么判断SQL语句是不是查出数据

快速掌握Oracle数据库游标的使用方法有哪些?

oracle PL/SQL编程语言之游标的使用

oracle PL/SQL编程语言之游标的使用

如何使用Oracle的游标?

Oracle 游标