如何使用带有可变表名和条件的立即执行

Sad*_*aal 2 oracle plsql

我想创建一个 PL/SQL 函数,它传递一个表名和一个条件,并返回比条件在该表上满足的行数。

我创建了函数:

CREATE OR REPLACE FUNCTION CHECK_EXISTS 
      (TABLE_NAME VARCHAR2, CONDITION VARCHAR2) RETURN NUMBER AS 
      VAL NUMBER;
      SQL_CODE VARCHAR2(200):='SELECT COUNT (*) INTO VAL FROM :TABLE_NAME WHERE
      :CONDITION';
BEGIN 
      EXECUTE IMMEDIATE SQL_CODE USING TABLE_NAME ,CONDITION;
      RETURN VAL;
EXCEPTION
      WHEN NO_DATA_FOUND THEN
      RETURN 0;
END;
Run Code Online (Sandbox Code Playgroud)

该函数已成功创建,但是当我尝试使用此代码使用它时:

BEGIN DBMS_OUTPUT.PUT_LINE(CHECK_EXISTS('EMPLOYEES' ,'DEPARTMENT_ID=50')); END;
Run Code Online (Sandbox Code Playgroud)

我收到异常:ORA-00903:表名无效。

Ale*_*ole 6

您不能对表名或列名或完整条件使用绑定变量。这些必须在解析语句时知道,这是在分配绑定变量之前 - 否则您将失去绑定变量的一项好处。您只能绑定变量的值。

当您的字符串被解析时,表名被逐字解释为:TABLE_NAME,并且冒号使其成为表名的无效值。它没有使用您传递给函数的值。

所以你需要连接名称和条件;您的INTO条款也出现在错误的地方:

CREATE OR REPLACE FUNCTION CHECK_EXISTS 
      (TABLE_NAME VARCHAR2, CONDITION VARCHAR2) RETURN NUMBER AS 
      VAL NUMBER;
      SQL_CODE VARCHAR2(200):='SELECT COUNT (*) FROM '
        || TABLE_NAME || ' WHERE ' || CONDITION;
BEGIN 
      EXECUTE IMMEDIATE SQL_CODE INTO VAL;
      RETURN VAL;
EXCEPTION
      WHEN NO_DATA_FOUND THEN
      RETURN 0;
END;
Run Code Online (Sandbox Code Playgroud)

考虑到您将看到的有关使用绑定变量避免 SQL 注入的所有建议,这可能看起来很奇怪。这仍然适用,您只是不能在此处为表名执行此操作。

但这确实意味着您可能会接受 SQL 注入,因此您应该清理您获得的输入,这对于表名来说相当简单 - 例如,您可以查看它是否存在于 中all_tables- 但变量条件将更难检查. 幸运的是,您只能使用动态 SQL 执行单个语句,因此很难做任何令人讨厌的事情,除非您可以将其置于有效条件中的副作用。


如果由于某种原因你真的想为你的execute immediate调用使用绑定变量,你可以做一些令人费解的事情,比如:

CREATE OR REPLACE FUNCTION CHECK_EXISTS 
      (TABLE_NAME VARCHAR2, CONDITION VARCHAR2) RETURN NUMBER AS 
      VAL NUMBER;
      SQL_CODE VARCHAR2(200):=q'[BEGIN EXECUTE IMMEDIATE 'SELECT COUNT (*) FROM ' || :TABLE_NAME || ' WHERE ' || :CONDITION INTO :VAL; END;]';
BEGIN 
      EXECUTE IMMEDIATE SQL_CODE USING TABLE_NAME, CONDITION, OUT VAL;
      RETURN VAL;
EXCEPTION
      WHEN NO_DATA_FOUND THEN
      RETURN 0;
END;
/
Run Code Online (Sandbox Code Playgroud)

... 将串联推入一个匿名块;执行然后使用 OUT 绑定变量作为计数结果而不是INTO. 不过,我不确定这样做有多大好处。


正如@AvrajitRoy 所提到的,由于您正在进行聚合,count()因此查询将始终返回结果(除非它从不存在的表或格式错误的条件中出错,当然,您想知道),因此您的异常处理程序永远无法达到。虽然它并没有真正造成任何伤害,但可以将其删除:

CREATE OR REPLACE FUNCTION CHECK_EXISTS (TABLE_NAME VARCHAR2, CONDITION VARCHAR2)
RETURN NUMBER AS 
    VAL NUMBER;
    SQL_CODE VARCHAR2(200):='SELECT COUNT (*) FROM '
        || TABLE_NAME || ' WHERE ' || CONDITION;
BEGIN 
    EXECUTE IMMEDIATE SQL_CODE INTO VAL;
    RETURN VAL;
END;
Run Code Online (Sandbox Code Playgroud)