【问题标题】:Strange behaviours with oracle nested cursorsoracle嵌套游标的奇怪行为
【发布时间】:2012-09-12 08:31:39
【问题描述】:

下面是我写的使用嵌套游标的存储过程。

create or replace
PROCEDURE SP_RUN_EMPLOYEE_UPDATES 
(
  IN_DATE IN VARCHAr2
) 
IS

update_sql varchar2(4000); 

employee_id BI_EMPLOYEE_UPDATE.employee_id%TYPE;   

effective_date date ; 
created_by number;
created_on date;
comments varchar2(4000);

CURSOR 
  employees 
IS
  SELECT distinct(employee_id) FROM BI_EMPLOYEE_UPDATE WHERE EFFECTIVE_DATE = to_date(IN_DATE,'dd-mm-yy') AND EXECUTED = 'N' AND ACTIVITY_ID = '0';

CURSOR 
  e_updates 
IS
  SELECT * FROM BI_EMPLOYEE_UPDATE WHERE EFFECTIVE_DATE = to_date(IN_DATE,'dd-mm-yy') AND EXECUTED = 'N' AND ACTIVITY_ID = '0' and employee_id = employee_id ;

BEGIN

OPEN employees;

    LOOP

      effective_date := '';
      created_by := '';
      created_on := '';
      comments := '';
      employee_id := ''; 

      FETCH employees into employee_id;
      EXIT WHEN employees%NOTFOUND;

        update_sql :=  'UPDATE BI_EMPLOYEE SET ';
        FOR e_update in e_updates
          LOOP

            select comments, effective_date , changed_by, changed_on into  comments, effective_date , created_by, created_on 
            from bi_employee_update where EMPLOYEE_UPDATE_ID = e_update.EMPLOYEE_UPDATE_ID; 

            update_sql := update_sql || e_update.column_name || ' = ''' || e_update.new_value || ''' , ' ; 

            UPDATE BI_EMPLOYEE_UPDATE
            SET 
              EXECUTED = 'Y'
            WHERE 
              EMPLOYEE_UPDATE_ID = e_update.EMPLOYEE_UPDATE_ID ;

          END LOOP;

          update_sql := update_sql || ' comments  = ''' || comments || ''', updated_by  = ''' || created_by  || ''',  updated_on  = ''' || created_on ||  ''',  effective_date = ''' || effective_date  || '''';  
          update_sql := update_sql || ' WHERE emp_id = ' || employee_id ;  

       dbms_output.put_line('KKKK '||update_sql);
        execute immediate update_sql ; 

    END LOOP;
    CLOSE employees;

 END;

问题出在第二个游标中,我将所有先前游标的数据组合在一起。

例如如果第一次迭代应该返回 a,第二次应该返回 b。但在实际的第一次迭代中返回 a、b,第二次也返回 a、b。

下面是生成的动态查询,完全相同。

第一次迭代

预期(正确):

UPDATE BI_EMPLOYEE SET EMPLOYEE_ID = '1111111111111' , PP_NUMBER = '22222222222' ,
    CORPORATE_TITLE_ID = '2' ,  comments  = 'c11', updated_by  = '361',
    updated_on  = '12-SEP-12',  effective_date = '25-SEP-12' WHERE emp_id = 18010

实际(错误):

UPDATE BI_EMPLOYEE SET EMPLOYEE_ID = '1111111111111' , PP_NUMBER = '22222222222' ,
    CORPORATE_TITLE_ID = '2' , LASTNAME = 'Ll22 edited ' , OFFSHORE_ONSHORE = '1' ,
    ONSHORE_REGION = '1' , ONSHORE_DESK_MANAGER = 'henrry ' ,
    comments  = 'cc 33 33', updated_by  = '361',  updated_on  = '12-SEP-12',
    effective_date = '25-SEP-12' WHERE emp_id = 18010

第二次迭代

预期(正确):

UPDATE BI_EMPLOYEE SET LASTNAME = 'Ll22 edited ' , OFFSHORE_ONSHORE = '1' ,
    ONSHORE_REGION = '1' , ONSHORE_DESK_MANAGER = 'henrry ' ,
    comments  = 'cc 33 33', updated_by  = '361',  updated_on  = '12-SEP-12',
    effective_date = '25-SEP-12' WHERE emp_id = 18009

实际(错误):

UPDATE BI_EMPLOYEE SET EMPLOYEE_ID = '1111111111111' , PP_NUMBER = '22222222222' ,
    CORPORATE_TITLE_ID = '2' , LASTNAME = 'Ll22 edited ' ,
    OFFSHORE_ONSHORE = '1' , ONSHORE_REGION = '1' ,
    ONSHORE_DESK_MANAGER = 'henrry ' ,  comments  = 'cc 33 33',
    updated_by  = '361',  updated_on  = '12-SEP-12',
    effective_date = '25-SEP-12'
    WHERE emp_id = 18009

为什么会这样?

【问题讨论】:

    标签: oracle cursor


    【解决方案1】:

    正如您在previous question 的评论中所述,您的第二个光标不限于第一个光标找到的员工,因为您之间没有链接。你在哪里:

    and employee_id = employee_id
    

    ...这两个都引用表格列,因此它根本不充当过滤器。你给你的局部变量取了相同的名字,这很让人困惑,但无论如何它超出了范围——这个游标看不到过程主体中设置的变量值。

    您需要执行以下操作:

    CREATE OR REPLACE PROCEDURE sp_run_employee_updates (p_date IN DATE) IS
        update_sql varchar2(4000);
        first_update boolean;
    
        CURSOR c_employees IS
            SELECT DISTINCT employee_id
            FROM bi_employee_update
            WHERE effective_date = p_date
            AND executed = 'N' 
            AND activity_id = '0';
    
        CURSOR c_updates(cp_employee_id bi_employee_update.employee_id%TYPE) IS
            SELECT *
            FROM bi_employee_update
            WHERE effective_date = p_date
            AND executed = 'N' 
            AND activity_id = '0'
            AND employee_id = cp_employee_id
            FOR UPDATE;
    
    BEGIN
        -- loop around all employees with pending records
        FOR r_employee IN c_employees LOOP
            -- reset the update_sql variable to its base
            update_sql :=  'UPDATE BI_EMPLOYEE SET ';
            -- reset the flag so we only add the comments etc. on the first record
            first_update := true;
    
            -- loop around all pending records for this employee
            FOR r_update IN c_updates(r_employee.employee_id) LOOP
                -- add the comments etc., only for the first update we see
                if first_update then
                    update_sql := update_sql
                        || ' comments = ''' || r_update.comments || ''','
                        || ' updated_by = ''' || r_update.changed_by  || ''','
                        || ' updated_on  = ''' || r_update.changed_on ||  ''','
                        || ' effective_date = ''' || r_update.effective_date  || '''';  
                    first_update := false;
                end if;
    
                -- add the field/value from this record to the variable
                update_sql := update_sql || ', '
                    || r_update.column_name || ' = ''' || r_update.new_value || '''' ; 
    
                -- mark this update as executed
                UPDATE bi_employee_update
                SET executed = 'Y'
                WHERE CURRENT OF c_updates;
    
            END LOOP;
    
            -- apply this update to the bi_employee record
            update_sql := update_sql || ' WHERE emp_id = ' || r_employee.employee_id;
    
            DBMS_OUTPUT.PUT_LINE(update_sql);
            EXECUTE IMMEDIATE update_sql; 
        END LOOP;
    END sp_run_employee_updates;
    

    实际上,重要的区别在于第二个游标现在有一个参数,而第一个游标中的员工 ID 作为该参数传递。

    另外,IN_DATE 被声明为日期,因此您无需通过TO_DATE() 传递它。在其他地方(生效日期等)会有隐式日期转换,因为您将它们视为字符串,但只要它们没有时间组件,这可能不会破坏任何内容,因为它应该在内部保持一致过程。

    【讨论】:

      猜你喜欢
      • 2012-06-15
      • 1970-01-01
      • 1970-01-01
      • 2013-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多