Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Jon.

Asked: April 29, 2004 - 12:39 pm UTC

Last updated: May 14, 2013 - 12:23 pm UTC

Version: 9.2.0

Viewed 1000+ times

You Asked

I would be interested in hearing your perspective on using Oracle Objects. Specifically, do they perform well enough to base a large scale, mission critical application on them? Are there any situations where you would urge people to shy away from them? Is it difficult to write reports against data within objects? Also, do you believe that they will continue to be supported in the long run? What improvements have been made in 9i and 10g? For example, I think I read under 8i, if the data structure changed, you had to drop and recreate and not just modify, but I think that restriction has been lifted in 9i, if I've read the documentation correctly. Lastly, are many companies using Oracle Objects in the database (if not, why do you think that is)? Anything that would help me form an intelligent opinion about Oracle Objects would be appreciated.

and Tom said...

Do you have access to my book "Expert One on One Oracle"?

I'm on record as being categorically against using them for persistent data (tables). We can do everything they do much more efficiently using good old relational tables. They are simply a generic set of "sugary syntax" on top of relational tables after all! They are a *generic* layer on top of the relational database. You can do much better yourself using parent/child tables and keys (it is what they do after all)

For programming -- in plsql -- they rule, they rock, they roll. I use them all of the time in my code. But, I've never created a TABLE with a nested table, varray, object type or the like.

If you have my book -- read the chapter on O-R features and you'll get the entire story from my perspective.

Rating

  (5 ratings)

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

Comments

Got it!

Jon, April 29, 2004 - 3:32 pm UTC

I do have your book and should have thought to look there first. Thanks, Tom

Object Views

Warren, April 30, 2004 - 12:43 pm UTC

I'm coming from the data side of things (DBA), and was working on a very large application a few years ago that was being driven by the application side of things who only wanted to model in UML and implement in objects.

We decided to go with Object-Relational views (basically just views with a fancy name!) on top of the properly designed relational tables, using Instead-Of triggers for the DML.

From the application side of things, this worked great. They defined the business object requirements and the views supported those objects. The application talked to these objects in a way the object-oriented programmers could understand.

From the data side of things, this worked great. We designed a relational model that supported batch processing, reporting that wasn't supported by the objects, tuning opportunities, etc. And it was something we could easily understand, modify, etc.

If a business object changed, we just changed the view to support the change in the app.

object relational view

Rajeshwaran, Jeyabal, November 01, 2010 - 5:45 am UTC

view using the WITH OBJECT IDENTIFIER clause

rajesh@10GR2>
rajesh@10GR2> create or replace view dept_or of dept_type
  2  with object identifier(deptno)
  3  as
  4  select deptno,dname,loc,
  5        cast( multiset( select e.* from emp e where e.deptno = d.deptno) as emp_tab_type)
  6  from dept d
  7  /

View created.

Elapsed: 00:00:00.10
rajesh@10GR2>
rajesh@10GR2> SELECT col#, name
  2  FROM sys.col$
  3  WHERE obj# =
  4    (SELECT object_id
  5    FROM user_objects
  6    WHERE object_name='DEPT_OR'
  7    )
  8  /

      COL# NAME
---------- ------------------------------
         0 SYS_NC_OID$
         0 SYS_NC_ROWINFO$
         1 DEPTNO
         2 DNAME
         3 LOC
         4 EMPS

6 rows selected.

Elapsed: 00:00:00.01


view using the WITH OUT OBJECT IDENTIFIER clause

rajesh@10GR2> create or replace view dept_or
  2  as
  3  select deptno,dname,loc,
  4        cast( multiset( select e.* from emp e where e.deptno = d.deptno) as emp_tab_type) as emps
  5  from dept d
  6  /

View created.

Elapsed: 00:00:00.06
rajesh@10GR2>
rajesh@10GR2> SELECT col#, name
  2  FROM sys.col$
  3  WHERE obj# =
  4    (SELECT object_id
  5    FROM user_objects
  6    WHERE object_name='DEPT_OR'
  7    )
  8  /

      COL# NAME
---------- ------------------------------
         1 DEPTNO
         2 DNAME
         3 LOC
         4 EMPS

Elapsed: 00:00:00.00
rajesh@10GR2>
rajesh@10GR2>


Questions:

1) Looks like View using WITH OUT OBJECT IDENTIFIER is better. since it doesn't introduce any additional columns in sys.col$ data dictionary. Is that correct?

2)Is there is any performance gain in specific to using WITH OBJECT IDENTIFIER clause?
Tom Kyte
November 01, 2010 - 3:30 pm UTC

1) Looks like View using WITH OUT OBJECT IDENTIFIER is better. since it doesn't introduce any additional columns in sys.col$ data dictionary. Is that correct?


explain why you 'believe' that to be a material thing to consider performance wise first - then we can talk. What possible bearing on raw performance do you think this could have???

2)Is there is any performance gain in specific to using WITH OBJECT IDENTIFIER clause?


that is not what this is about, it is about you already having a good key in place.

Usage of Object Views

Boris, May 14, 2013 - 5:03 am UTC

Hi Tom,

Could you please explain the practical use of object views with a simple example? Thanks in advance.
Tom Kyte
May 14, 2013 - 12:23 pm UTC

Here is an example from my first book - Expert one on one Oracle.


Object Relational Views

This is a fairly powerful feature for those of you who want to work with the object relational features, but still must present a relational view of the data to many applications. This allows you to use the standard VIEW mechanism to synthesize objects from relational tables. You don't have to create tables of a TYPE, with all of the mysterious columns and such – you can create a view of standard tables you have created (and probably already have). These views will behave just like an object table of that type would – without much of the overhead of hidden keys, surrogate keys, and other nuances.

In this section, we will use the EMP and DEPT tables to present a department-oriented view of the data. This is similar to the example of the nested table we used in Chapter 6 on Tables, where by we had the EMP_TAB_TYPE as a nested table of EMP_TYPE, and the DEPT table had a column of this nested table type. Here, we will model the EMP_TYPE and the EMP_TAB_TYPE once again, but we will also create a DEPT_TYPE object type as well and a view of that type.

It is interesting to note that this approach of using object views allows us to have the best of both worlds (relational and object relational). For example, we might have an application that needs a department-oriented view of the data. Their view starts at the department and employees in the department are naturally modeled as a collection inside the department. Another application however needs a different perspective. For example, when you walk up to a security guard and identify yourself as an employee, they will need to have an employee-oriented view of the data. Department in this case is inferred by the employee, not the other way around where the view was that department infers employees. This is the power of the relational model – many different views can efficiently be supported simultaneously. The object model does not support many different views of the same data as easily (if at all) or efficiently. By using many different object views of the relational data, we can satisfy everyone.
The Types
The types used in this example are borrowed from Chapter 6, with the addition of the DEPT_TYPE. They are:

 
scott@TKYTE816> create or replace type emp_type
  2  as object
  3  (empno       number(4),
  4   ename       varchar2(10),
  5   job         varchar2(9),
  6   mgr         number(4),
  7   hiredate    date,
  8   sal         number(7, 2),
  9   comm        number(7, 2)
 10  );
 11  /
Type created.

scott@TKYTE816>
scott@TKYTE816> create or replace type emp_tab_type
  2  as table of emp_type
  3  /
Type created.

scott@TKYTE816> create or replace type dept_type
  2  as object
  3  ( deptno number(2),
  4    dname  varchar2(14),
  5    loc       varchar2(13),
  6    emps      emp_tab_type
  7  )
  8  /
Type created.

Once again, a department is modeled as being an object with a department number, a name, a location, and employees.
The O-R View
From the above type definitions, it is very easy to synthesize our data for this view from the existing relational data. It would look like:

scott@TKYTE816> create or replace view dept_or
  2  of dept_type
  3  with object identifier(deptno)
  4  as
  5  select deptno, dname, loc,
  6         cast ( multiset (
  7                 select empno, ename, job, mgr, hiredate, sal, comm
  8                   from emp
  9                  where emp.deptno = dept.deptno )
 10                as emp_tab_type )
 11    from dept
 12  /
View created.

We are already familiar with the role of the CAST and the MULTISET – we are just turning a correlated subquery into a nested table collection here. For each row in DEPT, we'll query out all of the employees. We've told Oracle which column(s) identify a row uniquely in the view using the WITH OBJECT IDENTIFIER clause. This allows Oracle to synthesize an object reference for us, giving us the ability to treat this view as if it were an object table.

As soon as we have the view, we can start using it:

scott@TKYTE816> select dname, d.emps
  2    from dept_or d
  3  /

DNAME          EMPS(EMPNO, ENAME, JOB, MGR, HIREDATE, S
-------------- ----------------------------------------
ACCOUNTING     EMP_TAB_TYPE(EMP_TYPE(7782, 'CLARK',
               'MANAGER', 7839, '09-JUN-81', 2450,
               NULL), EMP_TYPE(7839, 'KING',
               'PRESIDENT', NULL, '17-NOV-81', 5000,
               NULL), EMP_TYPE(7934, 'MILLER', 'CLERK',
               7782, '23-JAN-82', 1300, NULL))

RESEARCH       EMP_TAB_TYPE(EMP_TYPE(7369, 'SMITH',
               'CLERK', 7902, '17-DEC-80', 800, NULL),
               EMP_TYPE(7566, 'JONES', 'MANAGER', 7839,
               '02-APR-81', 2975, NULL), EMP_TYPE(7788,
               'SCOTT', 'ANALYST', 7566, '09-DEC-82',
               3000, NULL), EMP_TYPE(7876, 'ADAMS',
               'CLERK', 7788, '12-JAN-83', 1100, NULL),
               EMP_TYPE(7902, 'FORD', 'ANALYST', 7566,
               '03-DEC-81', 3000, NULL))

SALES          EMP_TAB_TYPE(EMP_TYPE(7499, 'ALLEN',
               'SALESMAN', 7698, '20-FEB-81', 1600,
               300), EMP_TYPE(7521, 'WARD', 'SALESMAN',
               7698, '22-FEB-81', 1250, 500),
               EMP_TYPE(7654, 'MARTIN', 'SALESMAN',
               7698, '28-SEP-81', 1250, 1400),
               EMP_TYPE(7698, 'BLAKE', 'MANAGER', 7839,
               '01-MAY-81', 2850, NULL), EMP_TYPE(7844,
               'TURNER', 'SALESMAN', 7698, '08-SEP-81',
               1500, 0), EMP_TYPE(7900, 'JAMES',
               'CLERK', 7698, '03-DEC-81', 950, NULL))

OPERATIONS     EMP_TAB_TYPE()

4 rows selected.

scott@TKYTE816> select deptno, dname, loc, count(*)
  2    from dept_or d, table ( d.emps )
  3   group by deptno, dname, loc
  4  /

    DEPTNO DNAME          LOC             COUNT(*)
---------- -------------- ------------- ----------
        10 ACCOUNTING     NEW YORK               3
        20 RESEARCH       DALLAS                 5
        30 SALES          CHICAGO                6

3 rows selected.

So, we are on our way. We have the relational tables and the object relational view. Externally, it is difficult to tell which is the view, and which are the tables. The functionality of an object table is available to us – we have object references on this table, the nested table is set up, and so on. The advantage here is that we specify how to join EMP to DEPT using the existing natural parent/child relationship.

So, we have created an object relational view that exposes the data for querying. It does not however work when it comes to modifications yet:

scott@TKYTE816> update TABLE ( select p.emps
  2                   from dept_or p
  3                  where deptno = 20 )
  4     set ename = lower(ename)
  5  /
   set ename = lower(ename)
       *
ERROR at line 4:
ORA-25015: cannot perform DML on this nested table view column


scott@TKYTE816> declare
  2     l_emps  emp_tab_type;
  3  begin
  4      select p.emps into l_emps
  5        from dept_or  p
  6       where deptno = 10;
  7
  8      for i in 1 .. l_emps.count
  9      loop
 10          l_emps(i).ename := lower(l_emps(i).ename);
 11      end loop;
 12
 13      update dept_or
 14         set emps = l_emps
 15       where deptno = 10;
 16  end;
 17  /
declare
*
ERROR at line 1:
ORA-01733: virtual column not allowed here
ORA-06512: at line 13

We need to 'train' our view how to update itself. We have a somewhat complex mapping of relational data to object relational – it can be arbitrarily complex in fact. So, how can we 'train' our view to update itself? Oracle provides a mechanism called an INSTEAD OF trigger for this purpose. We can code the logic that should execute INSTEAD OF Oracles logic when we modify the contents of the view. For illustrative purposes, we will train the above view to allow it to update itself.

Oracle allows us to place instead of triggers on the view DEPT_OR as well as any nested table type included in the view. If we place a trigger on the nested table columns, it will allow us to process the first update from above – the update of the nested table column as if it where a table. The trigger for that would look like:

scott@TKYTE816> create or replace trigger EMPS_IO_UPDATE
  2  instead of UPDATE on nested table emps of dept_or
  3  begin
  4      if ( :new.empno = :old.empno )
  5      then
  6          update emp
  7             set ename = :new.ename, job = :new.job, mgr = :new.mgr,
  8              hiredate = :new.hiredate, sal = :new.sal, comm = :new.comm
  9           where empno = :old.empno;
 10      else
 11          raise_application_error(-20001,'Empno cannot be updated' );
 12      end if;
 13  end;
 14  /

Trigger created.

As you can see, this trigger will fire instead of update on the nested table column EMPS of the DEPT_OR view. It will be called for each and every row modified in the nested table and has access to the :OLD and :NEW values – just like a 'normal' trigger would. In this case, it is clear what we need to do. We need to update the existing EMP row by EMPNO, setting the columns to their new values. One thing I enforce in this trigger is that an update to the primary key is not allowed (hey, we might be using object relational features, but that doesn’t mean we should violate the basic tenets of relational database design!).

Now if we execute:

scott@TKYTE816> update TABLE ( select p.emps
  2                   from dept_or p
  3                  where deptno = 20 )
  4     set ename = lower(ename)
  5  /
5 rows updated.

scott@TKYTE816> select ename from emp where deptno = 20;

ENAME
----------
smith
jones
scott
adams
ford

scott@TKYTE816> select ename
  2    from TABLE( select p.emps
  3                  from dept_or p
  4                 where deptno = 20 );

ENAME
----------
smith
jones
scott
adams
ford

We see that the update of the nested table successfully translates into the relational table updates as expected. Coding the relevant INSERT and DELETE triggers are equally as easy – the update is the most complex case so we'll stop there.

At this point, it we execute:

scott@TKYTE816> declare
  2     l_emps  emp_tab_type;
  3  begin
  4      select p.emps into l_emps
  5        from dept_or  p
  6       where deptno = 10;
  7
  8      for i in 1 .. l_emps.count
  9      loop
 10          l_emps(i).ename := lower(l_emps(i).ename);
 11      end loop;
 12
 13      update dept_or
 14         set emps = l_emps
 15       where deptno = 10;
 16  end;
 17  /
declare
*
ERROR at line 1:
ORA-01732: data manipulation operation not legal on this view
ORA-06512: at line 13

We find that it still does not work. This might seem surprising. Shouldn't it fire our trigger from above? In fact, no it will not. Only updates to the nested table that use the collection un-nesting will fire that trigger – only when we treat the nested table as a table will that trigger fire. Here, we are simply updating a column in the DEPT_OR table – we are not doing a set operation on the nested table itself. In order to support this style of code (and to support updates to the other scalar attributes of the DEPT_OR view itself), we need an instead of trigger on the DEPT_OR view. The logic of this trigger will be to process the :OLD.EMPS and :NEW.EMPS as sets and:

1. Remove ALL records from EMP such that their EMPNO was in the :OLD set but is not seen in the :NEW set.  The SQL operator MINUS is excellent for this.
2. Update every EMP record such that their empno is in the set of empnos such that their :NEW record differs from their :OLD record.  Again MINUS is excellent for finding that set easily.
3. INSERT into EMP all :NEW records where the EMPNO of the :NEW record was not in the :OLD set of values.

This implementation is:

scott@TKYTE816> create or replace trigger DEPT_OR_IO_UPDATE
  2  instead of update on dept_or
  3  begin
  4      if ( :new.deptno = :old.deptno )
  5      then
  6          if updating('DNAME') or updating('LOC')
  7          then
  8             update dept
  9                set dname = :new.dname, loc = :new.loc
 10              where deptno = :new.deptno;
 11          end if;
 12
 13          if ( updating('EMPS') )
 14          then
 15             delete from emp
 16              where empno in
 17              ( select empno
 18                  from TABLE(cast(:old.emps as emp_tab_type))
 19                 MINUS
 20                  select empno
 21                    from TABLE(cast(:new.emps as emp_tab_type))
 22              );
 23             dbms_output.put_line( 'deleted ' || sql%rowcount );

This first MINUS gives us the set of empno's that were in the :OLD but are not seen in the :NEW set, we need to remove those records from the EMP table as they no longer exist in the collection.  Next we’ll modify the changed collection records:
 
 24
 25             update emp E
 26                set ( deptno, ename, job, mgr,
 27                      hiredate, sal, comm ) =
 28                    ( select :new.deptno, ename, job, mgr,
 29                             hiredate, sal, comm
 30                        from TABLE(cast(:new.emps as emp_tab_type)) T
 31                       where T.empno = E.empno
 32                    )
 33              where empno in
 34                ( select empno
 35                    from (select *
 36                            from TABLE(cast(:new.emps as emp_tab_type))
 37                            MINUS
 38                           select *
 39                             from TABLE(cast(:old.emps as emp_tab_type))
 40                          )
 41                );
 42             dbms_output.put_line( 'updated ' || sql%rowcount );

That minus returned everything in NEW minus anything in OLD – that’s the set of modified records.  We used that in a subquery to get the set of EMPNO’s we need to update in the EMP table and then used a correlated subquery to actually set those values.  Lastly, we’ll add all new records:

 43
 44             insert into emp
 45             ( deptno, empno, ename, job, mgr, hiredate, sal, comm )
 46             select :new.deptno,empno,ename,job,mgr,hiredate,sal,comm
 47               from ( select *
 48                        from TABLE(cast(:new.emps as emp_tab_type))
 49                       where empno in
 50                          ( select empno
 51                              from TABLE(cast(:new.emps as emp_tab_type))
 52                              MINUS
 53                            select empno
 54                              from TABLE(cast(:old.emps as emp_tab_type))
 55                          )
 56                    );
 57             dbms_output.put_line( 'inserted ' || sql%rowcount );
 58          else
 59             dbms_output.put_line( 'Skipped processing nested table' );
 60          end if;
 61      else
 62          raise_application_error(-20001,'deptno cannot be udpated' );
 63      end if;
 64  end;
 65  /
Trigger created.

That minus generated the set of EMPNO’s in the NEW collection that were not present in the OLD collection – this presents a list of rows to add to EMP.

This looks like a monster trigger, but it is actually straightforward. To recap, it begins by seeing if the scalar columns of DEPT_OR were modified. If so, it applies the changes to the DEPT table. Next, if the nested table column was updated (all of its values replaced), it reflects those changes to the EMP table. What we need to do to reflect those changes is:

1. Delete any records in EMP that were removed from the EMPS nested table column.
2. Update any records in EMP that had their values modified in the EMPS nested table column.
3. Insert any records into EMP that were added to the EMPS nested table column.

Fortunately, the SQL MINUS operator and the ability to TABLE the nested column variable make this easy for us. Now we can process:

scott@TKYTE816> declare
  2     l_emps  emp_tab_type;
  3  begin
  4      select p.emps into l_emps
  5        from dept_or  p
  6       where deptno = 10;
  7
  8      for i in 1 .. l_emps.count
  9      loop
 10          l_emps(i).ename := lower(l_emps(i).ename);
 11      end loop;
 12
 13      update dept_or
 14         set emps = l_emps
 15       where deptno = 10;
 16  end;
 17  /
deleted 0
updated 3
inserted 0

PL/SQL procedure successfully completed.

scott@TKYTE816> declare
  2     l_emps  emp_tab_type;
  3  begin
  4      select p.emps into l_emps
  5        from dept_or  p
  6       where deptno = 10;
  7
  8
  9      for i in 1 .. l_emps.count
 10      loop
 11          if ( l_emps(i).ename = 'miller' )
 12          then
 13              l_emps.delete(i);
 14          else
 15              l_emps(i).ename := initcap( l_emps(i).ename );
 16          end if;
 17      end loop;
 18
 19      l_emps.extend;
 20      l_emps(l_emps.count) :=
 21         emp_type(1234, 'Tom', 'Boss',
 22                   null, sysdate, 1000, 500 );
 23
 24      update dept_or
 25         set emps = l_emps
 26       where deptno = 10;
 27  end;
 28  /
deleted 1
updated 2
inserted 1

PL/SQL procedure successfully completed.

scott@TKYTE816> update dept_or set dname = initcap(dname);
Skipped processing nested table
Skipped processing nested table
Skipped processing nested table
Skipped processing nested table

4 rows updated.

scott@TKYTE816> commit;
Commit complete.

and our trigger translates our operations on the object instance into the equivalent modifications against the base relational tables.

This capability, to expose our relational data as object relational views, allows us to maximize the benefits of both the relational model and the object relational model.

The relational model shows its strength in its ability to answer almost any question you might have of the underlying data, easily and efficiently. Whether your view of the data is a departmental view (query a department and related employees) or employee-oriented (you specify an employee number and need to see departmental information), we can support you. You can use the relational tables directly or we can generate an object type model that exposes your view of the data, pulls all of the requisite data together, and gives it to you easily. Consider the results of these two queries:

scott@TKYTE816> select * from dept_or where deptno = 10;

    DEPTNO DNAME          LOC           EMPS(EMPNO, ENAME, JOB, MGR, HIREDATE, S
---------- -------------- ------------- ----------------------------------------
        10 Accounting     NEW YORK      EMP_TAB_TYPE(EMP_TYPE(7782, 'Clark',
                                        'MANAGER', 7839, '09-JUN-81', 2450,
                                        NULL), EMP_TYPE(7839, 'King',
                                        'PRESIDENT', NULL, '17-NOV-81', 5000,
                                        NULL), EMP_TYPE(1234, 'Tom', 'Boss',
                                        NULL, '25-MAR-01', 1000, 500)) 
scott@TKYTE816> select dept.*, empno, ename, job, mgr, hiredate, sal, comm
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  and dept.deptno = 10
  5  /

    DEPTNO DNAME       LOC      EMPNO ENAME JOB         MGR HIREDATE    SAL  COMM
---------- ----------- -------- ----- ----- --------- ----- --------- ----- -----
        10 Accounting  NEW YORK  7782 Clark MANAGER    7839 09-JUN-81  2450
        10 Accounting  NEW YORK  7839 King  PRESIDENT       17-NOV-81  5000
        10 Accounting  NEW YORK  1234 Tom   Boss            25-MAR-01  1000   500

They return similar data. The first one concisely gives you all of the information about a department in a single row. It could come back with many nested table types, which in SQL would take many queries. It can do a lot of work in the server assembling the answer for you, and shipping it back in a single row. If you are in an environment where network round trips are to be avoided when possible (long latency times) this could be extremely beneficial. Not to mention that a single select * from t can do the work of many SQL statements. Also, notice that repeating columns of data don't occur with the object view. The DEPTNO, DNAME, and LOC columns are not repeated for each employee; they are returned once, which may be more intuitive for many applications.

The second query requires the developer to have more knowledge of the data (that's not a bad thing mind you, just something to consider). They must know how to join the data together and, if there were many other tables to join to, they might very well need many separate queries they would have to assemble back together themselves to get the same answer. As a matter of example by what I mean by that, suppose in your model that a department has a fiscal year budget. It is stored relationally as:

scott@TKYTE816> create table dept_fy_budget
  2  ( deptno   number(2) references dept,
  3    fy       date,
  4    amount   number,
  5    constraint dept_fy_budget_pk primary key(deptno,fy)
  6  )
  7  /

Table created.

You have some data in there representing this year and the last couple of years FY budgets by department. Your application needs the department view that has all of the scalar data surrounding the department (name, location). It also needs the employee related information (the EMP_TAB_TYPE). It also however, needs the FY budget information as well. In order to get that relationally, the application programmer will have to code:

scott@TKYTE816> select dept.*, empno, ename, job, mgr, hiredate, sal, comm
  2  from emp, dept
  3  where emp.deptno = dept.deptno
  4  and dept.deptno = 10
  5  /

    DEPTNO DNAME       LOC      EMPNO ENAME JOB         MGR HIREDATE    SAL  COMM
---------- ----------- -------- ----- ----- --------- ----- --------- ----- -----
        10 Accounting  NEW YORK  7782 Clark MANAGER    7839 09-JUN-81  2450
        10 Accounting  NEW YORK  7839 King  PRESIDENT       17-NOV-81  5000
        10 Accounting  NEW YORK  1234 Tom   Boss            25-MAR-01  1000   500

3 rows selected.

scott@TKYTE816> select fy, amount
  2  from dept_fy_budget
  3  where deptno = 10
  4  /

FY            AMOUNT
--------- ----------
01-JAN-99        500
01-JAN-00        750
01-JAN-01       1000

3 rows selected.

It is not possible to write a single relational querythat retrieves that data in one call.  We can use some Oracle extensions (the CURSOR function in SQL) to return rows that return result sets themselves:

scott@TKYTE816> select
  2 dept.deptno, dept.dname,
  3 cursor(select empno from emp where deptno = dept.deptno),
  4 cursor(select fy, amount from dept_fy_budget where deptno = dept.deptno)
  5   from dept
  6  where deptno = 10
  7  /

DEPTNO DNAME          CURSOR(SELECTEMPNOFR CURSOR(SELECTFY,AMOU
------ -------------- -------------------- --------------------
    10 ACCOUNTING     CURSOR STATEMENT : 3 CURSOR STATEMENT : 4

CURSOR STATEMENT : 3

     EMPNO
----------
      7782
      7839
      7934

3 rows selected.

CURSOR STATEMENT : 4

FY            AMOUNT
--------- ----------
01-JAN-99        500
01-JAN-00        750
01-JAN-01       1000

3 rows selected.

1 row selected.

In this case – 1 row was selected and that row returned two more cursors to the client.  The client fetched from each cursor and displayed the results.  This works nicely, but requires a knowledge of the underlying data and how to put it together (how to write the correlated subqueries to generate the cursors).  We can instead model this data using the object relational extensions and recreate our view as follows:

scott@TKYTE816> create or replace type dept_budget_type
  2  as object
  3  ( fy        date,
  4    amount number
  5  )
  6  /
Type created.

scott@TKYTE816> create or replace type dept_budget_tab_type
  2  as table of dept_budget_type
  3  /
Type created.

scott@TKYTE816> create or replace type dept_type
  2  as object
  3  ( deptno number(2),
  4    dname  varchar2(14),
  5    loc       varchar2(13),
  6    emps      emp_tab_type,
  7    budget dept_budget_tab_type
  8  )
  9  /
Type created.

scott@TKYTE816> create or replace view dept_or
  2  of dept_type
  3  with object identifier(deptno)
  4  as
  5  select deptno, dname, loc,
  6         cast ( multiset (
  7                 select empno, ename, job, mgr, hiredate, sal, comm
  8                   from emp
  9                  where emp.deptno = dept.deptno )
 10                as emp_tab_type ) emps,
 11         cast ( multiset (
 12                 select fy, amount
 13                   from dept_fy_budget
 14                  where dept_fy_budget.deptno = dept.deptno )
 15                as dept_budget_tab_type ) budget
 16    from dept
 17  /

View created.

Now remember, the above is work we do once, the complexity is hidden from the application. The application will simply code:

scott@TKYTE816> select * from dept_or where deptno = 10
  2  /

    DEPTNO DNAME       LOC      EMPS(EMPNO, ENAME, J BUDGET(FY, AMOUNT)
---------- ----------- -------- -------------------- --------------------
        10 Accounting  NEW YORK EMP_TAB_TYPE(EMP_TYP DEPT_BUDGET_TAB_TYPE
                                E(7782, 'Clark',     (DEPT_BUDGET_TYPE('0
                                'MANAGER', 7839,     1-JAN-99', 500),
                                '09-JUN-81', 2450,   DEPT_BUDGET_TYPE('01
                                NULL),               -JAN-00', 750),
                                EMP_TYPE(7839,       DEPT_BUDGET_TYPE('01
                                'King', 'PRESIDENT', -JAN-01', 1000))
                                NULL, '17-NOV-81',
                                5000, NULL),
                                EMP_TYPE(1234,
                                'Tom', 'Boss', NULL,
                                '25-MAR-01', 1000,
                                500))


1 row selected.

Again, they get back one row, one object instance, that represents their view of the data. This can be quite handy indeed. The complexity of the underlying physical model is removed and it is easy to see how you might populate a GUI screen with this data.  Languages such as Java via JDBC, Visual Basic with OO4O (Oracle Objects for Ole), OCI (Oracle Call Interface), PL/SQL, and Pro*C can all make use of this functionality easily.  Using the relational model, it is a little more cumbersome as you get more and more complex one to many relationships. With the object relational model, it is a little more natural. Of course, we would have to modify our INSTEAD OF triggers to support modifications to the underlying relational data as well, so it is not totally completed but the gist is there.

Boris, May 15, 2013 - 12:28 am UTC

Thanks for your time and reference from your book. You are the best always.