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

pbgvytdp  于 2023-04-05  发布在  Oracle
关注(0)|答案(3)|浏览(182)

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

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来检查什么是错误的,但打印语句似乎不起作用。我猜问题是插入语句中的动态列名-知道什么是错误的吗?谢谢

2admgd59

2admgd591#

在使用动态SQL时,您应该始终保持谨慎。首先,最好检查静态SQL语句是否正常工作,然后尝试通过修改动态部分来转换它。此外,execute immediate之前的dbms_output可以帮助您了解准备好的SQL是否语法正确。其次,连接值容易发生 *SQL注入 *,应该避免。首选选项是使用绑定变量和EXECUTE IMMEDIATEUSING选项。
由于p_planned_value被定义为一个数字,这意味着你计划更新/插入的所有列的数据类型都将是整数。我在演示的例子中相应地使用了它。如果不是这样,你将不得不重新考虑如何定义过程的参数,以便它适用于其他情况,如DATE数据类型。

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

9rbhqvlz

9rbhqvlz2#

这部分肯定是鱼腥味:

|| p_date ||

因为它实际上也是

|| to_char(p_date) ||

因此,日期的未加引号的值成为语句的一部分,这将不会导致有效的sql语句。请尝试以下操作:

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

ac1kyiln3#

这是Kaushik的回答的补充,他们指出(非常正确,如果不是那么多话)您的语句完全容易受到SQL注入的影响。
我会把你的程序写如下:

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注入。
此外,我将参数移到子查询中,这意味着立即执行的using子句现在更短,而且我认为更清晰,更易于维护。

相关问题