0

I have the below procedure with no error message:

create or replace procedure insert_or_upd_movement_baselines_planned_weight_proc(
p_id IN VARCHAR2,
p_date IN DATE,
p_planned_col_name IN VARCHAR2,
p_planned_value IN NUMBER
) as
begin
 declare
    plsql_block NVARCHAR2(8000);
begin
    plsql_block := 'merge into MOVEMENT_BASELINES mb using dual on (mb.MOVEMENT_ID = ' || p_id || ' and mb.MOVEMENT_DATE = ' || p_date || ')
     when not matched then insert (mb.MOVEMENT_ID, mb.MOVEMENT_DATE, mb.' || p_planned_col_name || ')
       values ( ' || p_id || ', ' || p_date || ', ' || p_planned_value || ')
     when matched then update set '
       || p_planned_col_name || ' = ' || p_planned_value || ';';

    execute immediate plsql_block;
end;
end insert_or_upd_movement_baselines_planned_weight_proc;

When I try to execute it with values for the input parameters, I am getting a compiler error:

Connecting to the database localDB.
ORA-00933: SQL command not properly ended
ORA-06512: at "RTT.INSERT_OR_UPD_MOVEMENT_BASELINES_PLANNED_WEIGHT_PROC", line 17
ORA-06512: at line 12
Process exited.

I am new to Oracle and would like to print the dynamic sql to check what is wrong but the print statement does not seem to work. I am guessing the issue is with the dynamic column name in the insert statement - any idea what is wrong? Thanks

1
  • 1
    your issue lies in the semi-colon you've appended to your plsql_block text, fwiw. Commented Mar 12, 2019 at 14:19

3 Answers 3

2

You should always be cautious while using dynamic SQL. Firstly, it is better to check if a static SQL statement is working fine and then try to convert it by modifying the dynamic parts. Also, a dbms_output before execute immediate helps you to know if the prepared sql is syntactically correct. Secondly, concatenating values is prone to SQL Injection and should be avoided.Preferred option is to use bind variables with the USING option of EXECUTE IMMEDIATE.

since p_planned_value is defined as a number, It implies that the datatype of all the columns you're planning to update/insert are going to be integers. I have used it accordingly in my example in the demo. If that's not the case, you will have to rethink how you're going to define the parameters of the procedure for it work for other cases like DATE datatypes.

CREATE OR REPLACE PROCEDURE insert_or_upd_movement_baselines_planned_weight_proc (
     p_id                 IN VARCHAR2,
     p_date               IN DATE,
     p_planned_col_name   IN VARCHAR2,
     p_planned_value      IN NUMBER
)
     AS
  plsql_block   VARCHAR2(4000);
     BEGIN
plsql_block := 'merge into MOVEMENT_BASELINES mb using 
 ( select :id as movement_id,:dt as movement_date from dual
  ) s ON ( mb.movement_id = s.movement_id  
              and mb.movement_date = s.movement_date )
     when matched then update set '
          || p_planned_col_name || ' = ' || p_planned_value || 
 ' when not matched then insert (MOVEMENT_ID, MOVEMENT_DATE,'
          || p_planned_col_name || ')
       values (:id,:dt,:value)';

EXECUTE IMMEDIATE plsql_block
              USING p_id,p_date,p_id,p_date,p_planned_value;

END insert_or_upd_movement_baselines_planned_weight_proc;
/

Demo

Sign up to request clarification or add additional context in comments.

1 Comment

Yes they will be all numbers :) Thanks it works and thanks for the dbms_output info, I was looking on how to print with oracle
0

This part is definitely fishy:

|| p_date ||

since it actually does the same like

|| to_char(p_date) ||

so the unquoted value of the date becomes part of the statement, which will not result in a valid sql statement. Try this instead:

   values ( ' || p_id || ', to_date(''' || to_char(p_date) || '''), ' || p_planned_value || ')

2 Comments

Does not the issue I am afraid. Still getting the same error. Also the p_date is a DATE so i am not sure why we need to convert it into a date again?
Oh also the values I am inserting will take us to the update statement and not the insert. Do you know a way to print out the dynamic sql so we might be able to identify something wrong straight away?
0

This is an addendum to Kaushik's answer, where they state (quite correctly, if not in so many words) that your statement is completely vulnerable to SQL injection.

I would write your procedure as follows:

CREATE OR REPLACE PROCEDURE insert_or_upd_movement_baselines_planned_weight_proc(p_id               IN VARCHAR2,
                                                                                 p_date             IN DATE,
                                                                                 p_planned_col_name IN VARCHAR2,
                                                                                 p_planned_value    IN NUMBER) AS
  v_sql              CLOB;
  v_planned_col_name VARCHAR2(32);
BEGIN
  v_planned_col_name := dbms_assert.simple_sql_name(p_planned_col_name);

  v_sql := 'MERGE INTO movement_baselines tgt'||CHR(10)||
           'USING (SELECT :p_id movement_id,'||CHR(10)||
           '              :p_date movement_date,'||CHR(10)||
           '              :p_planned_value planned_value'||CHR(10)||
           '       FROM   dual) src'||CHR(10)||
           'ON (tgt.movement_id = src.movement_id AND tgt.movement_date = src.movement_date)'||CHR(10)||
           'WHEN NOT MATCHED THEN'||CHR(10)||
           '  INSERT (tgt.movement_id, tgt.movement_date, tgt.'||v_planned_col_name||')'||CHR(10)||
           '  VALUES (src.movement_id, src.movement_date, src.movement_date)'||CHR(10)||
           'WHEN MATCHED THEN'||CHR(10)||
           '  UPDATE'||CHR(10)||
           '  SET    tgt.'||v_planned_col_name||' = src.planned_value';


  dbms_output.put_line('merge statement: ' || chr(10) || v_sql);

  EXECUTE IMMEDIATE v_sql
    USING p_id, p_date, p_planned_value;

END;
/

Note the use of dbms_assert to sanitize your input - in this instance, we are checking that the value you passed in to p_planned_col_name meets the requirements for it to be a valid identifier, which means it definitely can't be used for SQL injection.

In addition, I moved the parameters into a subquery, meaning that the using clause of the execute immediate is now shorter and, I think, clearer and easier to maintain.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.