使用oracle嵌套游标的奇怪行为

时间:2012-09-12 08:31:39

标签: oracle cursor

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

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;

问题出在第二个游标中,我得到了所有以前游标的数据。

e.g。如果第一次迭代应该返回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

ACTUAL(WRONG):

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

ACTUAL(WRONG):

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

为什么会这样?

1 个答案:

答案 0 :(得分: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()传递它。在其他地方(生效日期等)会有隐含的日期转换,因为你将它们视为字符串,但只要它们没有时间组件,这可能不会破坏任何东西,因为它应该是一致的程序。