【问题标题】:collection of records to out sys_refcursor收集到 sys_refcursor 的记录
【发布时间】:2017-03-24 14:26:44
【问题描述】:

甲骨文 11g

这似乎比应该做的要难,所以我可能在这里走错了路。

我有一个生成用户定义表单的应用程序,我的数据比这复杂一点,但我的想法是——我有一个数据表,其中包含来自用户定义表单的所有数据输入

create table formData(
      id number
    , fName varchar(100)
    , lName varChar(100)
    , mName varChar(100)
    , formType varchar(100)
    ...
);

insert all 
    into formData(id,fName,lName,mName,formType)values(1,'Bob','Smith',NULL,'birthday')
    into formData(id,fName,lName,mName,formType)values(2,'Jim','Jones','Wilber','birthday')
    into formData(id,fName,lName,mName,formType)values(3,'Frank','Peterson',NULL,'general')
    into formData(id,fName,lName,mName,formType)values(4,'Alex','Anderson',NULL,'general')

我有一个表格,其中包含动态表单的字段选项

create table fieldOptions(
      id number
    , fieldName varchar(100)
    , fieldLabel varChar(100)
    , formType varchar(10)
    , fieldUsed number
    , ...
);
insert all 
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('fName','First Name','birthday',1)
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('lName','Last Name','birthday',1)
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('mName','Middle','birthday',1)
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('fName','First','general',1)
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('lName','Surname','general',1)
    into fieldOptions (fieldName,fieldLabel,formType,fieldUsed)values('mName','Middle Initial','general',0)

我想在我的包中创建一个过程,它将一个光标返回到我的 .net 页面,其中包含如下所示的数据:

其中 ID=3(一般输出)

  Label | Value
--------+---------
First   | Frank
Surname | Peterson

或其中 ID=1(生日输出)

  Label     | Value
------------+---------
First Name  | Bob
Last Name   | Smith
Middle      | NULL

我不确定是否可以在(枢轴?)查询中执行此操作。我开始玩弄通过处理数据构建的记录集合,但如果这是解决方案,我如何将记录集合放入out sys_refcursor?也许我想太多了,可以通过几个子查询来完成?向正确的方向推进将是完美的,谢谢。

【问题讨论】:

    标签: oracle plsql oracle11g


    【解决方案1】:

    假设您的formData 表结构是固定且已知的,您只需使用 case 表达式将 formOption.fName 转换为匹配的列值:

    select fo.fieldLabel as label,
      case fo.fieldName
        when 'fName' then fd.fName
        when 'lName' then fd.lName
        when 'nName' then fd.mName
      end as value
    from formData fd
    join fieldOptions fo
    on fo.formType = fd.formtype
    where fd.id = 3;
    
    LABEL                VALUE               
    -------------------- --------------------
    First                Frank               
    Surname              Peterson            
    Middle Initial                           
    
    ...
    where fd.id = 3;
    
    LABEL                VALUE               
    -------------------- --------------------
    First Name           Bob                 
    Last Name            Smith               
    Middle                                   
    

    然后您可以让您的过程为该查询打开一个引用游标,使用 ID 值的参数值。

    如果formData 结构未知,或者不是静态的,那么您可能会遇到更大的问题;但为此,您需要回退到动态 SQL。作为起点,您可以执行以下操作:

    create procedure p42 (p_id number, p_refcursor out sys_refcursor) as
      l_stmt varchar2(32767);
    begin
      l_stmt := 'select fo.fieldLabel as label, case lower(fo.fieldName) ';
      for r in (
        select column_name from user_tab_columns
        where table_name = 'FORMDATA'
        and data_type = 'VARCHAR2'
      )
      loop
        l_stmt := l_stmt || ' when ''' || lower(r.column_name) || ''' then fd.' || r.column_name;
      end loop;
      l_stmt := l_stmt || ' end as value '
        || 'from formData fd '
        || 'join fieldOptions fo '
        || 'on fo.formType = fd.formtype '
        || 'where fd.id = :d1';
      open p_refcursor for l_stmt using p_id;
    end p42;
    /
    

    这会使用表中实际定义的所有列在运行时创建案例表达式;因为您的 fieldName 的大小写可能与数据字典不匹配,所以我将所有内容都强制为小写以进行比较。我还限制为字符串列以使案例更简单,但如果您需要其他数据类型的列,则案例表达式的每个 when ... then 子句都需要检查该列的数据类型(您可以将其添加到 @ 987654328@ cursor) 并将实际列值适当地转换为字符串。所有的值都必须以相同的数据类型结束,所以它必须是字符串,真的。

    不管怎样,通过 SQL*Plus 进行测试:

    var rc refcursor
    exec p42(1, :rc);
    
    PL/SQL procedure successfully completed.
    
    print rc
    
    LABEL                VALUE
    -------------------- --------------------
    First Name           Bob
    Last Name            Smith
    Middle
    
    3 rows selected.
    

    您可以查询fieldOptions 来获取可能的列名,但您仍然可能遇到数据类型转换问题,这将更难处理;但如果所有引用的 formData 字段实际上都是字符串,那么这将是:

      for r in (
        select fo.fieldName
        from formData fd
        join fieldOptions fo
        on fo.formType = fd.formtype
        where fd.id = p_id
      )
      loop
        l_stmt := l_stmt || ' when ''' || r.fieldName || ''' then fd.' || r.fieldName;
      end loop;
    

    【讨论】:

    • 结构是已知的,但不是静态的。当随着需求的变化而添加字段时,有没有一种方法不涉及更改查询?
    • @Travis - 它应该是动态完成的。我添加了一个简单的示例,将该查询转换为基于当前表列构建案例表达式的动态版本。 (或者,我想,当表发生变化时,您可以根据数据字典重新生成过程;但这也很麻烦,在包中更是如此)。
    • 它是否需要是动态的,因为它是像select fo.fieldLabel as label, to_char((select fo.fieldName from formData where id = 1)) as value 这样的唯一方法,还是有更简单的方法?我试图运行它,我只得到了 fieldName 的值,这几乎不是我所期望的。即fNamelName
    • @Travis - 如果在编写代码时不知道表结构,那么是的,它必须是动态的。 fo.fieldName 必须替换为实际的字段名称,这只能动态完成,唉。不知道你的意思是什么,我从动态查询(两个版本的光标)中得到相同的输出 - 来自字段的值,而不是字段名称?
    • 所以,我尝试了select fo.fieldLabel as label, (select fo.fieldName from formData where id = 1) as value from fieldOptions fo inner join formData fd on fd.formType = fo.formType where fd.id = 1 and fo.inUse = 1 并得到了First Name | fName,这是怎么回事?
    【解决方案2】:

    如果您的逻辑很复杂,您可以考虑使用Oracle table functions 以编程方式生成查询行。基本上,您可以使用 table() 运算符生成记录集合并“将其转换为表”,如 select ... from table(your_table_function) ... ,这可以通过标准 sys_refcursor 进行。取自链接示例:

    -- Create the types to support the table function.
    DROP TYPE t_tf_tab;
    DROP TYPE t_tf_row;
    CREATE TYPE t_tf_row AS OBJECT (
      id           NUMBER,
      description  VARCHAR2(50)
    );
    /
    CREATE TYPE t_tf_tab IS TABLE OF t_tf_row;
    /
    -- Build the table function itself.
    CREATE OR REPLACE FUNCTION get_tab_tf (p_rows IN NUMBER) RETURN t_tf_tab AS
      l_tab  t_tf_tab := t_tf_tab();
    BEGIN
      FOR i IN 1 .. p_rows LOOP
        l_tab.extend;
        l_tab(l_tab.last) := t_tf_row(i, 'Description for ' || i);
      END LOOP;
      RETURN l_tab;
    END;
    /
    -- Test it.
    SELECT *
    FROM   TABLE(get_tab_tf(10))
    ORDER BY id DESC;
    
            ID DESCRIPTION
    ---------- --------------------------------------------------
            10 Description for 10
             9 Description for 9
             8 Description for 8
             7 Description for 7
             6 Description for 6
             5 Description for 5
             4 Description for 4
             3 Description for 3
             2 Description for 2
             1 Description for 1
    10 rows selected.
    

    如果集合很大,使用pipelined table function 流式传输它会很有用,这有点像c# 中的yield。同样,按照链接页面中的示例:

    -- Build a pipelined table function.
    CREATE OR REPLACE FUNCTION get_tab_ptf (p_rows IN NUMBER) RETURN t_tf_tab PIPELINED AS
    BEGIN
      FOR i IN 1 .. p_rows LOOP
        PIPE ROW(t_tf_row(i, 'Description for ' || i));   
      END LOOP;
    
      RETURN;
    END;
    /
    
    -- Test it.
    SELECT *
    FROM   TABLE(get_tab_ptf(10))
    ORDER BY id DESC;
    
            ID DESCRIPTION
    ---------- --------------------------------------------------
            10 Description for 10
             9 Description for 9
             8 Description for 8
             7 Description for 7
             6 Description for 6
             5 Description for 5
             4 Description for 4
             3 Description for 3
             2 Description for 2
             1 Description for 1
    
    10 rows selected.
    
    SQL>
    

    【讨论】:

      猜你喜欢
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-25
      相关资源
      最近更新 更多