PL / SQL创建包含结果集连接的过程

时间:2016-03-28 14:31:23

标签: oracle join stored-procedures cursor temp-tables

我想在PL / SQL中创建一个包含5个步骤的过程。步骤1和2首先执行并返回ID。在第3步中,我们有一个SELECT语句,其条件返回ID。然后,我希望获取SELECT语句的所有结果,并在另一个JOIN语句的SELECT中使用它们,并再次使用THOSE结果在第3 SELECT语句中JOIN。从我所看到的情况来看,我无法在CURSOR语句中使用JOIN。我的一些同事建议我将结果保存在CURSOR中,然后使用循环迭代每一行并将该数据用于下一个SELECT。然而,由于我要做2选择,这将创建一个巨大的内部循环分支,这正是我试图避免的。

另一个建议是使用Temprary Tables来存储数据。但是,许多用户可以同时执行此过程,并且表的数据会相互冲突。现在我正在查看根据会话过滤掉数据的LOCAL临时表,但我不确定我是否想为我的程序创建虚拟表,因为我想避免在模式中留下垃圾(此过程适用于应用程序的自定义部分)。有这样做的标准方法吗?有什么想法吗?

样品:

DECLARE
USERID INT := 1000000;
TEXT1 VARCHAR(100);
TEXT_INDEX INT;
CURSOR NODES IS SELECT * FROM NODE_TABLE WHERE DESCRIPTION LIKE TEXT || '%';
CURSOR USERS IS SELECT * FROM USERGROUPS JOIN NODES ON NODES.ID = USERGROUPS.ID;
BEGIN
  SELECT TEXT INTO TEXT1 FROM TABLE_1 WHERE ID = USERID;
  TEXT_INDEX = INSTR(TEXT, '-');
  TEXT = SUBSTR(TEXT, 0, TEXT_INDEX);
  OPEN NODES;
  OPEN USERS;
END;

注意:这不起作用。 Oracle不支持游标之间的连接。 注意2:这可以在一个查询中完成,但为了参数(在我的实际用例中),我想在一个过程中打破这些步骤。示例代码描述了我尝试实现游标之间的IF连接的工作原理。但是他们没有,我正在寻找替代方案。

2 个答案:

答案 0 :(得分:1)

我最终使用了一个函数(虽然也可以使用一个过程)以及。我学到的东西应该注意:

  1. PL / SQL函数只能返回已提前在架构中声明且清晰的类型。您无法创建返回MY_TABLE%ROWTYPE之类的函数,即使类似信息似乎可用,也是不可接受的。您必须创建自定义类型MY_TABLE%ROWTYPE才能返回它。
  2. Oracle将声明类型的表与%ROWTYPE的表区别对待。起初这让我感到很困惑,但从我收集的内容来看,这就是它的运作方式。

    DECLARE TYPE MY_CUSTOM_TABLE IS TABLE OF MY_TABLE%ROWTYPE;
    
  3. 声明MY_TABLE行类型的集合。为了添加到此,我们必须使用查询MY_TABLE的SQL语句中的BULK COLLECT INTO。生成的集合不能在JOIN语句中使用,这是不可查询的,并且不能由函数返回。

    DECLARE
    CREATE TYPE MY_CUSTOM_TYPE AS OBJECT (COL_A NUMBER, COL_B NUMBER);
    CREATE TYPE MY_CUSTOM_TABLE AS TABLE OF MY_CUSTOM_TYPE;
    my_custom_tab MY_CUSTOM_TABLE;
    

    这创建了my_custom_tab,它是(不是集合),如果使用TABLE(my_custmo_tab)语句中的FROM查询,则可以查询。作为在模式中预先声明的表,可以从函数返回。但是,它不能使用BULK COLLECT INTO填充,因为它不是集合。我们必须使用正常的SELECT INTO语句。但是,如果我们想要使用包含2个数字列的现有表中的数据填充它,我们不能简单地执行SELECT * INTO my_custom_tab FROM DOUBLE_NUMBER_TABLE,因为my_custom_tab尚未初始化且不包含足够的行接收数据。如果我们不知道查询返回了多少行,我们就无法对其进行初始化。填充表格的技巧是使用CAST命令并将我们的选择结果集转换为MY_CUSTOM_TABLE,然后添加它。

    SELECT CAST(MULTISET(SELECT COL_A, COL_B FROM DOUBLE_NUMBER_TABLE) AS MY_CUSTOM_TABLE) INTO my_custom_tab FROM DUAL
    

    现在,我们可以通过使用TABLE()函数轻松地在查询等中使用my_custom_tab。

    SELECT * FROM TABLE(my_custom_tab)
    

    有效。

答案 1 :(得分:-1)

您可以通过多种方式进行此类分解,但所有这些分解在与单个SQL语句的同步中都会显着降低性能。 可维护性改进也值得怀疑,具体情况取决于具体情况 要查看所有可能性,请查看documentation

以下是一些基于简单逻辑的可能变体:

  1. 根据给定的Id计算Oracle用户名前缀;
  2. 获取名称以此前缀开头的所有用户;
  3. 查找步骤2中用户拥有的所有表格;
  4. 计算找到的表格总数。
  5. <强> 1。流水线

    准备功能使用的类型:

    create or replace type TUserRow as object (
      username varchar2(30), 
      user_id  number,
      created  date
    )
    /
    
    create or replace type TTableRow as object (
      owner varchar2(30),  
      table_name varchar2(30),
      status     varchar2(8),
      logging    varchar2(3)
      -- some other useful fields here
    )
    /
    
    create or replace type TUserList as table of TUserRow
    /
    create or replace type TTableList as table of TTableRow
    /
    

    通过用户ID查找前缀的简单功能:

    create or replace function GetUserPrefix(piUserId in number) return varchar2
    is
      vUserPrefix varchar2(30);
    begin
    
      select substr(username,1,3) into vUserPrefix
      from all_users 
      where user_id = piUserId;
    
      return vUserPrefix;
    end;
    /
    

    搜索用户的功能:

    create or replace function GetUsersPipe(
      piNameStart in varchar2
    ) 
      return TUserList pipelined
    as
      vUserList TUserList;
    begin
    
      for cUsers in (
        select * 
        from 
          all_users 
        where 
          username like piNameStart||'%'
      )
      loop    
        pipe row( TUserRow(cUsers.username, cUsers.user_id, cUsers.created) ) ;
      end loop;
    
      return;
    end;
    

    搜索表的函数:

    create or replace function GetUserTablesPipe(
      piUserNameStart in varchar2
    ) 
      return TTableList pipelined
    as
      vTableList TTableList;
    begin
    
      for cTables in (
        select *
        from 
          all_tables tab_list,
          table(GetUsersPipe(piUserNameStart)) user_list
        where 
          tab_list.owner = user_list.username
      )    
      loop    
        pipe row ( TTableRow(cTables.owner, cTables.table_name, cTables.status, cTables.logging) );
      end loop;
    
      return;
    end;
    

    代码中的用法:

    declare
      vUserId     number := 5;
      vTableCount number;
    begin
      select count(1) into vTableCount
      from table(GetUserTablesPipe(GetUserPrefix(vUserId)));
      dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
    end;
    

    <强> 2。简单的表格功能

    此解决方案使用与上述流水线函数相同的类型。

    搜索用户的功能:

    create or replace function GetUsers(piNameStart in varchar2) return TUserList
    as
      vUserList TUserList;
    begin
    
      select TUserRow(username, user_id, created) 
      bulk collect into vUserList
      from 
        all_users 
      where 
        username like piNameStart||'%'
      ;
    
      return vUserList;
    end;
    /
    

    搜索表的函数:

    create or replace function GetUserTables(piUserNameStart in varchar2) return TTableList
    as
      vTableList TTableList;
    begin
    
      select TTableRow(owner, table_name, status, logging)
      bulk collect into vTableList
      from 
        all_tables tab_list,
        table(GetUsers(piUserNameStart)) user_list
      where 
        tab_list.owner = user_list.username
      ;
    
      return vTableList;
    end;
    /
    

    代码中的用法:

    declare
      vUserId     number := 5;
      vTableCount number;
    begin
      select count(1) into vTableCount
      from table(GetUserTables(GetUserPrefix(vUserId)));
      dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
    end;
    

    第3。 cursor - xml - cursor

    它是一个特定的案例,可以在没有用户定义类型的情况下实现,但性能损失很大,涉及不需要的类型转换,并且可维护性低。

    搜索用户的功能:

    create or replace function GetUsersRef(
      piNameStart in varchar2
    ) 
      return sys_refcursor
    as
      cUserList sys_refcursor;
    begin
    
      open cUserList for 
        select * from all_users
        where username like piNameStart||'%'
      ;      
    
      return cUserList;
    end;
    

    搜索表的函数:

    create or replace function GetUserTablesRef(
      piUserNameStart in varchar2
    ) 
      return sys_refcursor
    as
      cTableList sys_refcursor;
    begin
    
      open cTableList for 
        select 
          tab_list.*
        from 
          (
            XMLTable('/ROWSET/ROW'
              passing xmltype(GetUsersRef(piUserNameStart))
              columns 
                username varchar2(30) path '/ROW/USERNAME'
            )
          )           user_list,
          all_tables  tab_list   
        where 
          tab_list.owner = user_list.username
      ;
    
      return cTableList;
    end;
    

    代码中的用法:

    declare
      vUserId     number := 5;
      vTableCount number;
    begin
    
      select count(1) into vTableCount
      from 
        XMLTable('/ROWSET/ROW'
          passing xmltype(GetUserTablesRef(GetUserPrefix(vUserId)))
          columns 
            table_name varchar2(30) path '/ROW/TABLE_NAME'
        )
      ;
    
      dbms_output.put_line('Users with name started with "'||GetUserPrefix(vUserId)||'" owns '||vTableCount||' tables');
    end;
    

    当然,所有变体都可以混合使用,但SQL至少在简单的情况下看起来更好:

    declare
      vUserId     number := 5;
      vUserPrefix varchar2(100);
      vTableCount number;
    begin
    
      -- Construct prefix from Id
      select max(substr(user_list.username,1,3))
      into vUserPrefix
      from 
        all_users user_list
      where 
        user_list.user_id = vUserId
      ;
    
      -- Count number of tables owned by users with name started with vUserPrefix string 
      select 
        count(1) into vTableCount
      from   
        all_users   user_list,
        all_tables  table_list
      where 
        user_list.username like vUserPrefix||'%'
        and
        table_list.owner = user_list.username
      ;
    
      dbms_output.put_line('Users with name started with "'||vUserPrefix||'" owns '||vTableCount||' tables');
    
    end;
    

    P.S。所有代码仅用于演示目的:无优化等。