Oracle - имя столбца динамического SQL в операторе слияния / вставки - PullRequest
0 голосов
/ 12 марта 2019

У меня есть следующая процедура без сообщения об ошибке:

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 ]

2 голосов
/ 12 марта 2019

При использовании динамического SQL вы всегда должны быть осторожны.Во-первых, лучше проверить, работает ли статический оператор SQL, а затем попытаться преобразовать его, изменив динамические части.Кроме того, dbms_output перед execute immediate помогает вам узнать, является ли подготовленный sql синтаксически правильным.Во-вторых, объединение значений склонно к SQL-инъекции и его следует избегать. Предпочтительным вариантом является использование переменных связывания с параметром USING, равным EXECUTE IMMEDIATE.

, поскольку определено 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

0 голосов
/ 12 марта 2019

Это дополнение к ответу Каушика, в котором говорится (совершенно правильно, если не так много слов), что ваше утверждение полностью уязвимо для 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 немедленного выполнения теперь короче и, как мне кажется, яснее и проще в обслуживании.

0 голосов
/ 12 марта 2019

Эта часть определенно подозрительна:

|| p_date ||

так как он на самом деле делает то же самое, что и

|| to_char(p_date) ||

поэтому значение даты без кавычек становится частью оператора, что не приведет к действительному оператору SQL. Попробуйте вместо этого:

   values ( ' || p_id || ', to_date(''' || to_char(p_date) || '''), ' || p_planned_value || ')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...