当select语句未在SYS_REFCURSOR
中返回任何记录时,如何处理?
我正在使用使用绑定变量的动态sql生成过程。
create or replace
procedure test_dynamic_sql
(
last_name varchar2,
rc out sys_refcursor,
jobid varchar2,
sdate date,
edate date,
status out varchar2,
message out varchar2 )
is
q long;
lname varchar2(240);
begin
q := 'select employee_id
from employees e
where 1=1';
if last_name is not null then
q := q || 'and (e.LAST_NAME = :LAST_NAME)';
else
q := q || 'and (1=1 or :LAST_NAME is null)';
end if;
if jobid is not null then
q := q || 'and (e.JOB_ID = :JOBID)';
else
q := q || 'and (1=1 or :JOBID is null)';
end if;
if sdate is not null then
q := q || 'and (e.hire_date >= :sdate)';
else
q := q || 'and (1=1 or :sdate is null)';
end if;
if edate is not null then
q := q || 'and (e.hire_date <= :edate)';
else
q := q || 'and (1=1 or :edate is null)';
end if;
open rc for q using last_name, jobid, sdate, edate;
/*
IF rc%NOTFOUND THEN
STATUS := 'NR';
MESSAGE := 'Not Found';
ELSE
STATUS := 'S';
MESSAGE := 'Found';
END IF;
*/
exception
when others then
STATUS :='E';
message := sqlcode||'-->'||sqlerrm;
end;
我尝试了%NOTFOUND
和%FOUND
属性,但是它不起作用。
我还尝试了NO_DATA_FOUND
异常,但也无法正常工作。
我需要返回状态为“ S”,“ E”,“ NR”
谢谢!
答案 0 :(得分:3)
此答案是要解决使用“基准光标”作为“输出参数”时遇到的问题。以下代码调用您的test_dynamic_sql()
过程。在此过程中,我们OPEN
光标,FETCH
它指向的数据,并且我们不CLOSE
光标,因为我们立即在test_dynamic_sql()
之外再次使用该光标程序。 注意的一件事-使用FETCH
时,Cursor将不再为您提供数据,必须再次打开。由于您的游标正在使用动态SQL,因此我们必须在声明其余全局变量的同一位置声明动态“查询”。
“游标不是为了重复使用而设计的:您只能阅读一次,继续前进,而在这样做时,您将丢弃以前扫描的所有行。”这个事实是从Oracle. Reuse cursor as parameter in two procedures这样的SO帖子中窃取的。
在此过程之外,我们首先必须通过使用IF语句检查游标是否存在来检查游标是否已成功初始化:IF (g_rc IS NOT NULL) THEN
。
下面的完整代码示例:
DECLARE
/* g for Global */
g_status VARCHAR2(5);
g_message VARCHAR2(100);
g_rc SYS_REFCURSOR;
/* Store Dynamic SQL Query */
g_SQL VARCHAR2(200);
/* Bind Variables */
g_jobid NUMBER;
g_last_name VARCHAR2(240);
/* Declare Global Record used to FETCH data into */
g_rec_employee employees%ROWTYPE;
PROCEDURE test_dynamic_sql(pv_last_name VARCHAR2,
p_rc OUT SYS_REFCURSOR,
pv_jobid VARCHAR2,
pv_status OUT VARCHAR2,
pv_message OUT VARCHAR2,
pv_q OUT VARCHAR2)
AS
/* Declare Record used to FETCH data into */
rec_employee employees%ROWTYPE;
/* Bind Variables */
jobid NUMBER := to_number(pv_jobid);
last_name VARCHAR2(240) := pv_last_name;
BEGIN
/* Reset/Initialize Cursor Output Variable */
p_rc := NULL;
/* Dynamic SQL statement with placeholder: */
pv_q := 'SELECT * FROM employees WHERE 1=1';
IF last_name IS NOT NULL
THEN pv_q := pv_q || ' AND (lastname = :LAST_NAME)';
ELSE pv_q := pv_q || ' AND (1=1 or :LAST_NAME is null)';
END IF;
IF jobid IS NOT NULL
THEN pv_q := pv_q || ' AND (ID = :JOBID)';
ELSE pv_q := pv_q || ' AND (1=1 or :JOBID is null)';
END IF;
/* Open cursor & specify bind argument in USING clause: */
OPEN p_rc FOR pv_q USING last_name, jobid;
LOOP
/* In order to work with any Data that a Cursor 'points' to we must FETCH the data. This allows us to use %ROWCOUNT and %NOTFOUND */
FETCH p_rc INTO rec_employee;
EXIT WHEN p_rc%NOTFOUND;
END LOOP;
IF p_rc%ROWCOUNT = 0 THEN
pv_status := 'NR';
pv_message := 'Not Found';
--EXIT;
ELSIF p_rc%ROWCOUNT = 1 THEN
pv_status := 'S';
pv_message := 'Found';
--EXIT;
ELSE
pv_status := 'MU';
pv_message := 'Multiple Records';
END IF;
--dbms_output.put_line('Final Count: ' || p_rc%ROWCOUNT);
/* Close Cursor - We don't close the Cursor here as we want to use this cursor as an OUT Parameter outside of this Proc */
CLOSE p_rc;
EXCEPTION
WHEN OTHERS THEN
pv_status :='E';
pv_message := sqlcode||'-->'||sqlerrm;
dbms_output.put_line('STATUS: ' || pv_status);
dbms_output.put_line('MESSAGE: ' || pv_message);
CLOSE p_rc;
END test_dynamic_sql;
BEGIN
g_jobid := null;
g_last_name := 'Loch';
test_dynamic_sql(pv_last_name => g_last_name,
p_rc => g_rc, /* Out Parameter */
pv_jobid => g_jobid,
pv_status => g_status, /* Out Parameter */
pv_message => g_message, /* Out Parameter */
pv_q => g_SQL /* Out Parameter */
);
/* Output OUT Variables aka Provide Output to User */
dbms_output.put_line('STATUS: ' || g_status);
dbms_output.put_line('MESSAGE: ' || g_message);
IF (g_rc IS NOT NULL) THEN
dbms_output.put_line('We have something here. Fetching Data Now:');
OPEN g_rc FOR g_sql USING g_last_name, g_jobid;
LOOP
FETCH g_rc INTO g_rec_employee;
EXIT WHEN g_rc%NOTFOUND;
/* Print the Job ID just to show it is working */
dbms_output.put_line('Job_ID: ' || g_rec_employee.id || ' is the id');
END LOOP;
dbms_output.put_line('Total of: '|| g_rc%ROWCOUNT || ' records Fetched.');
CLOSE g_rc;
ELSE
dbms_output.put_line('null');
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('Error '||TO_CHAR(SQLCODE)||': '||SQLERRM);
CLOSE g_rc;
END;
上面的方法与我对这个SO问题的第一个答案中的雇员表数据相同。
答案 1 :(得分:0)
如果由于动态SQL或显式Cursor而希望隐式Ref Cursor,则希望以尝试的方式使用游标时,会缺少一些内容。
为了使用%ROWCOUNT或%NOTFOUND,您必须首先获取游标。此链接"PL/SQL 101 : Understanding Ref Cursors"提供了有关此主题的大量信息,但要帮助回答您的问题,所需要做的就是知道您必须首先获取数据。
下图是显示“员工”表中数据的图像。 请注意,有两名姓氏为'Loch'的员工。
以下代码位于其自己的匿名块中,但可以轻松转换为过程/函数。它具有您所有必需的状态和消息。为了处理具有多个结果的搜索,我添加了一个附加的状态/消息以告知用户已返回多个记录。最后,为了简化代码的使用,我删除了除两个参数之外的所有参数。 注意:如果将过程的参数全部作为NULL传递,则生成的动态SQL将查询整个表,因为它基本上删除了WHERE子句中的所有过滤器。
DECLARE
/* Parameters */
rc SYS_REFCURSOR;
q VARCHAR2(200);
status VARCHAR2(5);
message VARCHAR2(100);
/* Declare Record used to FETCH data into */
rec_employee employees%ROWTYPE;
/* Bind Variables */
jobid NUMBER := null;
last_name VARCHAR2(240) := 'Loch';
BEGIN
/* Dynamic SQL statement with placeholder: */
q := 'SELECT * FROM employees WHERE 1=1';
IF last_name IS NOT NULL
THEN q := q || ' AND (lastname = :LAST_NAME)';
ELSE q := q || ' AND (1=1 or :LAST_NAME is null)';
END IF;
IF jobid IS NOT NULL
THEN q := q || ' AND (ID = :JOBID)';
ELSE q := q || ' AND (1=1 or :JOBID is null)';
END IF;
/* Open cursor & specify bind argument in USING clause: */
OPEN rc FOR q USING last_name, jobid;
LOOP
/* In order to work with any Data that a Cursor 'points' to we must FETCH the data. This allows us to use %ROWCOUNT and %NOTFOUND */
FETCH rc INTO rec_employee;
EXIT WHEN rc%NOTFOUND;
END LOOP;
IF rc%ROWCOUNT = 0 THEN
STATUS := 'NR';
MESSAGE := 'Not Found';
--EXIT;
ELSIF rc%ROWCOUNT = 1 THEN
STATUS := 'S';
MESSAGE := 'Found';
--EXIT;
ELSE
STATUS := 'MU';
MESSAGE := 'Multiple Records';
END IF;
dbms_output.put_line('Final Count: ' || rc%ROWCOUNT);
/* Close Cursor */
CLOSE rc;
/* Return Variables or Provide Output to User */
dbms_output.put_line('STATUS: ' || STATUS);
dbms_output.put_line('MESSAGE: ' || MESSAGE);
EXCEPTION
WHEN OTHERS THEN
STATUS :='E';
message := sqlcode||'-->'||sqlerrm;
dbms_output.put_line('STATUS: ' || STATUS);
dbms_output.put_line('MESSAGE: ' || MESSAGE);
END;