如何创建可以返回特定实体以及所有实体的 Oracle 存储过程
Posted
技术标签:
【中文标题】如何创建可以返回特定实体以及所有实体的 Oracle 存储过程【英文标题】:How to create Oracle stored procedure which can return specific entities as well all entity 【发布时间】:2011-05-12 20:06:21 【问题描述】:我是存储过程的初学者。
我们需要创建一个存储过程,它应该返回所有记录或只返回作为IN参数传递的,过程返回游标。
如果通过传递accountId
列表调用过程,它应该只返回那些帐户,否则返回所有帐户。
如果你能举个例子那就太好了。
【问题讨论】:
【参考方案1】:这是一个简单的例子:
考虑表格:PERSONS (person_id, name)
如果没有提供参数,此函数将返回一个游标,该游标返回一条记录或所有记录:
CREATE FUNCTION get_person
(person_id IN persons.person_id%TYPE := NULL)
RETURN SYS_REFCURSOR IS
rc SYS_REFCURSOR;
BEGIN
OPEN rc FOR
SELECT *
FROM persons p
WHERE p.person_id = get_person.person_id
OR get_person.person_id IS NULL;
RETURN rc;
END;
【讨论】:
【参考方案2】:我们从一个嵌套表类型开始,我们可以使用它来传递一个数字列表。这需要是 SQL 类型,因为我们将在 select 语句中使用它。
create or replace type numbers_nt as table of number
/
这是一个以数字集合为参数的函数。如果集合被填充,它将使用这些数字来限制对 EMP 表的查询,否则它会选择所有记录。这使用动态 SQL 返回一个引用游标结果集。
create or replace function qry_emps
(p_nt in numbers_nt)
return sys_refcursor
as
rv sys_refcursor;
stmt varchar2(32767) := 'select * from emp';
begin
if p_nt.count() > 0
then
stmt := stmt || ' where empno in ( select * from table(:1) )';
open rv for stmt
using p_nt;
else
open rv for stmt;
end if;
return rv;
end qry_emps;
/
所以,首先我们传递一个填充的集合:
SQL> set serveroutput on
SQL>
SQL> declare
2 empty_nt numbers_nt := new numbers_nt();
3 pop_nt numbers_nt := new numbers_nt(7876,8083,7788);
4 rc sys_refcursor;
5 lrec emp%rowtype;
6 begin
7 rc := qry_emps(pop_nt);
8 dbms_output.put_line ( 'Three rows');
9
10 loop
11 fetch rc into lrec;
12 exit when rc%notfound;
13 dbms_output.put_line('empno = '||lrec.empno);
14 end loop;
15
16 dbms_output.put_line ( 'Done');
17 end;
18 /
Three rows
empno = 7876
empno = 8083
empno = 7788
Done
PL/SQL procedure successfully completed.
SQL>
现在我们编辑匿名块以传递一个空集合:
SQL> declare
2 empty_nt numbers_nt := new numbers_nt();
3 rc sys_refcursor;
4 lrec emp%rowtype;
5 begin
6 rc := qry_emps(empty_nt);
7 dbms_output.put_line ( 'all rows');
8
9 loop
10 fetch rc into lrec;
11 exit when rc%notfound;
12 dbms_output.put_line('empno = '||lrec.empno);
13 end loop;
14
15 dbms_output.put_line ( 'Done');
16 end;
17 /
all rows
empno = 8083
empno = 8084
empno = 8085
empno = 7369
empno = 7499
empno = 7521
empno = 7566
empno = 7654
empno = 7698
empno = 7782
empno = 7788
empno = 7839
empno = 7844
empno = 7876
empno = 7900
empno = 7902
empno = 7934
empno = 8060
empno = 8061
empno = 8100
empno = 8101
Done
PL/SQL procedure successfully completed.
SQL>
【讨论】:
【参考方案3】:以下使用PIPELINED 函数返回行。流水线函数的好处是它们返回与函数终止异步的行(您立即开始获取行,而不是在结尾处获取所有行)。它们也可以针对并行查询进行优化。如此明确的性能优势。
另外,返回游标是强类型的(不像 sys_refcursor 那样弱,当底层表发生变化时可以看到运行时异常等)。
set echo on
set serveroutput on
drop table people;
create table people
(
pid number primary key,
name varchar2(100),
address varchar2(100),
city varchar2(100),
state varchar2(2)
);
insert into people values (1, 'John Smith', '123 Main St', 'Denver', 'CO');
insert into people values (2, 'Jane Doe', '456 West St', 'Ft Lauderdale', 'FL');
insert into people values (3, 'Pete Rose', '789 North Ave', 'Philadelphia', 'PA');
commit;
创建类型:
create or replace package refcur_pkg is
type people_tab is table of people%rowtype;
end refcur_pkg;
create or replace type pid_tab as table of number;
还有main函数(放什么业务逻辑在这里)
-- pipelined function to return people based on list of people ids
create or replace function get_people(pids in pid_tab)
return refcur_pkg.people_tab pipelined
IS
v_people_row people%rowtype;
begin
--
-- Note: business rule is no input ids returns ALL rows:
--
if (pids is null or pids.count = 0) then
-- return all rows
for rec in (select * from people)
loop
pipe row(rec);
end loop;
else
-- return rows based on ids
for rec in (select * from people where pid in (select * from table(pids)))
loop
pipe row(rec);
end loop;
end if;
end;
一些使用示例
-- EXAMPLES
-- get any/all people with any of these ids
select * from table(get_people(new pid_tab(1,3,4,5)));
-- gets nobody (nobody with this pid)
select * from table(get_people(new pid_tab(-1)));
-- get ALL people
select * from table(get_people(new pid_tab()));
-- also gets ALL people
select * from table(get_people(NULL));
【讨论】:
【参考方案4】:您可以通过将声明的表类型传递给过程来做到这一点。
这是我的测试表和数据:
CREATE TABLE accounts
( account_id NUMBER
, NAME VARCHAR2(100)
);
INSERT INTO accounts values ( 1, 'Tom Selleck');
INSERT INTO accounts VALUES ( 2, 'Elvis Presley');
INSERT INTO accounts VALUES ( 3, 'Morgan Freeman');
INSERT INTO accounts values ( 4, 'Harry Morgan');
commit;
现在我创建声明的对象类型和表类型:
CREATE TYPE accountId_rec AS OBJECT ( account_id NUMBER );
CREATE TYPE accountid_tbl AS TABLE OF accountid_rec;
接下来是函数:
CREATE OR REPLACE
FUNCTION get_accounts ( p_accounts IN accountId_tbl )
RETURN sys_refcursor
IS
retcur sys_refcursor;
BEGIN
IF ( p_accounts IS NULL OR p_accounts.count < 1 ) THEN
OPEN retcur FOR SELECT * FROM accounts;
ELSE
OPEN retcur FOR SELECT * FROM accounts WHERE account_id IN ( SELECT account_id FROM TABLE( p_accounts ));
END IF;
RETURN retcur;
EXCEPTION
WHEN OTHERS THEN dbms_output.put_line('get_accounts error: '||sqlerrm);
END;
现在,用 pl/sql 块测试函数:
DECLARE
p_accounts accountId_tbl := accountId_tbl();
account_rec accounts%rowtype;
ref_cur sys_refcursor;
BEGIN
dbms_output.put_line('Test with no Account ID''s.');
ref_cur := get_accounts( p_accounts );
LOOP
FETCH ref_cur INTO account_rec;
EXIT WHEN ref_cur%NOTFOUND;
dbms_output.put_line('Account ID: '||account_rec.account_id||', Name: '||account_rec.name);
END LOOP;
dbms_output.put_line('');
-- now let's test with account ids provided.
dbms_output.put_line('Test with Account ID''s.');
p_accounts.EXTEND;
p_accounts( p_accounts.count ) := accountId_rec(2);
p_accounts.EXTEND;
p_accounts( p_accounts.count ) := accountId_rec(4);
-- get the new ref_cur
ref_cur := get_accounts( p_accounts );
LOOP
FETCH ref_cur INTO account_rec;
EXIT WHEN ref_cur%NOTFOUND;
dbms_output.put_line('Account ID: '||account_rec.account_id||', Name: '||account_rec.name);
END LOOP;
EXCEPTION
WHEN OTHERS THEN dbms_output.put_line('whoops: '||sqlerrm);
END;
以及测试结果:
Test with no Account ID's.
Account ID: 1, Name: Tom Selleck
Account ID: 2, Name: Elvis Presley
Account ID: 3, Name: Morgan Freeman
Account ID: 4, Name: Harry Morgan
Test with Account ID's.
Account ID: 2, Name: Elvis Presley
Account ID: 4, Name: Harry Morgan
我希望这会有所帮助。
【讨论】:
以上是关于如何创建可以返回特定实体以及所有实体的 Oracle 存储过程的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Doctrine 选择没有具有特定值的一对多实体的所有行