Oracle-合并/插入语句中的动态SQL列名称

时间:2019-03-12 13:37:05

标签: oracle dynamic-sql columnname

我有以下过程,没有错误消息:

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;

当我尝试使用输入参数的值执行它时,出现编译器错误:

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.

我是Oracle新手,想打印动态sql以检查出什么问题,但是print语句似乎不起作用。 我猜问题出在插入语句中的动态列名-知道什么地方错了吗? 谢谢

3 个答案:

答案 0 :(得分:2)

使用动态SQL时应始终保持谨慎。首先,最好检查静态SQL语句是否工作正常,然后尝试通过修改动态部分来对其进行转换。另外,在dbms_output之前的execute immediate可以帮助您了解所准备的sql在语法上是否正确。其次,连接值很容易发生 SQL注入,应该避免。首选的方法是将绑定变量与USING的{​​{1}}选项一起使用。

由于EXECUTE IMMEDIATE被定义为数字,这意味着您计划更新/插入的所有列的数据类型将为整数。在演示示例中,我已经相应地使用了它。如果不是这种情况,则您将不得不重新考虑如何定义该过程的参数,以使其适用于p_planned_value数据类型等其他情况。

DATE

Demo

答案 1 :(得分:0)

这部分绝对是鱼腥味

|| p_date ||

因为它实际上和

一样
|| to_char(p_date) ||

因此日期的未引用值将成为语句的一部分,这将不会导致有效的sql语句。尝试以下方法:

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

答案 2 :(得分:0)

这是Kaushik答案的附录,他们在其中指出(如果不是很多话,很正确),您的语句完全容易受到SQL注入的攻击。<​​/ p>

我将按照以下步骤编写您的程序:

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

请注意使用dbms_assert来清理您的输入-在这种情况下,我们正在检查您传递给p_planned_col_name的值是否符合其为有效标识符的要求,这意味着它绝对不能用于SQL注入。

此外,我将参数移到了子查询中,这意味着execute立即数的using子句现在更短了,而且我认为更清晰,更易于维护。