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

Strange behaviours with oracle nested cursors

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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。

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

第一次迭代

预期(正确):

1
2
3
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

实际(错误):

1
2
3
4
5
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

第二次迭代

预期(正确):

1
2
3
4
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

实际(错误):

1
2
3
4
5
6
7
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
and employee_id = employee_id

...两者都引用了表格列,因此它根本不充当过滤器。您已经给本地变量指定了相同的名称,这足以使事情感到困惑,但是无论如何它都超出了范围-该游标无法查看过程主体中设置的变量值。

您需要执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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()。在其他地方(有效日期等)将存在隐式日期转换,因为您将它们视为字符串,但是只要它们没有时间成分,这可能不会破坏任何内容,因为它应该在内部保持一致步骤。