Oracle SQL:在立即执行的内部循环中使用外部循环标识符

Posted

技术标签:

【中文标题】Oracle SQL:在立即执行的内部循环中使用外部循环标识符【英文标题】:Oracle SQL: Use outer loop identifiers in inner loop in execute immediate 【发布时间】:2015-03-10 08:28:33 【问题描述】:

我必须执行 32 次非常相似的操作,即为给定记录(给定季度)设置行中的列值。

为了简化我的代码并追求美观,我想使用带有立即执行的 for 循环,使用 I_cnt 在语句中动态设置列名。

我正在使用 Oracle 10g。

当我调用过程时,Oracle 返回

SQL 错误:ORA-00904:“REC”。“QUARTER_MEL”:niepoprawny identyfikator ORA-06512: przy "LREBIRT.P_PFPL_RISKPROFILE_TEST", linia 55 00904. 00000 - "%s: 无效标识符"

当我调用下面的过程时,v_risk_volume 和 v_risk_amount 计算正确,它无法立即执行我的语句。

我的程序代码:

PROCEDURE CALCULATE_RESULT_TABLE AS
  v_risk_volume float;
  v_risk_amount float;
  v_sql varchar2(1000);
  BEGIN
for rec in (select * from PFPL_RISKPROFILE_BASIS_TEST)
LOOP
  for i_cnt in 1..32
  LOOP
    DBMS_OUTPUT.PUT_LINE(rec.quarter_mel);
    select Q_VOLUME, Q_AMOUNT into v_risk_volume, v_risk_amount from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = i_cnt;
     DBMS_OUTPUT.PUT_LINE(v_risk_volume);
      DBMS_OUTPUT.PUT_LINE(v_risk_amount);
    v_sql := 'update PFPL_RISKPROFILE_RES_TEST t set Q_'||i_cnt||'_volume = v_risk_volume/rec.Q_VOLUME, Q_'||i_cnt||'_amount = v_risk_amount/rec.Q_AMOUNT where t.QUARTER_MEL = rec.QUARTER_MEL';
    DBMS_OUTPUT.PUT_LINE(v_sql);
    EXECUTE IMMEDIATE v_sql;
  END LOOP;
END LOOP;
  END CALCULATE_RESULT_TABLE;

dbms_output 的结果:

1-2012 7 448787,05 更新 PFPL_RISKPROFILE_RES_TEST t 设置 Q_1_volume = v_risk_volume/rec.Q_VOLUME, Q_1_amount = v_risk_amount/rec.Q_AMOUNT 其中 t.QUARTER_MEL = rec.QUARTER_MEL

先前更正后的程序的当前版本:

PROCEDURE CALCULATE_RESULT_TABLE AS
  v_risk_volume float;
  v_risk_amount float;
  v_sql varchar2(1000);
  BEGIN
    for rec in (select * from PFPL_RISKPROFILE_BASIS_TEST)
    LOOP
    DBMS_OUTPUT.PUT_LINE(rec.quarter_mel);
      for i_cnt in 1..32
      LOOP
        DBMS_OUTPUT.PUT_LINE(i_cnt);      
    DBMS_OUTPUT.PUT_LINE(rec.quarter_mel);
    select Q_VOLUME, Q_AMOUNT into v_risk_volume, v_risk_amount from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = i_cnt;
    if rec.Q_volume > 0 then v_risk_volume := round(v_risk_volume/rec.Q_volume,4); else v_risk_volume := 0; end if;
    if rec.Q_amount > 0 then v_risk_amount := round(v_risk_amount/rec.Q_amount,4); else v_risk_amount := 0; end if;
    v_sql := 'update PFPL_RISKPROFILE_RES_TEST t set Q_'||i_cnt||'_volume = :v, Q_'||i_cnt||'_amount = :a where t.QUARTER_MEL = :q';
    DBMS_OUTPUT.PUT_LINE(v_sql);      
    EXECUTE IMMEDIATE v_sql USING v_risk_volume, v_risk_amount, rec.quarter_mel;
  END LOOP;
END LOOP;
  END CALCULATE_RESULT_TABLE;

再次你好,我尝试了 Jeff 提出的方法,但我仍然有问题,而且真的很难看,我几乎哭了写代码 - 我做了 10 个季度,我仍然需要粘贴缺少的 22 个季度。现在程序处理了循环的四行中的两行。

PROCEDURE CALCULATE_RESULT_TABLE AS
  v_risk_volume_1 FLOAT;
  v_risk_volume_2 FLOAT;
  v_risk_volume_3 FLOAT;
  v_risk_volume_4 FLOAT;
  v_risk_volume_5 FLOAT;
  v_risk_volume_6 FLOAT;
  v_risk_volume_7 FLOAT;
  v_risk_volume_8 FLOAT;
  v_risk_volume_9 FLOAT;
  v_risk_volume_10 FLOAT;
  v_risk_amount_1 FLOAT;
  v_risk_amount_2 FLOAT;
  v_risk_amount_3 FLOAT;
  v_risk_amount_4 FLOAT;
  v_risk_amount_5 FLOAT;
  v_risk_amount_6 FLOAT;
  v_risk_amount_7 FLOAT;
  v_risk_amount_8 FLOAT;
  v_risk_amount_9 FLOAT;
  v_risk_amount_10 FLOAT;
  BEGIN
for rec in (select * from PFPL_RISKPROFILE_BASIS_TEST order by quarter_mel)
LOOP
DBMS_OUTPUT.PUT_LINE(rec.quarter_mel);
select Q_VOLUME, Q_AMOUNT into v_risk_volume_1, v_risk_amount_1 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 1;
    if rec.Q_volume > 0 then v_risk_volume_1 := round(v_risk_volume_1/rec.Q_volume,4); else v_risk_volume_1 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_1 := round(v_risk_amount_1/rec.Q_amount,4); else v_risk_amount_1 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_2, v_risk_amount_2 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 2;
    if rec.Q_volume > 0 then v_risk_volume_2 := round(v_risk_volume_2/rec.Q_volume,4); else v_risk_volume_2 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_2 := round(v_risk_amount_2/rec.Q_amount,4); else v_risk_amount_2 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_3, v_risk_amount_3 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 3;
    if rec.Q_volume > 0 then v_risk_volume_3 := round(v_risk_volume_3/rec.Q_volume,4); else v_risk_volume_3 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_3 := round(v_risk_amount_3/rec.Q_amount,4); else v_risk_amount_3 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_4, v_risk_amount_4 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 4;
    if rec.Q_volume > 0 then v_risk_volume_4 := round(v_risk_volume_4/rec.Q_volume,4); else v_risk_volume_4 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_4 := round(v_risk_amount_4/rec.Q_amount,4); else v_risk_amount_4 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_5, v_risk_amount_5 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 5;
    if rec.Q_volume > 0 then v_risk_volume_5 := round(v_risk_volume_5/rec.Q_volume,4); else v_risk_volume_5 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_5 := round(v_risk_amount_5/rec.Q_amount,4); else v_risk_amount_5 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_6, v_risk_amount_6 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 6;
    if rec.Q_volume > 0 then v_risk_volume_6 := round(v_risk_volume_6/rec.Q_volume,4); else v_risk_volume_6 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_6 := round(v_risk_amount_6/rec.Q_amount,4); else v_risk_amount_6 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_7, v_risk_amount_7 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 7;
    if rec.Q_volume > 0 then v_risk_volume_7 := round(v_risk_volume_7/rec.Q_volume,4); else v_risk_volume_7 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_7 := round(v_risk_amount_7/rec.Q_amount,4); else v_risk_amount_7 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_8, v_risk_amount_8 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 8;
    if rec.Q_volume > 0 then v_risk_volume_8 := round(v_risk_volume_8/rec.Q_volume,4); else v_risk_volume_8 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_8 := round(v_risk_amount_8/rec.Q_amount,4); else v_risk_amount_8 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_9, v_risk_amount_9 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 9;
    if rec.Q_volume > 0 then v_risk_volume_9 := round(v_risk_volume_9/rec.Q_volume,4); else v_risk_volume_9 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_9 := round(v_risk_amount_9/rec.Q_amount,4); else v_risk_amount_9 := 0; end if;
select Q_VOLUME, Q_AMOUNT into v_risk_volume_10, v_risk_amount_10 from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter = 10;
    if rec.Q_volume > 0 then v_risk_volume_10 := round(v_risk_volume_10/rec.Q_volume,4); else v_risk_volume_10 := 0; end if;
    if rec.Q_volume > 0 then v_risk_amount_10 := round(v_risk_amount_10/rec.Q_amount,4); else v_risk_amount_10 := 0; end if;
update PFPL_RISKPROFILE_RES_TEST set 
Q_1_volume = v_risk_volume_1,
Q_2_volume = v_risk_volume_2,
Q_3_volume = v_risk_volume_3,
Q_4_volume = v_risk_volume_4,
Q_5_volume = v_risk_volume_5,
Q_6_volume = v_risk_volume_6,
Q_7_volume = v_risk_volume_7,
Q_8_volume = v_risk_volume_8,
Q_9_volume = v_risk_volume_9,
Q_10_volume = v_risk_volume_10 where quarter_mel = rec.quarter_mel;
update PFPL_RISKPROFILE_RES_TEST set 
Q_1_amount = v_risk_amount_1,
Q_2_amount = v_risk_amount_2,
Q_3_amount = v_risk_amount_3,
Q_4_amount = v_risk_amount_4,
Q_5_amount = v_risk_amount_5,
Q_6_amount = v_risk_amount_6,
Q_7_amount = v_risk_amount_7,
Q_8_amount = v_risk_amount_8,
Q_9_amount = v_risk_amount_9,
Q_10_amount = v_risk_amount_10 where quarter_mel = rec.quarter_mel;
    END LOOP;
  END CALCULATE_RESULT_TABLE;

【问题讨论】:

看来 v_sql 语句不能包含 rec.Q_... 字符串。当我连接它们时,错误消失了。但我还没回家,现在除法 v_risk_volume/rec.Q_volume 返回一个十进制值,例如解释为,0056,逗号打破了语句。可能是因为我的地区设置... 我通过在v_sql语句中使用:variable然后execute immediate v_sql USING variable;解决了这个问题。 你在我发布解决方案的同时发现了。您可以将其标记为已回答。 好吧,我让它工作了,但我不明白一点:在外循环中我有几条记录,我想遍历外循环中的每一条记录,并且对于每一条记录,执行内部循环(因此对于 32 个 I_cnt 中的每一个执行 v_sql 语句)。但是当我调用该过程时,它只执行外部循环的一条记录并退出。有什么想法吗? 更好的解决方案可能是完全避免循环和动态 sql,并在单个 UPDATE 语句中完成所有操作,因为您只更新单个表。 【参考方案1】:

v_sql := '更新 PFPL_RISKPROFILE_RES_TEST t 设置 Q_'||i_cnt||'_volume = v_risk_volume/rec.Q_VOLUME, Q_'||i_cnt||'_amount = v_risk_amount/rec.Q_AMOUNT 其中 t.QUARTER_MEL = rec.QUARTER_MEL ';

您没有在此处传递变量值。您需要在 EXECUTE IMMEDIATE 中使用绑定变量和 USING 子句来引用绑定值..

你需要这样做:

v_sql := 'update PFPL_RISKPROFILE_RES_TEST t set Q_'||i_cnt||'_volume = :v_risk_volume/rec.Q_VOLUME, Q_'||i_cnt||'_amount = :v_risk_amount/:rec.Q_AMOUNT where t.QUARTER_MEL = rec.QUARTER_MEL';

execute immediate v_sql using v_risk_volume, v_risk_amount, rec.Q_AMOUNT

【讨论】:

rec.Q* 引用也需要绑定变量。 是的,我继续自己调整并发现了这一点。谢谢。但是我仍然有这样的问题,在为外循环的记录之一执行内循环后,程序结束而不为外循环的其他记录执行内循环,有什么线索吗? @StephaneDubois 外循环依赖于for rec in (select * from PFPL_RISKPROFILE_BASIS_TEST)。选择实际返回多少行? 当前选择返回 4 行。当我从代码中注释掉内循环时,外循环正确地运行了 4 行。但是,当执行内部循环时,它会在处理完 select 的第一行后立即退出。 能否注释掉EXECUTE IMMEDIATE并检查一次。【参考方案2】:

所以,我终于找到了最后一个错误的原因,我想在这里编译答案的不同元素。

第一个错误是使用EXECUTE IMMEDIATE,执行的sql不应该包含任何引用,我换成绑定变量就ok了。

第二个错误是,当内部光标从 1..32 开始时,对于所有 32 次迭代,不一定有要选择到 v_risk_volume 和 v_risk_amount 中的值,并且该过程在第一次迭代时崩溃,其中 select ... into ... 什么也没返回。

所以我改变了逻辑,将内部光标放在 select ... into ... 语句中的表上,现在它运行完美。我对这个解决方案非常满意,它用最少的代码完成了工作,并且由于处理的最大记录数约为 1024,因此性能完全可以接受。

这个程序的最终代码:

  PROCEDURE CALCULATE_RESULT_TABLE AS
  v_risk_volume float;
  v_risk_amount float;
  v_i_volume varchar2(30);
  v_i_amount varchar2(30);
  v_sql varchar2(1000);
  BEGIN
    for rec in (select * from PFPL_RISKPROFILE_RES_TEST)
    LOOP
DBMS_OUTPUT.PUT_LINE(rec.quarter_mel);
NULL;
  for i in (select * from PFPL_RISKPROFILE_CDR_Q_TEST where quarter_mel = rec.quarter_mel and quarter between 1 and 32 order by quarter)
  LOOP
    if rec.Q_volume > 0 then v_risk_volume := round(i.Q_VOLUME/rec.Q_volume,4); else v_risk_volume := 0; end if;
    if rec.Q_amount > 0 then v_risk_amount := round(i.Q_AMOUNT/rec.Q_amount,4); else v_risk_amount := 0; end if;
    v_i_volume := 'Q_'||i.quarter||'_volume';
    v_i_amount := 'Q_'||i.quarter||'_amount';
    v_sql := 'update PFPL_RISKPROFILE_RES_TEST t set '||v_i_volume||' = :v, '||v_i_amount||' = :a where t.QUARTER_MEL = :q';
    EXECUTE IMMEDIATE v_sql USING v_risk_volume, v_risk_amount, rec.quarter_mel;
  END LOOP;
END LOOP;
  END CALCULATE_RESULT_TABLE;

【讨论】:

以上是关于Oracle SQL:在立即执行的内部循环中使用外部循环标识符的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Oracle PL/SQL 过程的开始部分之后声明游标

您可以从立即执行语句中退出 PL-SQL 循环吗?

Oracle PL/SQL 版本 12.2.0.1.0 与 12.1.0.2.0 - 使用参数立即执行

在循环中立即执行获取表数据

使用立即执行调整数据文件 oracle 的大小

Oracle查询转换之连接谓词推入