【问题标题】:Converting Cursor statement from SQL Server to Oracle doesn't work将 Cursor 语句从 SQL Server 转换为 Oracle 不起作用
【发布时间】:2016-06-08 08:57:29
【问题描述】:

我使用游标将所有记录从表 CUT_CALENDAR 移动到 SD_CALENDAR(日历迁移)。为此,我在 Sql Server 上使用以下光标:

DECLARE @sdCalendarId NUMERIC(20)
DECLARE @calendarTypId NUMERIC(5)
DECLARE @name NVARCHAR(35)
DECLARE @description NVARCHAR(255)
DECLARE @ptyId NUMERIC(20)
DECLARE @lockCode NVARCHAR(20)
DECLARE @dataOwnerId NUMERIC(20)
DECLARE @cntId NUMERIC(20)
DECLARE @nonBusinessDaysMonday CHAR(1)
DECLARE @nonBusinessDaysTuesday CHAR(1)
DECLARE @nonBusinessDaysWednesday CHAR(1)
DECLARE @nonBusinessDaysThursday CHAR(1)
DECLARE @nonBusinessDaysFriday CHAR(1)
DECLARE @nonBusinessDaysSaturday CHAR(1)
DECLARE @nonBusinessDaysSunday CHAR(1)
DECLARE @ccyId NUMERIC(20)
DECLARE @code NVARCHAR(30)
DECLARE @version NUMERIC(10)
DECLARE @seal VARCHAR(255)
DECLARE @lstUpdTs DATETIME



DECLARE cursorCutoffCalendar CURSOR FOR
  SELECT ID, NAME, CALENDAR_TYP_ID,DESCRIPTION,'Y',PTY_ID,LOCK_CODE,DATA_OWNER_ID,CNT_ID,NON_BUSINESS_DAYS_MONDAY,NON_BUSINESS_DAYS_TUESDAY,NON_BUSINESS_DAYS_WEDNESDAY,NON_BUSINESS_DAYS_THURSDAY,NON_BUSINESS_DAYS_FRIDAY,NON_BUSINESS_DAYS_SATURDAY,NON_BUSINESS_DAYS_SUNDAY,CCY_ID,CODE,VERSION,SEAL,LST_UPD_TS
  FROM CUT_CALENDAR
  WHERE ID != 1

OPEN cursorCutoffCalendar
FETCH NEXT FROM cursorCutoffCalendar INTO @sdCalendarId, @calendarTypId, @name, @description, @ptyId, @lockCode, @dataOwnerId, @cntId, @nonBusinessDaysMonday, @nonBusinessDaysTuesday, @nonBusinessDaysWednesday, @nonBusinessDaysThursday, @nonBusinessDaysFriday, @nonBusinessDaysSaturday, @nonBusinessDaysSunday, @nonBusinessDaysMonday, @ccyId, @code, @version, @seal, @lstUpdTs
WHILE @@FETCH_STATUS = 0
BEGIN
    SELECT @sdCalendarId = COALESCE(MAX(ID),1) FROM SD_CALENDAR

    SET @sdCalendarId = @sdCalendarId + 1

    INSERT INTO SD_CALENDAR (ID, NAME, CALENDAR_ROLE_ID,DESCRIPTION,USE_IN_CUTOFF,PTY_ID,LOCK_CODE,DATA_OWNER_ID,CNT_ID,NON_BUSINESS_DAYS_MONDAY,NON_BUSINESS_DAYS_TUESDAY,NON_BUSINESS_DAYS_WEDNESDAY,NON_BUSINESS_DAYS_THURSDAY,NON_BUSINESS_DAYS_FRIDAY,NON_BUSINESS_DAYS_SATURDAY,NON_BUSINESS_DAYS_SUNDAY,CCY_ID,CODE,VERSION,SEAL,LST_UPD_TS)
    VALUES(@sdCalendarId, @name, @calendarTypId,@description,'Y',@ptyId,@lockCode,@dataOwnerId,@cntId,@nonBusinessDaysMonday,@nonBusinessDaysTuesday,@nonBusinessDaysWednesday,@nonBusinessDaysThursday,@nonBusinessDaysFriday,@nonBusinessDaysSaturday,@nonBusinessDaysSunday,@ccyId,@code,@version,@seal,@lstUpdTs)
END
CLOSE cursorCutoffCalendar
DEALLOCATE cursorCutoffCalendar
GO

因为我也想支持Oracle架构,所以我尝试将这个SqlServer游标转换为如下Oracle游标:

    DECLARE v_sdCalendarId NUMERIC(20);
     v_calendarTypId NUMERIC(5);
     v_name VARCHAR2(35);
     v_description VARCHAR2(255);
     v_ptyId NUMERIC(20);
     v_lockCode VARCHAR2(20);
     v_dataOwnerId NUMERIC(20);
     v_cntId NUMERIC(20);
     v_nonBusinessDaysMonday CHAR(1);
     v_nonBusinessDaysTuesday CHAR(1);
     v_nonBusinessDaysWednesday CHAR(1);
     v_nonBusinessDaysThursday CHAR(1);
     v_nonBusinessDaysFriday CHAR(1);
     v_nonBusinessDaysSaturday CHAR(1);
     v_nonBusinessDaysSunday CHAR(1);
     v_ccyId NUMERIC(20);
     v_code VARCHAR2(30);
     v_version NUMERIC(10);
     v_seal VARCHAR(255);
     v_lstUpdTs DATE;
    CURSOR cursorCutoffCalendar IS
        SELECT ID, NAME, CALENDAR_TYP_ID,DESCRIPTION,'Y',PTY_ID,LOCK_CODE,DATA_OWNER_ID,CNT_ID,NON_BUSINESS_DAYS_MONDAY,NON_BUSINESS_DAYS_TUESDAY,NON_BUSINESS_DAYS_WEDNESDAY,NON_BUSINESS_DAYS_THURSDAY,NON_BUSINESS_DAYS_FRIDAY,NON_BUSINESS_DAYS_SATURDAY,NON_BUSINESS_DAYS_SUNDAY,CCY_ID,CODE,VERSION,SEAL,LST_UPD_TS
        FROM CUT_CALENDAR
        WHERE ID != 1;

 BEGIN
    FOR calendar IN cursorCutoffCalendar
    LOOP
        SELECT COALESCE(MAX(ID),1) into v_sdCalendarId from SD_CALENDAR;

                    INSERT INTO SD_CALENDAR (ID, NAME, CALENDAR_ROLE_ID,DESCRIPTION,USE_IN_CUTOFF,PTY_ID,LOCK_CODE,DATA_OWNER_ID,CNT_ID,NON_BUSINESS_DAYS_MONDAY,NON_BUSINESS_DAYS_TUESDAY,NON_BUSINESS_DAYS_WEDNESDAY,NON_BUSINESS_DAYS_THURSDAY,NON_BUSINESS_DAYS_FRIDAY,NON_BUSINESS_DAYS_SATURDAY,NON_BUSINESS_DAYS_SUNDAY,CCY_ID,CODE,VERSION,SEAL,LST_UPD_TS)
                    VALUES(v_sdCalendarId, v_name, v_calendarTypId,v_description,'Y',v_ptyId,v_lockCode,v_dataOwnerId,v_cntId,v_nonBusinessDaysMonday,v_nonBusinessDaysTuesday,v_nonBusinessDaysWednesday,v_nonBusinessDaysThursday,v_nonBusinessDaysFriday,v_nonBusinessDaysSaturday,v_nonBusinessDaysSunday,v_ccyId,v_code,v_version,v_seal,v_lstUpdTs);
    END LOOP;
CLOSE cursorCutoffCalendar;

END;
/

ORA-01001: 无效游标 ORA-06512: 在第 34 行

我的 oracle 光标脚本有什么问题?

【问题讨论】:

    标签: sql-server oracle cursor


    【解决方案1】:

    您的程序有很多问题:

    1. 您正在获取一个游标,但您没有将值存储在任何地方。这是你的错误的原因。您需要将值提取到游标行类型变量或单个变量中。
    2. 如果您绝对必须对游标执行循环,则使用 CURSOR FOR LOOP 会容易得多。
    3. 您正在使用 max(val) + 1 来填充(可能)唯一的 id 列。这不是您应该做的事情——尤其是在有其他并发事务的情况下。相反,您应该使用一个序列,例如:

    .

    create sequence sd_calendar_seq
      START WITH 261 -- something that's higher than the max id of sd_calendar
      MAXVALUE 999999999999999999999999999
      MINVALUE 1
      NOCYCLE
      CACHE 20
      NOORDER;
    
    1. 您根本不需要用于循环的游标——事实上,这样做的速度很慢。您可以只使用一个插入语句一举完成所有工作:

    .

    insert into sd_calendar (id,
                             name,
                             calendar_role_id,
                             description,
                             use_in_cutoff,
                             pty_id,
                             lock_code,
                             data_owner_id,
                             cnt_id,
                             non_business_days_monday,
                             non_business_days_tuesday,
                             non_business_days_wednesday,
                             non_business_days_thursday,
                             non_business_days_friday,
                             non_business_days_saturday,
                             non_business_days_sunday,
                             ccy_id,
                             code,
                             version,
                             seal,
                             lst_upd_ts)
      SELECT sd_calendar_seq.nextval,
             name,
             calendar_typ_id,
             description,
             'Y',
             pty_id,
             lock_code,
             data_owner_id,
             cnt_id,
             non_business_days_monday,
             non_business_days_tuesday,
             non_business_days_wednesday,
             non_business_days_thursday,
             non_business_days_friday,
             non_business_days_saturday,
             non_business_days_sunday,
             ccy_id,
             code,
             version,
             seal,
             lst_upd_ts
      from   cut_calendar
      where  id != 1;
    

    查看您更新的代码,还有一些其他问题:

    1. 您选择变量的语法不正确。相反,它应该是:select coalesce(max(id),1) into v_sdcalendarid from sd_calendar;
    2. 您设置变量值的语法不正确。应该是v_sdcalendarid := v_sdcalendarid + 1;
    3. 现在您已经定义了游标记录 (calendar),您不需要所有单独的变量。此外,您需要更改要插入的值以引用记录字段。
    4. 当您使用游标进行循环时,您无需显式打开或关闭游标。

    这意味着您的代码应该类似于:

    declare
      v_sdcalendarid numeric (20);
    
      cursor cursorcutoffcalendar
      is
        select id,
               name,
               calendar_typ_id,
               description,
               'Y' use_in_cutoff,
               pty_id,
               lock_code,
               data_owner_id,
               cnt_id,
               non_business_days_monday,
               non_business_days_tuesday,
               non_business_days_wednesday,
               non_business_days_thursday,
               non_business_days_friday,
               non_business_days_saturday,
               non_business_days_sunday,
               ccy_id,
               code,
               version,
               seal,
               lst_upd_ts
        from   cut_calendar
        where  id != 1;
    begin
      for calendar in cursorcutoffcalendar
      loop
        select coalesce (max (id), 1)
        into   v_sdcalendarid
        from   sd_calendar;
    
        v_sdcalendarid := v_sdcalendarid + 1;
    
        insert into sd_calendar (id,
                                 name,
                                 calendar_role_id,
                                 description,
                                 use_in_cutoff,
                                 pty_id,
                                 lock_code,
                                 data_owner_id,
                                 cnt_id,
                                 non_business_days_monday,
                                 non_business_days_tuesday,
                                 non_business_days_wednesday,
                                 non_business_days_thursday,
                                 non_business_days_friday,
                                 non_business_days_saturday,
                                 non_business_days_sunday,
                                 ccy_id,
                                 code,
                                 version,
                                 seal,
                                 lst_upd_ts)
        values      (v_sdcalendarid,
                     calendar.name,
                     calendar.calendartypid,
                     calendar.description,
                     calendar.use_in_cutoff,
                     calendar.ptyid,
                     calendar.lockcode,
                     calendar.dataownerid,
                     calendar.cntid,
                     calendar.nonbusinessdaysmonday,
                     calendar.nonbusinessdaystuesday,
                     calendar.nonbusinessdayswednesday,
                     calendar.nonbusinessdaysthursday,
                     calendar.nonbusinessdaysfriday,
                     calendar.nonbusinessdayssaturday,
                     calendar.nonbusinessdayssunday,
                     calendar.ccyid,
                     calendar.code,
                     calendar.version,
                     calendar.seal,
                     calendar.lstupdts);
      end loop;
    end;
    /
    

    但是,我会强烈在这里推荐两件事:

    1. 使用序列生成 id 值
    2. 在单个插入语句中执行此操作,就像我在上面显示的那样。

    如果出于某种原因(为什么不?)您不能使用序列,您仍然可以执行以下操作:

    insert into sd_calendar (id,
                             name,
                             calendar_role_id,
                             description,
                             use_in_cutoff,
                             pty_id,
                             lock_code,
                             data_owner_id,
                             cnt_id,
                             non_business_days_monday,
                             non_business_days_tuesday,
                             non_business_days_wednesday,
                             non_business_days_thursday,
                             non_business_days_friday,
                             non_business_days_saturday,
                             non_business_days_sunday,
                             ccy_id,
                             code,
                             version,
                             seal,
                             lst_upd_ts)
      select (select coalesce (max (id), 1) from sd_calendar) + rownum id,
             name,
             calendar_typ_id,
             description,
             'Y',
             pty_id,
             lock_code,
             data_owner_id,
             cnt_id,
             non_business_days_monday,
             non_business_days_tuesday,
             non_business_days_wednesday,
             non_business_days_thursday,
             non_business_days_friday,
             non_business_days_saturday,
             non_business_days_sunday,
             ccy_id,
             code,
             version,
             seal,
             lst_upd_ts
      from   cut_calendar
      where  id != 1;
    

    我推荐单 sql 语句方法的原因是它更易于维护(代码少得多!),更易于测试(您可以自行运行 SQL 语句以查看将插入哪些行)并且它'将比围绕光标的相应循环更快。这是因为关系数据库非常擅长处理大量数据 - 这是它们的设计目的。

    但是,当您循环游标(并基于游标执行 DML)时,您正在做的是:

    1. 从 PL/SQL 引擎调用 SQL 引擎以获取行。
    2. 然后返回到 PL/SQL 引擎对结果进行处理。
    3. 然后返回 SQL 引擎运行 DML 语句。
    4. 然后返回到 PL/SQL 引擎获取下一行
    5. 重复步骤 2-4,直到找不到更多行。

    这是非常多的上下文切换。为什么不通过使用单个 DML 语句批量处理整个数据集来完全避免这种情况。如果您愿意,您甚至可以将该语句嵌入到 PL/SQL 中!

    【讨论】:

    • 我又遇到了一个新问题,我已经更新了我的答案,你能再检查一次吗?
    • @Boerejef 来自我上面的回答:4. When you use a cursor for loop, you don't need to explicitly open or close the cursor.... 所以,删除关闭光标语句。出于好奇,为什么您仍然坚持使用 cursor-for-loop 方法而不是使用单个插入语句?
    猜你喜欢
    • 1970-01-01
    • 2013-04-19
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-08
    • 1970-01-01
    相关资源
    最近更新 更多