有没有办法动态设置 SELECT 查询 FOR 循环
Is there a way to dynamically set SELECT Query for a FOR LOOP
我正在尝试的是根据特定条件迭代 SELECT。
CREATE OR REPLACE PROCEDURE procedure
IS
selectForLoop varchar2(256);
v_birthdate date;
BEGIN
select X into v_birthdate from Y where C = Z;
IF true THEN
selectForLoop := 'Select name from employees';
ELSE
selectForLoop := 'Select name from employees where birthdate = v_birthdate';
END IF;
FOR val in (selectForLoop)
LOOP
BusinessLogic
END LOOP;
我能做的是:
CREATE OR REPLACE PROCEDURE procedure
IS
v_birthdate date;
BEGIN
select X into v_birthdate from Y where C = Z;
If true THEN
FOR i IN (Select name from employees)
LOOP
BusinessLogic
END LOOP;
ELSE
FOR i IN (Select name from employees where birthdate = v_birthdate
LOOP
BusinessLogic
END LOOP;
END IF;
这将是一个解决方案,但业务逻辑在这两种方式上都是相同的,只是我想要迭代的数据不同。我真的很想阻止那种肮脏的复制粘贴尝试。
有什么建议吗?
您可以检查查询中的参数是否为 NULL。示例(基于示例数据emp/dept)
CREATE OR REPLACE PROCEDURE list_employees (hired_after_i DATE DEFAUlT NULL)
AS
BEGIN
FOR r IN
(SELECT * FROM emp
WHERE hiredate > hired_after_i OR
hired_after_i IS NULL)
LOOP
dbms_output.put_line(r.ename||' hired on '||r.hiredate);
END LOOP;
END;
/
Procedure LIST_EMPLOYEES compiled
set serveroutput on size 999999
BEGIN
list_employees;
END;
/
SMITH hired on 17-DEC-1980
ALLEN hired on 20-FEB-1981
WARD hired on 22-FEB-1981
JONES hired on 02-APR-1981
MARTIN hired on 28-SEP-1981
BLAKE hired on 01-MAY-1981
CLARK hired on 09-JUN-1981
SCOTT hired on 19-APR-1987
KING hired on 17-NOV-1981
TURNER hired on 08-SEP-1981
ADAMS hired on 23-MAY-1987
JAMES hired on 03-DEC-1981
FORD hired on 03-DEC-1981
MILLER hired on 23-JAN-1982
PL/SQL procedure successfully completed.
BEGIN
list_employees(hired_after_i => DATE'1982-01-01');
END;
/
SCOTT hired on 19-APR-1987
ADAMS hired on 23-MAY-1987
MILLER hired on 23-JAN-1982
PL/SQL procedure successfully completed.
如果您想查看一个集合,但它的填充方式不同,那么这也是一种可能。对于此示例,我根据参数的值以 2 种不同的方式填充 pl/sql 集合,然后循环遍历集合一次。
create or replace PROCEDURE list_employees2 (some_input_variable VARCHAR2)
AS
TYPE emp_t IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
t_emp emp_t;
BEGIN
IF some_input_variable = 'A' THEN
SELECT ename
BULK COLLECT INTO t_emp
FROM emp WHERE hiredate > DATE'1982-01-01';
ELSIF some_input_variable = 'B' THEN
t_emp(1) := 'KOEN';
t_emp(2) := 'MIKE';
END IF;
FOR i IN 1 .. t_emp.COUNT LOOP
dbms_output.put_line(t_emp(i));
END LOOP;
END;
/
您可以在 PL/SQL 中使用 cursors and cursor variables 在查询之间动态切换。
CREATE OR REPLACE PROCEDURE procedure
IS
selectForLoop varchar2(256);
v_name employees%name%TYPE;
v_birthdate date;
v_cursor SYS_REFCURSOR;
BEGIN
IF condition THEN
OPEN v_cursor FOR SELECT name FROM employees;
ELSE
OPEN v_cursor FOR SELECT name FROM employees WHERE birthdate =
v_birthdate;
END IF;
LOOP
FETCH v_cursor INTO v_name;
EXIT WHEN v_cursor%NOTFOUND;
BusinessLogic;
END LOOP;
CLOSE v_cursor;
END;
您还可以将游标变量作为参数传递给您的业务逻辑,并将循环移动到业务逻辑过程中:
OPEN v_cursor FOR selectForLoop;
BusinessLogic(v_cursor);
CLOSE v_cursor;
CREATE OR REPLACE
PROCEDURE BusinessLogic(p_cursor SYS_REFCURSOR IN) IS
v_name employees%name%TYPE;
BEGIN
LOOP
FETCH p_cursor INTO v_name;
EXIT WHEN p_cursor%NOTFOUND;
-- Do business logic
END LOOP;
END;
你有两个objectives
保留代码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;
请注意,您应该小心使用谓词SALARY >= salary_from OR salary_from is NULL
[=26] OR
解决方案 =]
为什么?您将 一个查询 用于两种截然不同的场景。游标可以 returns 所有数据或非常有限的数据,这可能需要 不同的 访问方法(例如索引访问与完整 table 扫描)。因此,在一种情况下,您 可能 和次优计划。
上述方法的问题在于它无法缩放。如果你有四个可选标准,你将需要 16 倍 IF
和一个高度冗余的代码。
保持上述 objective 有效的解决方案是什么?
使用动态SQL,但不要连接条件值,例如
SELECT LAST_NAME, SALARY FROM hr.employees WHERE salary >= 1000 and salary <= 10000
这将使绑定变量无效 objective!
为了与带有可选参数 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)
它包含绑定变量,优化器(使用*快捷方式求值)会将其简化为所需的
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 and
我正在尝试的是根据特定条件迭代 SELECT。
CREATE OR REPLACE PROCEDURE procedure
IS
selectForLoop varchar2(256);
v_birthdate date;
BEGIN
select X into v_birthdate from Y where C = Z;
IF true THEN
selectForLoop := 'Select name from employees';
ELSE
selectForLoop := 'Select name from employees where birthdate = v_birthdate';
END IF;
FOR val in (selectForLoop)
LOOP
BusinessLogic
END LOOP;
我能做的是:
CREATE OR REPLACE PROCEDURE procedure
IS
v_birthdate date;
BEGIN
select X into v_birthdate from Y where C = Z;
If true THEN
FOR i IN (Select name from employees)
LOOP
BusinessLogic
END LOOP;
ELSE
FOR i IN (Select name from employees where birthdate = v_birthdate
LOOP
BusinessLogic
END LOOP;
END IF;
这将是一个解决方案,但业务逻辑在这两种方式上都是相同的,只是我想要迭代的数据不同。我真的很想阻止那种肮脏的复制粘贴尝试。 有什么建议吗?
您可以检查查询中的参数是否为 NULL。示例(基于示例数据emp/dept)
CREATE OR REPLACE PROCEDURE list_employees (hired_after_i DATE DEFAUlT NULL)
AS
BEGIN
FOR r IN
(SELECT * FROM emp
WHERE hiredate > hired_after_i OR
hired_after_i IS NULL)
LOOP
dbms_output.put_line(r.ename||' hired on '||r.hiredate);
END LOOP;
END;
/
Procedure LIST_EMPLOYEES compiled
set serveroutput on size 999999
BEGIN
list_employees;
END;
/
SMITH hired on 17-DEC-1980
ALLEN hired on 20-FEB-1981
WARD hired on 22-FEB-1981
JONES hired on 02-APR-1981
MARTIN hired on 28-SEP-1981
BLAKE hired on 01-MAY-1981
CLARK hired on 09-JUN-1981
SCOTT hired on 19-APR-1987
KING hired on 17-NOV-1981
TURNER hired on 08-SEP-1981
ADAMS hired on 23-MAY-1987
JAMES hired on 03-DEC-1981
FORD hired on 03-DEC-1981
MILLER hired on 23-JAN-1982
PL/SQL procedure successfully completed.
BEGIN
list_employees(hired_after_i => DATE'1982-01-01');
END;
/
SCOTT hired on 19-APR-1987
ADAMS hired on 23-MAY-1987
MILLER hired on 23-JAN-1982
PL/SQL procedure successfully completed.
如果您想查看一个集合,但它的填充方式不同,那么这也是一种可能。对于此示例,我根据参数的值以 2 种不同的方式填充 pl/sql 集合,然后循环遍历集合一次。
create or replace PROCEDURE list_employees2 (some_input_variable VARCHAR2)
AS
TYPE emp_t IS TABLE OF emp.ename%TYPE INDEX BY BINARY_INTEGER;
t_emp emp_t;
BEGIN
IF some_input_variable = 'A' THEN
SELECT ename
BULK COLLECT INTO t_emp
FROM emp WHERE hiredate > DATE'1982-01-01';
ELSIF some_input_variable = 'B' THEN
t_emp(1) := 'KOEN';
t_emp(2) := 'MIKE';
END IF;
FOR i IN 1 .. t_emp.COUNT LOOP
dbms_output.put_line(t_emp(i));
END LOOP;
END;
/
您可以在 PL/SQL 中使用 cursors and cursor variables 在查询之间动态切换。
CREATE OR REPLACE PROCEDURE procedure
IS
selectForLoop varchar2(256);
v_name employees%name%TYPE;
v_birthdate date;
v_cursor SYS_REFCURSOR;
BEGIN
IF condition THEN
OPEN v_cursor FOR SELECT name FROM employees;
ELSE
OPEN v_cursor FOR SELECT name FROM employees WHERE birthdate =
v_birthdate;
END IF;
LOOP
FETCH v_cursor INTO v_name;
EXIT WHEN v_cursor%NOTFOUND;
BusinessLogic;
END LOOP;
CLOSE v_cursor;
END;
您还可以将游标变量作为参数传递给您的业务逻辑,并将循环移动到业务逻辑过程中:
OPEN v_cursor FOR selectForLoop;
BusinessLogic(v_cursor);
CLOSE v_cursor;
CREATE OR REPLACE
PROCEDURE BusinessLogic(p_cursor SYS_REFCURSOR IN) IS
v_name employees%name%TYPE;
BEGIN
LOOP
FETCH p_cursor INTO v_name;
EXIT WHEN p_cursor%NOTFOUND;
-- Do business logic
END LOOP;
END;
你有两个objectives
保留代码
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;
请注意,您应该小心使用谓词SALARY >= salary_from OR salary_from is NULL
[=26] OR
解决方案 =]
为什么?您将 一个查询 用于两种截然不同的场景。游标可以 returns 所有数据或非常有限的数据,这可能需要 不同的 访问方法(例如索引访问与完整 table 扫描)。因此,在一种情况下,您 可能 和次优计划。
上述方法的问题在于它无法缩放。如果你有四个可选标准,你将需要 16 倍 IF
和一个高度冗余的代码。
保持上述 objective 有效的解决方案是什么?
使用动态SQL,但不要连接条件值,例如
SELECT LAST_NAME, SALARY FROM hr.employees WHERE salary >= 1000 and salary <= 10000
这将使绑定变量无效 objective!
为了与带有可选参数 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)
它包含绑定变量,优化器(使用*快捷方式求值)会将其简化为所需的
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 and