collection of records to out sys_refcursor

时间秒杀一切 提交于 2020-01-03 17:02:59

问题


Oracle 11g

This seems harder than it should be so I might be on the wrong path here.

I have an application that generates user defined forms, my data is a bit more complicated than this but the idea is -- I have a data table which contains all the data input from the user defined forms

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')

I have a table which contains the field options for the dynamic forms

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)

I want to create a procedure in my package that will return a cursor to my .net page that contains data that looks like:

where ID=3 (general output)

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

or where ID=1 (birthday output)

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

I'm unsure if I can do this in a (pivot?) query. I started toying with a collection of records built by processing the data but how would I get a collection of records into an out sys_refcursor if that's the solution? Perhaps I'm over thinking this and it can be done with a few sub queries? A shove in the right direction would be perfect, thanks.


回答1:


Assuming your formData table structure is fixed and known, you can just use a case expression to translate the formOption.fName to the matching column value:

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                                   

You can then have your procedure open a ref cursor for that query, using an argument value for the ID value.

If the formData structure isn't known, or isn't static, then you probably have bigger problems; but for this you'd need to fall back to dynamic SQL. As a starting point, you could do something like:

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;
/

This uses all the columns actually defined in the table to create the case expression at run time; because the case of your fieldName may not match the data dictionary, I'm forcing everything to lowercase for comparison. I'm also restricting to string columns to make the case simpler, but if you need columns which are other data types then each when ... then clause of the case expressions would need to check that column's data type (which you can add to the r cursor) and convert the actual column value to a string appropriately. All of the values have to end up the same data type, so it has to be strings, really.

Anyway, testing this from 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.

You could query fieldOptions to get the possible column names instead, but you still may have the data type conversion issue, wich would be harder to deal with; but if all the referenced formData fields are actually strings then that would be:

  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;



回答2:


If your logic is complex, you can consider generating your query rows programatically using Oracle table functions. Basically you generate a collection of records and "convert it to table" using the table() operator, as in select ... from table(your_table_function) ... , which can be made through a standard sys_refcursor. Taken from the linked example:

-- 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.

If the collection can be large, it can be useful to stream it using a pipelined table function, which works a bit like yield in c#. Again, following the examples in the linked page:

-- 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>


来源:https://stackoverflow.com/questions/43002054/collection-of-records-to-out-sys-refcursor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!