如何在存储过程中处理逗号分隔的输入

How to process comma separated input in stored procedure

我需要编写一个程序,其中将 table 名称作为输入参数(一次应该只有一个 table)和列名(应该有多个列名逗号分隔)和列值(应该有多个列名逗号分隔)。

我的尝试:

CREATE OR REPLACE PROCEDURE sp_test_insert(
p_table_name IN VARCHAR2,
p_column_name IN VARCHAR2,-- It can have multiple column names separated by comma 
p_column_value IN VARCHAR2-- It can have multiple column names separated by 
)
AS
lv_str VARCHAR2(4000);
BEGIN
lv_str := 'INSERT INTO '||p_table_name||'should have multiple column names' ||
'VALUES('||'should have multiple column names' ||')';

EXECUTE IMMEDIATE lv_str;
END;

Tool used: SQL Developer(18c)

我对如何在过程主体内处理多个列名和列值感到困惑。我将如何定义一个数组并相应地进行?

不要。您正在为自己设置易受 SQL 注入攻击的程序。


如果你真的想要(请不要):

CREATE OR REPLACE PROCEDURE sp_test_insert(
  p_table_name   IN VARCHAR2,
  p_column_name  IN VARCHAR2,
  p_column_value IN VARCHAR2 
)
AS
  lv_str VARCHAR2(4000);
BEGIN
lv_str := 'INSERT INTO '||p_table_name||' (' || p_column_name || ') VALUES(' || p_column_value ||')';

EXECUTE IMMEDIATE lv_str;
END;
/

那么你可以这样做:

BEGIN
  sp_test_insert(
    'my_table',
    'col1, col2, col3',
    q'['a', DATE '2022-05-31', 42]'
  );
END;
/

但你也可以这样做:

BEGIN
  sp_test_insert(
    'my_table',
    'col1, col2, col3',
    q'['a', (SELECT DATE '1970-01-01' FROM secret_table WHERE username = 'Admin' AND password_hash = 'abcgefg1234'), 42]'
  );
END;
/

不要让您的应用程序容易受到 SQL 注入攻击;尽量避免动态 SQL!


如果你想让它更能抵抗 SQL 注入攻击,那么你可以使用 DBMS_ASSERT 包:

CREATE OR REPLACE PROCEDURE sp_test_insert(
  p_table_name   IN VARCHAR2,
  p_column_name  IN SYS.ODCIVARCHAR2LIST,
  p_column_value IN SYS.ODCIVARCHAR2LIST 
)
AS
  lv_str VARCHAR2(4000);
BEGIN
  lv_str := 'INSERT INTO '
            || DBMS_ASSERT.SQL_OBJECT_NAME(
                 DBMS_ASSERT.ENQUOTE_NAME(p_table_name, FALSE)
               )
            ||' ('
            || DBMS_ASSERT.ENQUOTE_NAME(p_column_name(1), FALSE);

  FOR i IN 2 .. p_column_name.COUNT LOOP
    lv_str := lv_str || ', '
                     || DBMS_ASSERT.ENQUOTE_NAME(p_column_name(i), FALSE);
  END LOOP;

  lv_str := lv_str || ') VALUES('
                   || DBMS_ASSERT.ENQUOTE_LITERAL(p_column_value(1));

  FOR i IN 2 .. p_column_name.COUNT LOOP
    lv_str := lv_str || ', '
                     || DBMS_ASSERT.ENQUOTE_LITERAL(p_column_value(i));
  END LOOP;

  lv_str := lv_str || ')';

  EXECUTE IMMEDIATE lv_str;
END;
/

然后:

BEGIN
  sp_test_insert(
    'MY_TABLE',
    SYS.ODCIVARCHAR2LIST( 'COL1', 'COL2', 'COL3'),
    SYS.ODCIVARCHAR2LIST( 'a', '31-MAY-2022', '42')
  );
END;
/

但是,这些值现在都作为字符串传递到动态 SQL 语句中,这使得将数据传递到 DATETIMESTAMPINTERVAL 变得更加困难(等)列,因为它依赖于隐式 data-type 转换。

您仍应尽可能避免动态 SQL。

db<>fiddle here