如何创建可以返回特定实体以及所有实体的 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 选择没有具有特定值的一对多实体的所有行

核心数据:返回另一个实体的对象的谓词

以编程方式:列出在实体框架模型中找到的所有存储过程

如何从 ManagedObjectContext 中删除给定实体的所有对象

如何按包含的实体搜索/选择,但将所有相关实体包含到结果集中

如何从核心数据中获取父实体的所有子实体,以及如何将父数据用作 UITableview