2

I have to execute 32 times a very similar operation, that is setting the value of a column in a row for a given record (for a given quarter).

To simplify my code and thrive for beauty, I wanted to use a for loop with an execute immediate, using I_cnt to dynamically set the column name in the statement.

I am using Oracle 10g.

When I call the procedure, Oracle returns

SQL Error: ORA-00904: "REC"."QUARTER_MEL": niepoprawny identyfikator ORA-06512: przy "LREBIRT.P_PFPL_RISKPROFILE_TEST", linia 55 00904. 00000 - "%s: invalid identifier"

When I call the procedure below, v_risk_volume and v_risk_amount are correctly calculated, it fails to execute immediate my statement.

The code of my procedure:

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;

The result of the dbms_output:

1-2012 7 448787,05 update PFPL_RISKPROFILE_RES_TEST t set Q_1_volume = v_risk_volume/rec.Q_VOLUME, Q_1_amount = v_risk_amount/rec.Q_AMOUNT where t.QUARTER_MEL = rec.QUARTER_MEL

Current version of the procedure after previous corrections:

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;

Hello again, I tried what Jeff proposed, but I still have a problem, and it's really ugly, I almost cried writing the code - I did it for 10 quarters, I still need to paste the missing 22 quarters. Now the procedure treated two of the four rows of the loop.

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;
  • It seems the v_sql statement cannot contain the `rec.Q_...` string. When I concatenate them the error disappears. But I'm not home yet, now the division v_risk_volume/rec.Q_volume returns a decimal value interpreted as `,0056` for example, and the comma breaks the statement. Maybe due to my regional settings... – Stephane Dubois Mar 10 '15 at 08:45
  • I solved this problem by using `:variable` in the v_sql statement and then `execute immediate v_sql USING variable;`. – Stephane Dubois Mar 10 '15 at 08:58
  • You figured out at the same time I posted a solution. You could mark it as answered. – Lalit Kumar B Mar 10 '15 at 09:05
  • Well, I got it to work, but I do not understand one point: in the outer loop I have several records, I wanted to loop through each of the record in the outer loop, and for each record, execute the inner loop (so for each of the 32 I_cnt execute the v_sql statement). But when I call the procedure, it executes only for one record of the outer loop and exits. Any idea? – Stephane Dubois Mar 10 '15 at 09:27
  • A better solution may be to avoid the loops and dynamic sql entirely, and do it all in a single UPDATE statement, since you're only updating a single table. – Jeffrey Kemp Mar 10 '15 at 12:46
  • @JeffreyKemp : I need to update 32 couples of columns, so a solution would be to have 32 update statements in my procedure, but it is not "elegant". – Stephane Dubois Mar 10 '15 at 13:22
  • No, you can actually update multiple columns in a single SQL update statement. And it will most likely be faster. – Jeffrey Kemp Mar 10 '15 at 14:12
  • @JeffreyKemp I put my code with the static SQL and update statements in an edit of my original post. I still have an issue however, the procedure treated only two rows out of four returned by the cursor in the loop... – Stephane Dubois Mar 10 '15 at 14:57
  • You need to go further: I suggested you get rid of the loop and do it *all* in a single update statement (no other queries). – Jeffrey Kemp Mar 10 '15 at 23:27

2 Answers2

1

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';

You are not passing the variable value here. You need to use bind variables and USING clause in the EXECUTE IMMEDIATE to refer the bind values..

You need to do it as:

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
Lalit Kumar B
  • 47,486
  • 13
  • 97
  • 124
  • The `rec.Q*` references need to be bind variables too though. – Alex Poole Mar 10 '15 at 09:23
  • Yes, I continued to tweak about by myself and found that. Thanks. However I still have the problem that after the execution of the inner loop for one of the records of the outer loop, the procedure ends without executing the inner loop for the other records of the outer loop, Any clue on that? – Stephane Dubois Mar 10 '15 at 09:33
  • @StephaneDubois The outer loop depends on `for rec in (select * from PFPL_RISKPROFILE_BASIS_TEST)`. How many rows does the select actually return? – Lalit Kumar B Mar 10 '15 at 09:54
  • The select returns 4 rows at the moment. When I comment the inner loop out of the code, the outer loop runs correctly through the 4 rows. However when the inner loop is executed, it exits right after the first row of the select is treated. – Stephane Dubois Mar 10 '15 at 09:56
  • Can you comment out EXECUTE IMMEDIATE and check once. – Lalit Kumar B Mar 10 '15 at 10:14
  • @LalitKumarB I get the same, the loop only goes trough the first row of the outer loop. I edited my post with the current version of the procedure. – Stephane Dubois Mar 10 '15 at 10:52
0

So, I finally found the cause of the last error and I'd like to compile here the different elements of the answer.

The first error was that using EXECUTE IMMEDIATE, the sql executed should not include any references, I replaced by bind variables and it was ok.

The second error was that while the inner cursor goes from 1..32, there were not necessarily values to select into v_risk_volume and v_risk_amount for all the 32 iterations, and the procedure crashed at the first iteration where the select ... into ... returned nothing.

So I changed the logic, putting the inner cursor on the table that was in the select ... into ... statement, and now it runs perfectly. I'm quite happy with this solution, it does the job with minimum code and as the maximum number of records treated is something like 1024 the performances are totally acceptable.

The final code of this procedure:

  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;