你有两个目标
-
保留代码DRY 和
-
使用绑定变量
如果您只有一个非常简单的条件(如您的示例,使用过滤器或不使用过滤器),您可以使用IF 语句为这两种情况打开不同的游标。
IF salary_from is null THEN
OPEN v_cursor FOR SELECT LAST_NAME, SALARY FROM hr.employees;
ELSE
OPEN v_cursor FOR SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= salary_from;
END IF;
请注意,您应该小心使用谓词OR 解决方案使用谓词SALARY >= salary_from OR salary_from is NULL
为什么?您将一个查询用于两种截然不同的场景。游标可以返回所有数据或非常有限的数据,这可能需要 不同 访问方法(例如索引访问与全表扫描)。因此,您可能在一种情况下使用次优计划。
上述方法的问题在于无法扩展。如果您有四个可选条件,则需要 16 倍 IF 和高度冗余的代码。
保持上述目标有效的解决方案是什么?
使用动态SQL,但不要连接条件值,例如
SELECT LAST_NAME, SALARY FROM hr.employees WHERE salary >= 1000 and salary <= 10000
这将使绑定变量目标无效!
为了与示例保持一致,您要使用可选参数 salary_from 和 salary_to
open v_cursor for v_sql using salary_from, salary_to;
但这要求,both 绑定变量必须在查询文本中定义 - 如果您只有 salary_from,应该怎么做?
为此查询打开光标
SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from
会导致错误
ORA-01006: bind variable does not exist
诀窍是使用 dummy 谓词总是返回 true,但包含绑定变量(将被忽略)。
因此,如果您只有 salary_from 作为过滤器,您将创建以下动态 SQL
SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from AND (1=1 or SALARY <= :salary_to)
它包含两个绑定变量,优化器(带有 *shortcut 评估)会将其简化为所需的
SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from
所以工资过滤器示例的相关代码是
IF salary_from is NOT null THEN
v_sql := 'SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from';
ELSE
v_sql := 'SELECT LAST_NAME, SALARY FROM hr.employees WHERE (1 = 1 or SALARY >= :salary_from)';
END IF;
IF salary_to is NOT null THEN
v_sql := v_sql ||' AND SALARY <= :salary_to';
ELSE
v_sql := v_sql ||' AND (1=1 or SALARY <= :salary_to)';
END IF;
open v_cursor for v_sql using salary_from, salary_to;
下面是四种情况下生成的SQL概览
-- no filter
SELECT LAST_NAME, SALARY FROM hr.employees WHERE (1 = 1 or SALARY >= :salary_from) AND (1=1 or SALARY <= :salary_to)
-- salary_from
SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from AND (1=1 or SALARY <= :salary_to)
-- salary from, to
SELECT LAST_NAME, SALARY FROM hr.employees WHERE SALARY >= :salary_from AND SALARY <= :salary_to
-- salary_to
SELECT LAST_NAME, SALARY FROM hr.employees WHERE (1 = 1 or SALARY >= :salary_from) AND SALARY <= :salary_to
与此想法相似的主题:here、here 和 here