将记录集合到sys_refcursor中

时间:2017-03-24 14:26:44

标签: oracle plsql oracle11g

Oracle 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

我不确定我是否可以在(pivot?)查询中执行此操作。我开始玩弄通过处理数据构建的记录集合,但如果这是解决方案,我将如何将记录集合转换为out sys_refcursor?也许我在想这个,可以用一些子查询来完成它?在正确的方向推动将是完美的,谢谢。

2 个答案:

答案 0 :(得分:2)

假设您的formData表结构已修复且已知,您可以使用案例表达式将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;
/

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

无论如何,从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;

答案 1 :(得分:0)

如果您的逻辑很复杂,可以考虑使用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>