Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, x.

Asked: September 13, 2001 - 8:20 pm UTC

Last updated: December 25, 2003 - 12:02 pm UTC

Version: 8/9i

Viewed 1000+ times

You Asked

Is there a way to query an object view which has a nested table.
The objective is to query the object view and apply filter on both the attributes in the object view as well as the attributes in the nested table .

The object types are say :
o_list_item
item_no number
quantity number
price number

o_Purchase_order
Purchase_order_no number
list_item nt_list_item




The resultset however should not be flattened( I tried the cursor method as well as using the TABLE function) .
eg :
if the data for purchase order 1001 looks like :
Purchase_order_no list_item
----------------- --------------------------------------
1001 nt_list_item(o_list_item
(11,200,43.00),o_list_item(22,200,43.00),
o_list_item(33,200,43.00))


on applying filter through the query the output should hane just one element in the nested table(say for id 22), and the output should look like :

Purchase_order_no list_item
----------------- --------------------------------------
1001 nt_list_item(o_list_item(22,200,43.00))

Please do respond if anyone have some pointers to this !

and Tom said...

Not without flattening it or writing a method that does this subsetting procedurally.

Consider it like this -- the nested table is just an attribute, a column. If it was the ENAME column and you were just interested in part of it -- you would use substr. We'll have to do something similar.

I guess we could "flatten" and "unflatten" like this:

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type Order_Type as object ( id int )
2 /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type OrderNT as table of Order_Type
2 /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create or replace type PO as object ( po_num int, some_order OrderNT )
2 /

Type created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> create table t of PO nested table some_order store as some_order_nt
2 /

Table created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> insert into t values ( PO( 1, OrderNT( Order_type( 1 ), Order_type( 22 ) ) ) );

1 row created.

ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM>
ops$tkyte@ORA817DEV.US.ORACLE.COM> select t.po_num, cast( multiset( select o.*
2 from table( t.some_order ) O
3 where id = 22 ) as orderNt )
4 from t t
5 /

PO_NUM
----------
CAST(MULTISET(SELECTO.*FROMTABLE(T.SOME_ORDER)OWHEREID=22)ASORDERNT)(ID)
-----------------------------------------------------------------------------------------------------------------------------------
1
ORDERNT(ORDER_TYPE(22))


as well....

Rating

  (6 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Querying object views

x, September 13, 2001 - 8:53 pm UTC

Thanks a lot for a speedy response....
I needed it badly...

Thanks again

Need another example

Tommy W., December 23, 2003 - 12:26 pm UTC

I have the following tables with a one to many relationship between
c_hdr_common_t and c_li_exc_t.

SQL> desc msdss.c_hdr_common_t
 Name                                      Null?    Type
 ----------------------------------------- -------- ------------
 C_HDR_PD_DT                               NOT NULL DATE
 B_SYS_ID                                  NOT NULL NUMBER(9)
 C_TCN_NUM                                 NOT NULL CHAR(17)
 C_TOT_REIMB_AMT                                    NUMBER(11,2)
..................


SQL> desc msdss.c_li_exc_t
 Name                                      Null?    Type
 ----------------------------------------- -------- -----------
 C_HDR_PD_DT                               NOT NULL DATE
 B_SYS_ID                                  NOT NULL NUMBER(9)
 C_TCN_NUM                                 NOT NULL CHAR(17)
 C_LI_NUM                                  NOT NULL NUMBER(4)
 R_CLM_EXC_CD                              NOT NULL CHAR(4)
..............


I want to be able to create an object view (or maybe something else) that will 
allow me to return 1 occurence of c_hdr_common_t that matches a list of R_CLM_EXC_CD's.

such as:
SELECT a.B_SYS_ID,a.C_HDR_PD_DT,a.C_TCN_NUM,a.
FROM msdss.clm_hdr_view a
WHERE c_hdr_pd_dt = '22-DEC-2003'
AND  R_CLM_EXC_CD IN ('0423','0230')

I have tried creating the object views below, but still can't get it to the point of returning 
one occurence. Simply adding DISTINCT to the above query is not an option. I want to be able to use
this view to group by and sum on other data in c_hdr_common_t.

CREATE TYPE msdss.clm_exc_t AS OBJECT
(
  R_CLM_EXC_CD CHAR(4)
);

CREATE TYPE msdss.clm_exc_list_t AS TABLE OF clm_exc_t;

CREATE TYPE msdss.clm_hdr_t AS OBJECT
(   C_HDR_PD_DT DATE,
    B_SYS_ID NUMBER(9),
    C_TCN_NUM CHAR(17),
    clm_exc_list   clm_exc_list_t
);

CREATE VIEW msdss.clm_hdr_view OF clm_hdr_t WITH OBJECT IDENTIFIER (C_HDR_PD_DT,B_SYS_ID,C_TCN_NUM) AS
    SELECT a.C_HDR_PD_DT,a.B_SYS_ID,a.C_TCN_NUM,
            CAST( MULTISET (
                           SELECT DISTINCT(b.R_CLM_EXC_CD) 
                           FROM msdss.c_li_exc_t b 
                           WHERE a.C_HDR_PD_DT = b.C_HDR_PD_DT
                           AND a.B_SYS_ID = b.B_SYS_ID
                           AND a.C_TCN_NUM = b.C_TCN_NUM) 
                        AS clm_exc_list_t)
                   AS clm_exc_list
   FROM   msdss.c_hdr_common_t a


thanks. 

Tom Kyte
December 23, 2003 - 4:56 pm UTC

is the goal to get the records from the HDR table that have a set of children records which completely cover the "in list"??

is that the goal (rather then show me the proposed -- but not working -- solution, phrase the question with a little more detail -- tell us exactly what the desired output is and what the inputs are).

If you say "given 0423, 0230 -- i would like all of the HRD records that have a child record for each of those codes", we can do that.

example

Tommy W, December 24, 2003 - 9:45 am UTC

"If you say "given 0423, 0230 -- i would like all of the HRD records that have a child record for each of those codes"

That's what I'm looking for, but I need all the logic hidden in a view, so the join of the two tables appears to be flattened, and the criteria can be expressed as R_CLM_EXC_CD IN ('0423','0230')

Also, if the HRD has more than one child that matches the criteria, I only want to see one HDR.
ie.. it is possible that a HDR can have child records with values like (0423,0230,0423)

thanks

Tom Kyte
December 24, 2003 - 10:24 am UTC

you are trying to apply SET semantics (in) to a single row -- ain't going to work.

this is not something you can "hide in a view", not going to happen.

example

Tommy W., December 24, 2003 - 10:58 am UTC

Given that I need to be able to use ad-hoc reporting tools to query this data, and a view will not work, and I would prefer not to create a new table/materialized view, what would be your recommendation.

thanks


Tom Kyte
December 24, 2003 - 11:15 am UTC

this is NOT going to happen in a view. I don't know what to tell you. I don't know the capabilities of your tool.

The query is going to be:


select hdr.columns...
from hdr, dtl
where hdr.key = dtl.key
and dtl.column in ( 'a','b','c')
group by hdr.columns....
having count(*) = 3

for example of


select ....
from hdr
where key in ( select key from dtl where column in ( 'a','b' ) group by key having count(*) = 2 ) )


see how the INLIST changes.
see how the count(*) = changes.

this is not going to be a view. I can two step it like this:


create view v
as
select *
from hdr
where key in ( select key
from dtl
where column in ( select * from gtt )
group by key
having count(*) = (select count(*) from gtt) ) )
/


then the user must fill gtt (stands for global temporary table) with the values of interest before running the query against the view.


Another way would be to use Oracle text (believe it or not). Would the ad-hoc tools be able to use the CONTAINS operator?



example

Tommy W, December 24, 2003 - 11:44 am UTC

The adhoc tool is Business Objects. I'm not the Business Objects designer, but I believe you can define an object using any available sql.


Tom Kyte
December 24, 2003 - 1:28 pm UTC

sure, you can define an object -- but -- BO isn't going to query it nor will it be what you would term "fast" since your query is on what would be the nested table.

this is not going to happen in a view in any way that is

a) usable
b) performant


BO is going to need to not use a view, it'll query hdr and dtl and it'll be the query above.

sorry -- but I don't see any efficient or effective alternative here.

we can use text to index this and use the contains clause -- but your back to "bo is database independent, it isn't going to recognize most features provided by the database"

Same thing but using a Pl/SQl Table

Suleiman Akbar, December 25, 2003 - 11:35 am UTC

Tom,

It's nice to see ur replies everytime u answer to all IT nerds...pls. keep up the good work.

Request u to pls. have a look for my problem...

pls. find the problem enclosed beneath.

Question)

Declare
TYPE col1 Is Table of <tab.column_name> Index By Binary_Integer;
TYPE col2 Is Table of <tab.column_name> Index By Binary_Integer;

pCol1 col1;
pCol2 col2;
pCol3 col2;

Begin
Select <pk_col_name> Bulk Collect Into pCol1 From <tab1> Where <unq_key_col> = <value>;
Select <pk_col_name> Bulk Collect Into pCol2 From <tab2> Where <col_name> = <value>;

-- Would like to make u aware that pCol1 has 100 rows.
-- and pCol2 has only 4 rows.
-- I want to load remaining 96 rows into pCol3...
j := 1;
For i in 1..pCol1.Count Loop
If pCol1(i) <> pCol2(i) Then
pCol3(j) := pCol1;
j := j+1;
end If;
End Loop;
End;

-- But it's giving me some error...don't know what's wrong in this code... I should execute it but when I debug this with Pl/SQL developer, I have noticed that after comparing the 4 values of pCol2 it's returning it as NULL and it's going to a no_data_found exception again and again..

Tom request u to solve my query at the earliest...

Hope to see ur reply ASAP..

Suleiman khatib atavur



Tom Kyte
December 25, 2003 - 12:02 pm UTC

your logic is a bit "botched" there.  you are using I as the index to both pcol1 and pcol2 -- but pcol1 has 100 entries whereas pcol2 has 4!

anyway, here are two approaches


ops$tkyte@ORA9IR2> create or replace procedure method_1
  2  as
  3      type array is table of number index by binary_integer;
  4
  5      l_col1 array;
  6      l_col2 array;
  7      l_col3 array;
  8
  9      function is_in( p_array in array, p_value in number ) return boolean
 10      is
 11      begin
 12          for i in 1 .. p_array.count
 13          loop
 14              if ( p_array(i) = p_value )
 15              then
 16                  return TRUE;
 17              end if;
 18          end loop;
 19          return FALSE;
 20      end is_in;
 21  begin
 22      select object_id bulk collect into l_col1 from all_objects where rownum <= 100;
 23      select object_id bulk collect into l_col2 from all_objects where rownum <= 4;
 24
 25      for i in 1 .. l_col1.count
 26      loop
 27          if ( NOT is_in( l_col2, l_col1(i) ) )
 28          then
 29              l_col3(l_col3.count+1) := l_col1(i);
 30          end if;
 31      end loop;
 32
 33      dbms_output.put_line( 'added ' || l_col3.count || ' array entries' );
 34  end;
 35  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace type myArrayType as table of number
  2  /
 
Type created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace procedure method_2
  2  as
  3      l_col1 myarrayType;
  4      l_col2 myarrayType;
  5      l_col3 myarrayType;
  6  begin
  7      select object_id bulk collect into l_col1 from all_objects where rownum <= 100;
  8      select object_id bulk collect into l_col2 from all_objects where rownum <= 4;
  9
 10      select * bulk collect into l_col3
 11        from ( select * from TABLE( cast( l_col1 as myArrayType ) )
 12               MINUS
 13               select * from TABLE( cast( l_col2 as myArrayType ) )
 14             );
 15
 16      dbms_output.put_line( 'added ' || l_col3.count || ' array entries' );
 17  end;
 18  /
 
Procedure created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> exec method_1
added 96 array entries
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2> exec method_2
added 96 array entries
 
PL/SQL procedure successfully completed.
 
 

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library