Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Mike.

Asked: October 31, 2000 - 3:52 pm UTC

Last updated: February 16, 2013 - 9:59 am UTC

Version: 8.1.5

Viewed 10K+ times! This question is

You Asked

Tom,

I'm trying to get a handle on inline views. When I was first learning SQL (many years ago), inline views were not available.

Would inline views be an appropriate technology for, say, listing the owners,their tables and rowcounts of their tables in DBA_TABLES? I can't assume that the statistics for a given table are very current, so I can't just pull that value from DBA_TABLES. I know I can spool a SQL file with the appropriate criteria and then execute it, but it would be nice not to have to create temporary files.

Thanks!
Mike Morgan

and Tom said...

No, an inline view would not be useful for doing that. In order to query DBA_TABLES and get a count of rows in a table for each row in DBA_TABLES -- the inline view would have to query a different table each time. Thats not what they are for. There is a way to do this in 8.1.5 and up easily and I'll demonstrate that but first I'll show you what inline views are great at.

Lets say you have multiple tables in a query and they show information at different levels of detail. Table DBA_FREE_SPACE and DBA_DATA_FILES for example. DBA_FREE_SPACE tracks free space in a tablespace by extents. DBA_DATA_FILES tracks available space in a tablespace by file. Let's say you wanted to write a query to print out the tablespace name, amount of space allocated to it, and the amount of free space. To get the amount of space allocated to it, we need the SUM(bytes) from dba_data_files group by TABLEPSACE. To get the amount of space FREE in it, we need sum(bytes) from dba_free_space group by tablespace. So, if a tablespace had 2 one megabyte files allocated to it and it had 4 free extents of 256k and we joined by tablespace name(resulting in 8 rows) and then did the group by -- we would end up with the erroneous result of 8 megabytes of allocated space (not 2) and 4 meg of free space (not 2). Since the information is at different levels of detail -- we need to GROUP BY first and then join.

A query to do this could be:


select nvl(b.tablespace_name,
nvl(a.tablespace_name,'UNKOWN')) name,
kbytes_alloc kbytes,
kbytes_alloc-nvl(kbytes_free,0) used,
nvl(kbytes_free,0) free,
((kbytes_alloc-nvl(kbytes_free,0))/
kbytes_alloc)*100 pct_used,
nvl(largest,0) largest,
nvl(kbytes_max,kbytes_alloc) Max_Size,
decode( kbytes_max,
0, 0,
(kbytes_alloc/kbytes_max)*100) pct_max_used
from ( select sum(bytes)/1024 Kbytes_free,
max(bytes)/1024 largest,
tablespace_name
from sys.dba_free_space
group by tablespace_name ) a,
( select sum(bytes)/1024 Kbytes_alloc,
sum(maxbytes)/1024 Kbytes_max,
tablespace_name
from sys.dba_data_files
group by tablespace_name )b

where a.tablespace_name (+) = b.tablespace_name
order by 1
/


That gets the free space from dba_free_space summed up by tablespace and the allocated space from dba_data_files summed up by tablespace -- bringing the two results to the same level of detail and THEN joins.

In a nutshell -- that is what inline views are great for. Prior to the introduction of inline views in Oracle7.1 -- we needed to create "real" views in support of a query like this.

Now, as for how to run a query like you want -- to query DBA_TABLES and get a row count for each table -- we can use dynamic sql for that. We need to since the count will be the count of rows in a table we do not know the name of until runtime! We can code this in Oracle8i release 1 and up:



ops$tkyte@ORA8I.WORLD> create or replace
2 function get_rows( p_tname in varchar2 ) return number
3 as
4 l_columnValue number default NULL;
5 begin
6 execute immediate
7 'select count(*)
8 from ' || p_tname INTO l_columnValue;
9
10 return l_columnValue;
11 end;
12 /

Function created.

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select user, table_name,
2 get_rows( user||'.'||table_name) cnt
3 from user_tables
4 /

USER TABLE_NAME CNT
--------- --------------- ----------
OPS$TKYTE A 0
OPS$TKYTE B 0
OPS$TKYTE C1 0
OPS$TKYTE C2 0
OPS$TKYTE C3 0
OPS$TKYTE C4 0
OPS$TKYTE C5 0
OPS$TKYTE CHAINED_ROWS 0
OPS$TKYTE EMP 2
OPS$TKYTE IMAGE_LOAD 1
OPS$TKYTE LEAVE_DTL 6
OPS$TKYTE LST_INFO 9
OPS$TKYTE MERGE 3
OPS$TKYTE MV_1 3
OPS$TKYTE P 0
OPS$TKYTE RECIPE 0
OPS$TKYTE RECIPEVERSION 0
OPS$TKYTE T 1
OPS$TKYTE T1 2
OPS$TKYTE T2 1
OPS$TKYTE TEMP 0
OPS$TKYTE TEMP1 4
OPS$TKYTE TEMP2 10
OPS$TKYTE TEST_BLOB 1
OPS$TKYTE TEST_DATE_TBL 2
OPS$TKYTE TMP_ROW_COUNT 27
OPS$TKYTE TREC_RPT_VARS 5

27 rows selected.


Be prepared to have that take a while to execute of course!


follow up to comment one below:

My use of USER_TABLES was intentional -- DBA_TABLES has quite a few tables in it! I just wanted to demonstrate the concept. Against DBA_TABLES this of course would be:

select owner, table_name, get_rows( owner || '.' || table_name )
from dba_tables;




Rating

  (68 ratings)

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

Comments

Inline views, aka table rows count from dba_tables

DC Law, May 20, 2001 - 10:22 pm UTC

Tom,

Your response was, as usual, spot on.
With one little hitch: from user_tables vs dba_tables.

My brain being a sieve, yes

Harrison Picot, March 23, 2002 - 12:24 pm UTC

Hi Tom:

Like they say about chopping wood (warms you twice) this example (located in the archives) saved me the embarrasement of having to ask you to repeat an answer you provided a year or so ago (and I both lost and forgot) about inline views, so the archives saved my asking, and your thinking, "Is this guy's brain a sieve?". Well, yes, but I'm saving room for Heather Locklear - also a worthwhile endeavor. Despite my lack of an orderly archive, yours provided another great answer.

Cheers,

Harrison

Very helpful

Hai, March 17, 2003 - 5:49 pm UTC

Tom,
Thanks for keeping this in the archives. It saved me a lot of time try to develop the same function. I was right to go here to look for hints and advices.
As usual, your web site is very helpful.


inline or seperate !!

A reader, March 28, 2003 - 4:46 pm UTC

hi tom,

i have a table

emp
-------
emp_no number,
emp_name varchar2(20),
emp_inst_date date,
emp_is_current char(1)


dept
-------------
dept_no number,
dept_name varchar2(20),
dept_inst_date date,
dept_is_courrent char(1)

we have our own audit trails.

so for the normal application we have views

emp_v
select * from emp where emp_is_current is not null;

dept_v
select * from dept where dept_is_current is not null;


so when I join the result set
should I go by

select e.*,d.* from emp_v e, dept_v d
where e.emp_dept_code = d.dept_code;

or

should I go like

select e.* , d.*
from emp,
(select * from dept where dept_is_current is not null) d
where e.emp_dept_code = d.dept_code
and e.emp_is_current is not null;

why ?



Tom Kyte
March 28, 2003 - 5:07 pm UTC

they are all the same -- will result in the same plans.

use whichever is easier to type.

or like

A reader, March 28, 2003 - 4:48 pm UTC

select e.* , d.*
from emp e,
(select * from dept where dept_is_current is not null) d
where e.emp_dept_code = d.dept_code (+)
and e.emp_is_current is not null;



which is faster ?

A reader, March 28, 2003 - 5:35 pm UTC

tom which of them is faster ?


Tom Kyte
March 29, 2003 - 9:08 am UTC

i guess the one that includes the least number of characters since you can type it in faster -- but only if you include the time to type in the query -- since as I said "same plan". "same plan" means "they are the same"

what about that outer join ?

A reader, March 28, 2003 - 5:40 pm UTC

sorry for posting that query later.

but this outer join query will get more rows in some cases
does this make sance or is it impractical ?

Tom Kyte
March 29, 2003 - 9:15 am UTC

i didn't comment on the outer join -- that one is different, I was only talking about the ones that were together.

the outer join is just a totally different result set. cannot be compared for performance -- they are apples and oranges.

outer join

A reader, March 31, 2003 - 10:08 am UTC

I have to create a view with this query to provide some data in the listbox or some thing. I do need a join there.

I am not 100% sure that
1.) should I go with the outer join query,
( which will return all the valid rows
and dept where ever found.)

or

2.) simple join (restrict emp rows where dept not found.)
and create a view


NOte I have fk and not null constraints ( in trigger insert/update) to enforce this fk/not null relationship.


Tom Kyte
March 31, 2003 - 10:53 am UTC

well, you know what -- I cannot answer that.

Only you -- with your knowledge of which result set is correct -- can.


Do you need to show all depts? if so, outer join away.

Should you only show depts with emps? If so, outer join would be wrong


so, back to you -- which is the right answer.

thanks,

A reader, March 31, 2003 - 11:41 am UTC

I have to show all emp and dept names are secondary info..
empcd and empname is the objective or the query
so I will go by outer joins

( I have trigger enforced constarint on emp table that
when ever we insert in emp dept should not be null and
deptcd should be from department table.
)

so always outer join and simple join, both,will return the same result.
I just still want to be sure that I get the full record set for emp..

Tanks again..



Tom Kyte
March 31, 2003 - 12:20 pm UTC

then you do not need an outer join -- I pray you are not using a trigger to do something as simple as:

..
deptcd REFERENCES dept(deptno) not null,
....

if you wrote code for that - erase it and just use the constraint. faster, safer, correct.

A reader, March 31, 2003 - 12:54 pm UTC

I had to write the code because
the pk in dept is PK(deptcd,dept_entry_ts)



Tom Kyte
March 31, 2003 - 1:01 pm UTC

then you have a problem with your data model.

It is pretty nigh impossible to correctly implement foreign keys with triggers due to the way concurrency controls work.

You should fix the model - you are missing an object in your schema. I'm assuming that dept_entry_ts is a timestamp, and you have "versions" of departments. You are missing the top level schema object that you have "versions of". it would have the primary key on deptcd and that is what the child should point to.

A reader, March 31, 2003 - 12:55 pm UTC

what else I can do I can not reference a part of a primary key
can I ?

what are you saying ?

A reader, March 31, 2003 - 1:09 pm UTC

{
You are missing the top level schema object that you have
"versions of". it would have the primary key on deptcd and that is what the..
}

oracle 817

table
emp

empcd number
emp_entry_date date
empname varchar
emp_entry_empid number
emp_deptcd number


dept
deptcd number
dept_entry_dt date
deptname varchar
dept_entry_empid number..

now what is that I am missing.

in this case If I want to
refer : deptcd
from : emp table for (emp_deptcd)

and
refer : empcd
from : dept table for ( dept_entry_empcd)


we have not introduce the surrogate keys !!

what is (better ? )way ?



Tom Kyte
March 31, 2003 - 1:22 pm UTC

what are the primary keys of anything there and WHY -- what is the meaning of dept_entry_dt?

A reader, March 31, 2003 - 1:26 pm UTC


all table contains this two audit trail fields

<tablename>_entry_dt (is timestemp)
<tablename>_entry_empcd (user who enterd/updated it)

primary key for emp table consists of
(empcd,emp_entry_dt)

- also there is recursive relationship
emp_entry_empcd ( references it self)
(for the user who entered the record).

and

primary key for dept table consists of
(deptcd,dept_entry_dt)

-- also dept_entry_empcd referes to emp.empcd


Tom Kyte
March 31, 2003 - 2:35 pm UTC

like I said -- you are missing the "master" or parent table to this VERSIONED table.

you are missing a table in your model, the parent table of this versioned one.. IT would have a primary key on deptcd and other attributes that do not change regardless of version. It may even be a one column table.

A reader, March 31, 2003 - 2:43 pm UTC


we have only one table and audit trail is enabled on it.
based on max(entry_date) we select the "eligible record".
( if there is something i am missing, can you please
illustrate with an exaple. )

I think what you are refering as the master table is
another table which contains the all audit trail information ? and current table should contain only valid records ?

please let me know better design

Tom Kyte
March 31, 2003 - 2:57 pm UTC

No, I'm saying if you have a fkey and no unique key -- you've made a mistake

create table dept_master( deptno primary key );

create table dept_version( deptno references dept_master, date_column, primary key(deptno,date_column) );

create table emp( ...., deptno references dept_master NOT NULL );


I don't know how to say "you are missing a table" any other way -- sorry.




ok

A reader, March 31, 2003 - 4:37 pm UTC

so you are saying I have to create this two tables for dept if I have audit trail enable on it.
and constraints are not as as efficiant ( or wrong use of
constraint) in this case.

I have like 40/50 tables in this way because of the audit trail, where primary key consists of two columns
(code and timestemp) and in our operational schema we are enforcing constraints via insert/update row level triggers.

you are suggesting that I should create 40/50 more master tables and take those triggers/packages(to avoid mutating situations) based on the information I provided you.

Thanks tom, I will try to take it to upper mgmt. cause I need approval and time to do it, test it and move it to prod. and ofcourse I have to create a demo to make my case there :)

Thanks,


Tom Kyte
March 31, 2003 - 5:06 pm UTC

telling you -- you cannot reliably enforce contraints via triggers.

Yes, I'm suggesting that instead of gobs of procedural code (which doesn't work 100%), that runs for each and every insert/update delete -- you simply do it the "relational way".

Or, if you are on 9i, consider this
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:4529781014729 <code>

automated auditing that lets you have primary and foreign keys with no code.

9i

A reader, March 31, 2003 - 6:03 pm UTC


good evening !! Thanks for being there !! it's 6:06 pm already..

tom, 9iR2 is far for me. I mean my company is on 817 (stable ) deployment -- nobody wants to change it
( and 817 is very cool too).

there are 47 tables, at this point working fine
( passes all qa tests and all set.)
when things are running fine nobody want to change it/touch it and update big deal .. even if I push for 9i it will be only me in the IT dept !!

also what about surrogate key ? what if I intoduce them in this case ? I think sooner or later I do have to bring them in the picture because of the complexity.

what me be the implecation of that ?

so I go like ...

emp

emp_skey number
empcd number
emp_entry_dt date default sysdate,
emp_name varchar2
emp_dept_skey number references dept(dept_skey) not null,
emp_entry_emp_skey number references emp(emp_skey) not null

dept

dept_skey number
dept_cd number,
dept_entry_dt date,
dept_entry_emp_skey number reference emp(emp_skey) not null,
dept_name

what implecations you see in that case ?






Tom Kyte
March 31, 2003 - 6:56 pm UTC

I don't see how a surrogate key would help you -- you have multiple versions of a record. you have a composite key. using a surrogate would not change anything. it would not be any different than EMP have a composite fkey -- meaning you have to update EMP after each insert into DEPT -- a total change and a total mess.



thanks tom,

A reader, March 31, 2003 - 7:12 pm UTC

I will try to create new master tables !!

Thanks !!

demo time !!

A reader, April 02, 2003 - 5:13 pm UTC

hi tom,


now in the same schema, I have two tables
say country and currency.
I want to create a table countrytocurrency
where audit trial should be enabled.

what is your design suggestion would be.

1.) the objective and the contax of the application
will not influnce the design as it is solely going to be
used for holding the mapping between country and
currency
eg. ( england can have pound and Euro)
same for any europian country
us only USD
australia asd
etc..

2.) I have country table
country(country_code,country_name,timestemp)
with pk(country_code)
3.) I have currency table
currency(curr_code,curr_name,timestemp)
with pk(curr_code)
4.) my new contrytocurrency table is
depandent table (primary key cosists of fk s)
contrytocurrency(country_code,curr_code,timestamp,entry_usr,flag)
pk(country_code (fk:country)),curr_code(fk:currency))


q)
what would be the "master" and child table in this case ?


Tom Kyte
April 02, 2003 - 9:09 pm UTC

wow, did we get off track on this thread or what...

question is too vague to be answerable. "audit trail enabled". "countryToCurrency" but no way to join country to currency...

no understanding of the columns involved.

tell you what, when I'm accepting questions -- fill in the blanks and make it a question.

in short, a countrytocurrency association object would have just the primary key of country and the primary key of currency -- period. auditing -- audit on countrytocurrency by select, insert,update, delete whatever you need.

Query using inline view

surjam, April 27, 2004 - 12:04 pm UTC

Thanks for the great explanations.
Tom,

I am trying to do this query using inline view but looks like not getting the output as desired.

I have a table MY_TBL

Inst_type varchar2(20)
curr varchar2(3)

My data is:

Inst_type curr

abc aud
abc aud
bcd jpy
efg jpy
abc jpy
bcd aud
bcd jpy
efg aud
zzz aud
yyy jpy

I need the query to display like:

Inst_type AUD JPY

abc 2 1
bcd 2 1
efg 1 1
zzz 1 0
yy 0 1

Using Union I am seeing:

SELECT AUD.INST_TYPE,AUD.CNT,0 JPY
FROM
(SELECT INST_TYPE,COUNT(*) FROM MY_TBL
WHERE CURR='AUD' GROUP BY INST_TYPE) AUD
UNION
SELECT JPY.INST_TYPE,0,JPY.CNT
FROM
(SELECT INST_TYPE,COUNT(*) FROM MY_TBL
WHERE CURR='JPY' GROUP BY INST_TYPE) JPY;


Inst_type AUD JPY

abc 2 0
abc 0 1
bcd 2 0
bcd 0 1
efg 1 0
efg 0 1
yyy 0 1
zzz 1 0

Need you expert view. Thanks for your time.

Tom Kyte
April 28, 2004 - 1:14 pm UTC

select inst_type, count( decode( curr, 'AUD', curr ) ) aud,
count( decode( curr, 'JPY', curr ) ) jpy
from t
group by inst_type;

A typo in my last query

surjam, April 27, 2004 - 12:05 pm UTC

I did the group by inst_type in the query.
Thanks.

surjam, April 28, 2004 - 1:59 pm UTC

Thanks a lot. Just wondering why I could not see this. Trying all the time complex looking queries.



How to handle null return in inline view?

Tony, July 21, 2004 - 4:56 am UTC

Tom,
I am facing a problem with inline view as below:
(This is just a simulated example from actual application)

SELECT e.empno,e.salary, NVL(d.deptno,'UNK'), NVL(d.description,'UNK')
FROM employee e, (select dept.deptno, dept.description FROM dept WHERE EXISTS (SELECT 'X' FROM depdetail a
where a.deptno = dept.deptno) d
WHERE e.dept = d.dept(+)
I want to return employees even if they does not exist in deptdetail table through deptno.

Please note that I want to use inline view, and not change query, as this example is made from big application using lots of inline views.
Your help will be highly appreciated.
Regards

Tom Kyte
July 21, 2004 - 8:13 am UTC

don't keep us guessing -- what problem are you facing exactly?

Can I do this?

A reader, August 19, 2004 - 1:55 am UTC

Hi Tom,
I have an inline view in which the select clause is not returning rows based upon some criteria. I want to use another SQL if inline view sql is not retring anything. Is it possible?
For example:
SELECT a.estmate
FROM a, (select.....
FROM b
WHERE ...
) c
WHERE a....=c....
If (select.....
FROM b
WHERE ...
) c
does not return a value, I want to use different in line view.
Please advise.
Regards

Tom Kyte
August 19, 2004 - 9:33 am UTC

if your second query is relatively "inexpensive", then you could use (the unioned all queries are your two queries repectively):

ops$tkyte@ORA9IR2> @test
ops$tkyte@ORA9IR2> select *
  2    from (
  3  select count(case when what='T1' then what end) over () cnt, x.*
  4    from ( select 'T1' what, d.* from dual d where 1=0
  5           union all
  6           select 'T2' what, d.* from dual d where 1=1 ) x
  7             )
  8   where (cnt > 0 and what = 'T1') or (cnt=0)
  9  /
 
       CNT WHAT                           D
---------- ------------------------------ -
         0 T2                             X
 
ops$tkyte@ORA9IR2> select *
  2    from (
  3  select count(case when what='T1' then what end) over () cnt, x.*
  4    from ( select 'T1' what, d.* from dual d where 1=1
  5           union all
  6           select 'T2' what, d.* from dual d where 1=1 ) x
  7             )
  8   where (cnt > 0 and what = 'T1') or (cnt=0)
  9  /
 
       CNT WHAT                           D
---------- ------------------------------ -
         1 T1                             X


If query two is expensive, you would want to use two queries probably or a pipelined function that runs query one and pipes any rows it finds and upon not finding any rows, runs query two. 

What is over () cnt?

A reader, August 19, 2004 - 4:57 pm UTC

Hi Tom,
Thanks for your help.
Could you please help me to understand, what over () cnt is doing?
Can you please also give an example to do through pipelined function?
Thanks

Tom Kyte
August 19, 2004 - 8:06 pm UTC

scott@ORA9IR2> select empno, job, count(*) over (),
2 count( decode( job, 'CLERK', job ) ) over ()
3 from emp;

EMPNO JOB COUNT(*)OVER() COUNT(DECODE(JOB,'CLERK',JOB))OVER()
---------- --------- -------------- ------------------------------------
7369 CLERK 14 4
7499 SALESMAN 14 4
7521 SALESMAN 14 4
7566 MANAGER 14 4
7654 SALESMAN 14 4
7698 MANAGER 14 4
7782 MANAGER 14 4
7788 ANALYST 14 4
7839 PRESIDENT 14 4
7844 SALESMAN 14 4
7876 CLERK 14 4
7900 CLERK 14 4
7902 ANALYST 14 4
7934 CLERK 14 4

14 rows selected.


they are analytic functions -- if you want to read more about them

o chapter in Expert one on one Oracle
o search this site for:

analytics rock and roll


in the above, count(*) over () gives a count(*) over the entire result set -- 14 rows. count(decode....) over () gives a count of the non-null occurrences of the decode so it counts how many clerks are in the result set.


scott@ORA9IR2> create or replace type myType as table of varchar(2)
2 /

Type created.

scott@ORA9IR2>
scott@ORA9IR2> create or replace function f( l_input in number ) return myType
2 pipelined
3 as
4 l_found boolean := false;
5 begin
6 for x in ( select * from dual where 1 = l_input )
7 loop
8 pipe row( 'T1' );
9 l_found := TRUE;
10 end loop;
11 if ( not l_found )
12 then
13 for x in ( select * from dual where 1 <> l_input )
14 loop
15 pipe row( 'T2' );
16 end loop;
17 end if;
18 return;
19 end;
20 /

Function created.

scott@ORA9IR2>
scott@ORA9IR2> select * from table( F(1) );

CO
--
T1

scott@ORA9IR2> select * from table( F(0) );

CO
--
T2



Thanks a lot!

A reader, August 20, 2004 - 9:11 pm UTC

Hi Tom,
Thanks for your excellent help. I am reading your both books. They are really useful, especially when I can not explore answer myself, you provide the solution to the specific problem in a very nice way.

One more clarification please...
In the second inline view, in one of the join condition, I have to say
WHERE colvalue = 0 or colvalue = 1 or.....
upto 999. Can I put in some array? and then use it instead of typing so many times. The sequence is known and can have any value from 0 to 999.
Once again thanks for your invaluable help.
Regards

Tom Kyte
August 21, 2004 - 11:37 am UTC

where colvalue in ( :x1, :x2, :x3, ... )

you can use an in list?

More clarification please ...

A reader, August 22, 2004 - 6:07 am UTC

Hi Tom,
Do you mean I have to write
:X1.....:X999?
Or Am I missing something?
Thanks

Tom Kyte
August 22, 2004 - 8:23 am UTC

either that or put them into a collection:

ops$tkyte@ORA9IR2> create or replace type myArrayType as table of number;
  2  /
 
Type created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> declare
  2          l_data myArrayType := myArrayType( 1,2,3,4,5 );
  3  begin
  4          open :x for
  5          select * from all_users where user_id
  6          in (select * from TABLE(cast(l_data as myArrayType)));
  7  end;
  8  /
 
PL/SQL procedure successfully completed.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> print x
 
USERNAME                          USER_ID CREATED
------------------------------ ---------- ---------
SYSTEM                                  5 12-MAY-02
 

OK

Siva, August 22, 2004 - 1:11 pm UTC

Hi Tom,
I would like to have a query which provides a result set as
follows.Can Inline views be used here?

SQL> select distinct job from emp e1,(select empno,ename from
emp e2...

Query output should be


            DEPTNO
            ------
 
            10                 20                     30   --- Deptno values
            
job                
---
ANALYST    ENAME   EMPNO     ENAME  EMPNO        ENAME EMPNO
            ....     ...     ....   ....         ....  .....
            .....    ...     ....   ....         ..... .....


SALESMAN   ENAME   EMPNO     ENAME  EMPNO        ENAME EMPNO
            ....     ...     ....   ....         ....  .....
            .....    ...     ....   ....         ..... .....




CLERK      ENAME   EMPNO     ENAME  EMPNO        ENAME EMPNO
            ....     ...     ....   ....         ....  .....
            .....    ...     ....   ....         ..... .....




MANAGER  ENAME   EMPNO     ENAME  EMPNO        ENAME EMPNO
            ....     ...     ....   ....         ....  .....
            .....    ...     ....   ....         ..... .....


PRESIDENT ENAME   EMPNO     ENAME  EMPNO        ENAME EMPNO
            ....     ...     ....   ....         ....  .....
            .....    ...     ....   ....         ..... .....




Is it possible to achieve this result set?
Please do reply.
Bye!




 

Tom Kyte
August 22, 2004 - 4:59 pm UTC

i'm not really sure what I'm looking at here to tell you the truth?

but if you wanted a report that looked like:


JOB DEPT10 DEPT20 DEPT30

analyst frank 1234 mary 5233 george 45325
sue 4323 bob 323
alice 34232
.....


that is, each deptNN is sort of like a jagged array -- it is theoretically possible (yes, i could write such a query) but practically, the expense would be horrific.

You would have to query up dept10, dept20, dept30 each -- assign rownums to the emps by job (using row_number()) and full outer join each to eachother.


This'll be something you want to do in a reporting tool.



Thanks

Siva, August 23, 2004 - 5:29 am UTC

Hello Tom,
Thanks for your response.Yes.It is a matrix report.People asked me how would I achieve a matrix
report in plain SQL?.Hence I asked for your help.From your hints for the query,I have understood
only the part of the query(Query is below) and the remaining part "and full outer join each to each other.",I don't understand that part.How to query deptno -- 10,20,30 and perform a full join on them so that the deptnoNN appear as row headers?

Could you please help?
Please do reply.
Bye!



SQL> bre on job
SQL> select job,empno,ename,rn from( select e.*,row_number() over(partition by job order by job)
  2  as rn from emp e)
  3  /

JOB            EMPNO ENAME              RN
--------- ---------- ---------- ----------
ANALYST         7788 SCOTT               1
                7902 FORD                2
CLERK           7369 SMITH               1
                7876 ADAMS               2
                7934 MILLER              3
                7900 JAMES               4
MANAGER         7566 JONES               1
                7782 CLARK               2
                7698 BLAKE               3
PRESIDENT       7839 KING                1
SALESMAN        7499 ALLEN               1

JOB            EMPNO ENAME              RN
--------- ---------- ---------- ----------
SALESMAN        7654 MARTIN              2
                7844 TURNER              3
                7521 WARD                4

14 rows selected. 

Tom Kyte
August 23, 2004 - 8:24 am UTC

it is not a matrix report.  matrix reports are square, this is not square.

this would NOT be something you'd want to do in SQL, however, this is what the SQL could look like:

ops$tkyte@ORA9IR2> break on job skip 1
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> with dept_10
  2  as (
  3  select ename, empno, job, row_number() over (partition by job order by ename) rn
  4     from emp
  5    where deptno = 10  ),
  6  dept_20
  7  as (
  8  select ename, empno, job, row_number() over (partition by job order by ename) rn
  9     from emp
 10    where deptno = 20  ),
 11  dept_30
 12  as (
 13  select ename, empno, job, row_number() over (partition by job order by ename) rn
 14     from emp
 15    where deptno = 30  )
 16  select coalesce( dept_10.job, dept_20.job, dept_30.job ) job,
 17         dept_10.ename, dept_10.empno,
 18         dept_20.ename, dept_20.empno,
 19         dept_30.ename, dept_30.empno
 20    from dept_10 full outer join dept_20 on ( dept_10.job = dept_20.job and dept_10.rn = dept_20.rn )
 21         full outer join dept_30 on ( coalesce(dept_10.job,dept_20.job) = dept_30.job and coalesce(dept_10.rn,dept_20.rn) = dept_30.rn)
 22   order by 1, 2
 23  /
 
JOB       ENAME    EMPNO ENAME    EMPNO ENAME    EMPNO
--------- -------- ----- -------- ----- -------- -----
ANALYST                  SCOTT     7788
                         FORD      7902
 
CLERK     MILLER    7934 ADAMS     7876 JAMES     7900
                         SMITH     7369
 
MANAGER   CLARK     7782 JONES     7566 BLAKE     7698
 
PRESIDENT KING      7839
 
SALESMAN                                ALLEN     7499
                                        MARTIN    7654
                                        TURNER    7844
                                        WARD      7521
 
 
10 rows selected.
 
ops$tkyte@ORA9IR2> clear breaks
 

view or inline view ?

A reader, August 23, 2004 - 5:51 pm UTC

hi tom, in my stored package I have to join two views/tables in more than one places.

should I create a permanent view for this or should I keep the inline view in the from clause ?
as my app. is time sensitive I am more interested in konwing which one is faster ?

Tom Kyte
August 23, 2004 - 7:45 pm UTC

neither is faster, they should be "the same" (well, teensy tiny so small you won't notice recursive sql on a hard parse of a query on a view to get the view text....)

Help Help!!!

A reader, August 26, 2004 - 5:47 am UTC

Hi Tom,
I used your above technique for getting data from second inline view if first is un-succussful. But I am in trouble, as my complex dynamic query (simplified in the test case below) does not return value as below:

create table c (client number(10), client_type VARCHAR2(10), wt number(10));
insert into c values (1,'GOOD',10);
insert into c values (2,'BAD',1);
create table c_other (client number(10), wt number(10));
insert into c_other values (1,10);
insert into c_other values (4,10);
create table master (client number(10), addr varchar2(10));
insert into master values (1,'A1');
insert into master values (2,'A2');
COMMIT;
select master.client, master.addr, X1.wt
FROM master, (
SELECT COUNT(CASE WHEN WHAT = 'T1' THEN WHAT END ) OVER () CNT, X.*
FROM ( SELECT 'T1' WHAT, client, client_type,wt FROM c
WHERE client_type IN ('GOOD','BAD')
UNION ALL
SELECT 'T2' WHAT, client,'UNK',wt FROM c_other
) X
) X1
WHERE ((cnt > 0 and WHAT = 'T1') OR (cnt = 0))
AND master.client = X1.client(+)
/
_____________
output:

CLIENT ADDR WT
---------- ---------- ----------
1 A1 10
2 A2 1

What I expect is: since client 1 was in both tables, first selection is alright, but I am missing values (client 4 in this case) if client was not exist in first table. Can you please help me?


Tom Kyte
August 26, 2004 - 10:07 am UTC


lots wrong with that query -- and I'm not really following the logic.

first, when I see:

where t.c = t2.c(+)
and t2.other_c = ....

I know someone has misunderstood what outer joins are.


if t2.c is outer joined to -- then t2.other_c will be NULL -- meaning the "=" is never ever true. Meaning the (+) is meaningless.


Also, the outer join semantics specifically mean "if a row is in master, we'll see it". here, client=4 isn't in master, there is no chance of seeing it.



So, you've got that outer join when it cannot be used.
And you seem to want to use master to drive the query but master doesn't have all of the data.



Also, your predicate "cnt > 0 and what = 'T1' ) or (cnt=0) filters out client=4 (the "what" is t2 for client =4 and cnt=2 -- so, no client=4 record can possible be produced by the query ever)

What I want to achive?

A reader, August 26, 2004 - 4:42 pm UTC

Hi Tom,
The test case was from large inline views, and had problem in simulating the exact case:
"You are right all rows from master (I added client 4 in the master)"
What I want to achieve:-
Give all rows from master based upon a inline view. If a query in inline views does not return row for specific client for a specific condition, get client and value from another one query with different condition. If first query returned row just ignore the second one.
How do I achieve this?
Thanks for you help.


Tom Kyte
August 26, 2004 - 7:09 pm UTC

well, you have a predicate on the inline view'ed table that ISN"T OUTER JOINED TO properly. thats the

where t.c = t2.c(+)
and t2.other_c = ....


this
WHERE ((cnt > 0 and WHAT = 'T1') OR (cnt = 0))

needs to be pushed into the inline view AND THEN you outer join to that resulting inline view.
comment.

Thanks

Siva, August 27, 2004 - 9:38 am UTC

Dear Tom,
Thanks for your reply.But when I run your query,I got errors as follows.

SQL> with dept_10 as (select job,ename,empno,row_number() over 
(partition by deptno order by ename) rn
                    from emp where deptno = 10),
 dept_20 as (select job,ename,empno,row_number() over
(partition by deptno order by ename) rn
                    from emp where deptno = 20),
 dept_30 as (select job,ename,empno,row_number() over
(partition by deptno order by ename) rn
                    from emp where deptno = 30)
    select coalesce(dept_10.job,dept_20.job,dept_30.job) as job,
           dept_10.ename as Ename,dept_10.empno as Empno,
           dept_20.ename as Ename,dept_20.empno as Empno,
           dept_30.ename as Ename,dept_30.empno as Empno
    from dept_10 full outer join dept_20 on (dept_10.job = dept_20.job and 
dept_10.rn = dept_20.rn) full outer join 
dept_30 on (coalesce(dept_10.job,dept_20.job) =
 dept_30.job and coalesce (dept_10.rn,dept_20.rn) = dept_30.rn)
/

ERROR at line 1:
ORA-00600: internal error code, arguments: [kkqwrm_noref: COLFDNF set], [], [],
[], [], [], [], []

I use Oracle 9i Release 2.What may be the reason for the error?
Please do reply.

 

Tom Kyte
August 27, 2004 - 10:10 am UTC

please contact support as you would do for *any* internal error??!?

A reader, August 28, 2004 - 6:48 pm UTC

Hi Tom,
Regarding :-
"well, you have a predicate on the inline view'ed table that ISN"T OUTER JOINED
TO properly. thats the

where t.c = t2.c(+)
and t2.other_c = ....


this
WHERE ((cnt > 0 and WHAT = 'T1') OR (cnt = 0))

needs to be pushed into the inline view AND THEN you outer join to that
resulting inline view.
comment.
"
I am not able to understand, What is where t.c = t2.c(+)
and t2.other_c = ....

in relation to my test case.
Any way, if you can show me the query, that will be really helpful, as I don't think I will be able to figure out any way, since I don't have good grip on Analtical functions.
Thanks


Tom Kyte
August 28, 2004 - 7:07 pm UTC

this has nothing to do with analytics? it has to do with an inline view.

push the predicate into the inline view "X"

Is it Possible ?

Jagjeet Singh, August 29, 2004 - 6:25 am UTC

Hi,

The main question is
---------------------

" Would inline views be an appropriate technology for, say, listing the
owners,their tables and rowcounts of their tables in DBA_TABLES?

And your answer is
-------------------

" and we said...

No, an inline view would not be useful for doing that. In order to query
DBA_TABLES and get a count of rows in a table for each row in DBA_TABLES -- the
inline view would have to query a different table each time."


o My first point is .. Is it possible to get rowcount using
an inline view with using any function [ like your func ] ? I don't think.

o If we have to create a func to get rowcount.. and this func will be executing for each row in DBA_TABLES. Then what is the diff. in between using it in inline view or call it directly in SQL ?

Thanks,
Js


Tom Kyte
August 29, 2004 - 11:49 am UTC

I said "no, an inline view would not be useful for doing that"???

so, answer = No


the difference with the function is the function could change the SQL it executes eacy and every time it is called (and it would have to since the table to be queried would change each and every time) but the inline view doesn't have the ability to dynamically change it's mind for every row.

Is ORDER BY in inline views documented anywhere?

Martin, November 23, 2004 - 6:14 am UTC

Hi Tom,

quick question :

Is the use of ORDER BY in inline views guaranteed (i.e. documented) to produce the same results as an ORDER BY on the "parent" query? i.e. are the two queries below identical? If so, or otherwise, can you point me to the point in the docs which states this? I've done a search but can't find an explicit reference to this.

SELECT a
FROM t
ORDER BY a

and

SELECT a
FROM ( SELECT a FROM t ORDER BY a )

Thanks in advance


Thanks!

Martin, November 24, 2004 - 3:09 am UTC


Guaranteed only when using rownum ?

pasko, November 24, 2004 - 4:07 am UTC

Hi Tom,

Thanks for the link.

but the link you provided above has a discussion of order by when used with rownum,

Does the same apply when using " order by some_column " ?


I think the answer is "YES" ?


although Martin didn't use rownum in his query.
"
SELECT a
FROM ( SELECT a FROM t ORDER BY a )
"

Another question on the same subject :

when i create a View :

Create or replace view vw_inline as
SELECT a
FROM ( SELECT a FROM t ORDER BY a )

and then:

select a from vw_inline ;

would my order by criteria still preserved ?


Thanks in advance.


Tom Kyte
November 24, 2004 - 7:32 am UTC

rownum was just part of that example -- it was trying to show how to order and then assign rownum instead of assigning rownum and then ordering. the answer "use the inline view with order by as that will sort, then assign rownum to the sorted records"

as long as wv_inline was queried "standalone", yes. if you join it or anything like that -- all bets are off.

Get the lastes records in inline query.

Sean, December 08, 2004 - 11:48 am UTC

Hi Tom,

I have a log table with 25M rows. I want to get latest 5000 records, but this query takes too long time though I have index on log_time column.

select log_content, log_time
from (select * from log_table order by log_time desc)
rownum < 5000;

Thanks so much for your help.


Tom Kyte
December 09, 2004 - 12:37 pm UTC

first rows hint it and make sure log_time is defined NOT NULL or add "and log_time is NOT NULL" to the query.

View Errors

A reader, May 26, 2005 - 5:14 pm UTC

Hi, Tom,

I have a script to create view:
create or replace view blah... as
SELECT
USERNAME, GRANTED_ROLE, 'DIRECT' AS GRANT_TYPE
FROM
SYS.DBA_ROLE_PRIVS INNER JOIN ALL_USERS ON ( GRANTEE=USERNAME )
UNION
SELECT DISTINCT
USERNAME, ROLE_ROLE_PRIVS.GRANTED_ROLE, 'INDIRECT' as GRANT_TYPE
FROM
SYS.DBA_ROLE_PRIVS INNER JOIN ALL_USERS on ( GRANTEE=USERNAME )
INNER JOIN ROLE_ROLE_PRIVS on ( SYS.DBA_ROLE_PRIVS.GRANTED_ROLE = ROLE )
WHERE NOT EXISTS (
SELECT 1
FROM SYS.DBA_ROLE_PRIVS drp
WHERE DRP.GRANTED_ROLE = ROLE_ROLE_PRIVS.GRANTED_ROLE and DRP.GRANTEE = USERNAME
)
/

sho err
/
no error

But when I see it under OEM manager, there is error on this view. And the strangs thing is that I can not edit it under sys & DBA role in OEM, it always tells me that either "table or views not exist" or some other phony errors.

Please help


Tom Kyte
May 26, 2005 - 7:43 pm UTC

please contact support for assistance with OEM like that.

difference between queries

whizkid, June 01, 2005 - 11:21 am UTC

hi tom...

this is probably the most stupid question that you have received.. can you tell me what is the difference between a subquery, inline query(view?), inner query, nested query?

as far as my understanding of the docs go

a. subquery = inline query = inner query
b. nested query is different from the above..

eg of subquery:

select * from (select sysdate from dual);

is the below a nested query or does it use an inline view? what is a nested query? can you give an eg?

select * from
(select pk from x) a,
(select pk from y) b
where a.pk = b.pk;

thanks for taking time out for this one!

Tom Kyte
June 01, 2005 - 11:48 am UTC

there are subqueries -- they are things you use in IN's and such

select * from t where (x,y) in ( SUBQUERY );


there are inline views -- a select statment where a table is normally found:

select * from ( INLINE_VIEW )

there are scalar subqueries, they return 0 or 1 rows (the above return any number of rows) and 1 column, you use them where you could use a "string"


select 'KING', ( SCALAR_SUBQUERY )
from t;


nested just means "many levels of ()"


They could all be considered subqueries. (last one is using inline views above)

Inline view - Explain Plans different

Shreyas, September 26, 2005 - 1:12 am UTC

Hi Tom,

We frequently need to use inline views in our project, and we have run into performance problems with some of our queries. Below is a simulation of what we face -

SQL> create table t1 as select * from user_tables;

Table created.

SQL> create table t2 as select * from user_tables;

Table created.

SQL> create index ik_t1_1 on t1(table_name);

Index created.

SQL> create index ik_t2_1 on t2(table_name);

Index created.

SQL> create table t_big as select * from user_objects;

Table created.

SQL> insert into t_big select * from user_objects
  2  ;

2902 rows created.

SQL> /

2902 rows created.

SQL> /

2902 rows created.

SQL> /

2902 rows created.

SQL> /

2902 rows created.

SQL> /

2902 rows created.

SQL> commit;

Commit complete.

SQL> create index ik_t_big_1 on t_big(object_name);

Index created.

SQL> analyze table t1 compute statistics for table for all indexes for all indexed columns;

Table analyzed.

SQL> analyze table t2 compute statistics for table for all indexes for all indexed columns;

Table analyzed.

SQL> analyze table t_big compute statistics for table for all indexes for all indexed columns;

Table analyzed.

SQL> select table_name, num_rows, to_char(last_analyzed, 'DD-Mon-YYYY HH24:mi:ss')
  2  from user_tables
  3  where table_name in ('T1', 'T2', 'T_BIG');

TABLE_NAME                       NUM_ROWS TO_CHAR(LAST_ANALYZED,'DD-
------------------------------ ---------- --------------------------
T1                                    865 23-Sep-2005 16:16:15
T2                                    866 23-Sep-2005 16:16:19
T_BIG                               20314 23-Sep-2005 16:16:24


Now for a simple union all query (no inline view)

SQL> select table_name, tablespace_name
  2  from    t1
  3  where   table_name = 'A'
  4  union all
  5  select t2.table_name, t2.tablespace_name
  6  from    t1, t2
  7  where   t1.table_name = t2.table_name
  8  and     t2.table_name = 'B';

no rows selected

Elapsed: 00:00:00.06

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=5 Card=2 Bytes=79)
   1    0   UNION-ALL
   2    1     TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=32)
   3    2       INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1)
   4    1     TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=1 Bytes=32)
   5    4       NESTED LOOPS (Cost=3 Card=1 Bytes=47)
   6    5         INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   7    5         INDEX (RANGE SCAN) OF 'IK_T2_1' (NON-UNIQUE) (Cost=1 Card=1)

Extending the query above to make it an inline view, and joining T_BIG

SQL> select b.object_name, b.object_id, t.table_name
  2  from  (
  3  select table_name, tablespace_name
  4  from    t1
  5  where   table_name = 'A'
  6  union all
  7  select t2.table_name, t2.tablespace_name
  8  from    t1, t2
  9  where   t1.table_name = t2.table_name
 10  and     t2.table_name = 'B'
 11  ) t, t_big b
 12  where t.table_name = b.object_name;

no rows selected

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=18 Card=15 Bytes=675)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_BIG' (Cost=8 Card=7 Bytes=210)
   2    1     NESTED LOOPS (Cost=18 Card=15 Bytes=675)
   3    2       VIEW (Cost=2 Card=2 Bytes=30)
   4    3         UNION-ALL
   5    4           INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   6    4           NESTED LOOPS (Cost=2 Card=1 Bytes=30)
   7    6             INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   8    6             INDEX (RANGE SCAN) OF 'IK_T2_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   9    2       INDEX (RANGE SCAN) OF 'IK_T_BIG_1' (NON-UNIQUE) (Cost=1 Card=7)

is fine - uses the indexes that I expect to be used.

Now, just adding this result set as a sub query, the explain plan is no longer the same.

SQL> select * from
  2  (
  3  select b.object_name, b.object_id, t.table_name
  4  from  (
  5  select table_name, tablespace_name
  6  from    t1
  7  where   table_name = 'A'
  8  union all
  9  select t2.table_name, t2.tablespace_name
 10  from    t1, t2
 11  where   t1.table_name = t2.table_name
 12  and     t2.table_name = 'B'
 13  ) t, t_big b
 14  where t.table_name = b.object_name
 15  );

no rows selected

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=42 Card=12815 Bytes=576675)
   1    0   HASH JOIN (Cost=42 Card=12815 Bytes=576675)
   2    1     VIEW (Cost=7 Card=1731 Bytes=25965)
   3    2       UNION-ALL
   4    3         INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   5    3         NESTED LOOPS (Cost=2 Card=1 Bytes=30)
   6    5           INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   7    5           INDEX (RANGE SCAN) OF 'IK_T2_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   8    1     TABLE ACCESS (FULL) OF 'T_BIG' (Cost=32 Card=20314 Bytes=609420)

It does a FTS of T_BIG

Further, I observe, this only happens when there is a UNION ALL query in the inline view

Commenting out the second sub query -

SQL> select * from
  2  (
  3  select b.object_name, b.object_id, t.table_name
  4  from  (
  5  select table_name, tablespace_name
  6  from    t1
  7  where   table_name = 'A'
  8  -- union all
  9  -- select t2.table_name, t2.tablespace_name
 10  -- from    t1, t2
 11  -- where   t1.table_name = t2.table_name
 12  -- and     t2.table_name = 'B'
 13  ) t, t_big b
 14  where t.table_name = b.object_name
 15  );

no rows selected

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=10 Card=1 Bytes=45)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_BIG' (Cost=9 Card=1 Bytes=30)
   2    1     NESTED LOOPS (Cost=10 Card=1 Bytes=45)
   3    2       INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
   4    2       INDEX (RANGE SCAN) OF 'IK_T_BIG_1' (NON-UNIQUE) (Cost=1 Card=1)


Using a UNION instead of UNION ALL

SQL> select * from
  2  (
  3  select b.object_name, b.object_id, t.table_name
  4  from  (
  5  select table_name, tablespace_name
  6  from    t1
  7  where   table_name = 'A'
  8  union
  9  select t2.table_name, t2.tablespace_name
 10  from    t1, t2
 11  where   t1.table_name = t2.table_name
 12  and     t2.table_name = 'B'
 13  ) t, t_big b
 14  where t.table_name = b.object_name
 15  );

no rows selected

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=26 Card=16 Bytes=752)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'T_BIG' (Cost=9 Card=8 Bytes=240)
   2    1     NESTED LOOPS (Cost=26 Card=16 Bytes=752)
   3    2       VIEW (Cost=9 Card=2 Bytes=34)
   4    3         SORT (UNIQUE) (Cost=9 Card=2 Bytes=79)
   5    4           UNION-ALL
   6    5             TABLE ACCESS (BY INDEX ROWID) OF 'T1' (Cost=2 Card=1 Bytes=32)
   7    6               INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1)
   8    5             TABLE ACCESS (BY INDEX ROWID) OF 'T2' (Cost=2 Card=1 Bytes=32)
   9    8               NESTED LOOPS (Cost=3 Card=1 Bytes=47)
  10    9                 INDEX (RANGE SCAN) OF 'IK_T1_1' (NON-UNIQUE) (Cost=1 Card=1 Bytes=15)
  11    9                 INDEX (RANGE SCAN) OF 'IK_T2_1' (NON-UNIQUE) (Cost=1 Card=1)
  12    2       INDEX (RANGE SCAN) OF 'IK_T_BIG_1' (NON-UNIQUE) (Cost=1 Card=8)

So, the problem arises when I use an inline view with UNION ALL and then provide an outer query over the result set. 

In my actual query, the inline view is a set of 5 queries with a UNION ALL (they all return different results), and the outer most query does GROUP BY ROLLUP. Also the T_BIG is actually a transaction table with a lot of rows.

Can you please help me understand this behaviour. Is there anything wrong in the query itself? Is there a better way to write the query.

Thanks. 

Tom Kyte
September 26, 2005 - 9:12 am UTC

what is your version

Inline view - Explain Plans different

Shreyas, September 27, 2005 - 12:29 am UTC

Sorry, should have mentioned earlier.

Oracle 9i (9.2.0.4.0)

Thanks.

Tom Kyte
September 27, 2005 - 10:57 am UTC

I cannot reproduce this in 9206, nor 10g's

A reader, March 01, 2006 - 2:15 pm UTC

Hi Tom,

I have the following requirement. I have a table with 400,000 rows. The structure of the table is :

create table tab
(
id number,
fln number,
ffn number
);

insert into tab values (110, 1, 0);
insert into tab values (111, 1, 1);
insert into tab values (112, 0, 1);

commit;

Now for some reporting purposes, we have some translations like when fln = 1 then weight = 100, fln= 0 then weight = 200, ffn = 0, then weight = 50, ffn = 1, then weight = 100. So for id = 110, I should get total weight as 150, id= 111 total weight = 200 and so on. But this weight translation can be changeable ie, we can decide that when fln = 1, weight = 150 and so on, that is why I cannot have a column totwt in tab which will calculate the total weight. What other method will be easier for me to approach to get the total weight dynamically under this scenario. Can I use some views to populate this?

Please help.

Thanks.

Tom Kyte
March 02, 2006 - 8:32 am UTC

insufficient data. You say "but this weight translation can be changeable" but don't give "how"

if you "change it", does it change for everyone, everywhere.

should individidual sessions be able to change it.

whatever....


If it is "global", just create a table

weights

fln ffn weight

and put all of your pairs in there - and just join.

recreate a bad view

Sean, March 17, 2006 - 10:07 am UTC

Tom,
We use a 3rd party software and I found out it performs prooly. I recreate the view and the new view performs much better. The output of the old and new view are the same. I showed the vendor the new view. But vendor insists they are not the same without approve.

Please look the views, do you think they are not different?

OLD
view_rsrc:

select r.*,
(select uv.udf_text
from v_udfvalue uv, v_udftype ut
where ut.table_name='RSRC'
and ut.udf_type_label='Rsrc ID'
and uv.UDF_TYPE_ID = ut.UDF_TYPE_ID
and uv.fk_id = r.rsrc_id)
as user_text3
from rsrc r
where r.delete_date is null

New
view_rsrc_tmp:

select r.*, uv.udf_text USER_TEXT3
from v_udfvalue uv, v_udftype ut,
rsrc r
where ut.table_name='RSRC'
and ut.udf_type_label='Rsrc ID'
and uv.UDF_TYPE_ID = ut.UDF_TYPE_ID
and uv.fk_id(+) = r.rsrc_id
and r.delete_date is null

select * from view_rsrc_tmp
MINUS
select * from view_rsrc
/


select * from view_rsrc
MINUS
select * from view_rsrc_tmp
/

The both return nothing.





Tom Kyte
March 17, 2006 - 5:47 pm UTC

they are not the same at all.

Not in general anyway.


MINUS implies a distinct. Their view does not. I stopped looking at this after seeing minus (hence I don't know if there are other issues, but minus would be a show stopper)

Inline Views: DB2 vs Oracle

Andreas, June 07, 2006 - 2:14 pm UTC

Tom,

I found the following sql statement which runs in DB2

WITH TEMPTABLE (COL1, COL2) AS
(VALUES ( 0, CHAR('AA',1))
,( 1, CHAR('BB',1))
,( 2, NULL)
)
SELECT *
FROM TEMPTABLE;

I know that you could emulate this statement in Oracle with the help of PL/SQL but is there also a way in Oracle to do it in pure SQL?

Thanks!

Andreas

Tom Kyte
June 07, 2006 - 3:35 pm UTC

ops$tkyte@ORA9IR2> with temptable
  2  as
  3  (select 0 col1, 'aa' col2 from dual
  4   union all
  5   select 1, 'bb' from dual
  6   union all
  7   select 2, null from dual
  8  )
  9  select * from temptable;

      COL1 CO
---------- --
         0 aa
         1 bb
         2

 

Inline views and query rewrite

Serge Shmygelsky, December 15, 2006 - 10:52 am UTC

Hello Tom,

I have a strange situation with an inline view:

OPS$SHMYG@REX> desc test
Name Null? Type
------------------------------- -------- ----------------------------
1 CUSTOMER_ID NUMBER
2 CO_ID NUMBER
3 AMOUNT NUMBER

OPS$SHMYG@REX> select * from test;

CUSTOMER_ID CO_ID AMOUNT
----------- ---------- ----------
1 1 100
1 2 200
OPS$SHMYG@REX> select customer_id, co_id, sum(amount) as total_amount from test group by customer_id;
select customer_id, co_id, sum(amount) as total_amount from test group by customer_id
*
ERROR at line 1:
ORA-00979: not a GROUP BY expression


OPS$SHMYG@REX> select customer_id, total_amount from (select customer_id, co_id, sum(amount) as total_amount from test group by customer_id);

CUSTOMER_ID TOTAL_AMOUNT
----------- ------------
1 300

Looks like my inline view gets rewritten in some way (actually, I can presume that this is simple SELECT customer_id, SUM(amount) FROM test GROUP BY customer_id) but I cannot understand why CO_ID from sub-query disappears and semantically wrong query becomes right. Is it normal? If it is, is there any rules describing query re-write? I didn't find any useful information in the documentation.

Tom Kyte
December 15, 2006 - 11:44 am UTC

full example please - one we can all run ourselves.

looks like a "product issue"

Inline views and query rewrite - ctd

Serge Shmygelsky, December 18, 2006 - 3:52 am UTC

Here it comes


OPS$SHMYG@REX> create table test (customer_id number, co_id number, amount number);
OPS$SHMYG@REX> insert into test values (1, 1, 100);
OPS$SHMYG@REX> insert into test values (1, 2, 200);
OPS$SHMYG@REX> commit;
OPS$SHMYG@REX> select * from test;

CUSTOMER_ID CO_ID AMOUNT
----------- ---------- ----------
1 1 100
1 2 200
OPS$SHMYG@REX> select customer_id, co_id, sum(amount) from test group by customer_id;
select customer_id, co_id, sum(amount) from test group by customer_id
*
ERROR at line 1:
ORA-00979: not a GROUP BY expression


OPS$SHMYG@REX> select customer_id, total_amount from (select customer_id, co_id, sum(amount) as total_amount from test group by customer_id);

CUSTOMER_ID TOTAL_AMOUNT
----------- ------------
1 300
OPS$SHMYG@REX> select * from v$version;

BANNER
----------------------------------------------------------
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod
PL/SQL Release 10.2.0.1.0 - Production
CORE 10.2.0.1.0 Production
TNS for Linux: Version 10.2.0.1.0 - Production
NLSRTL Version 10.2.0.1.0 - Production


Tom Kyte
December 18, 2006 - 8:28 am UTC

interesting, I see what it did now - it realized that "co_id" wasn't relevant and simply rewrote it out of the query all together, it was "not there"

ops$tkyte%ORA10GR2> select *
  2    from (select customer_id, co_id, sum(amount) as total_amount
  3            from test
  4           group by customer_id
  5         )
  6  /
select *
       *
ERROR at line 1:
ORA-00979: not a GROUP BY expression


ops$tkyte%ORA10GR2> select customer_id, total_amount
  2    from (select customer_id, co_id, sum(amount) as total_amount
  3            from test
  4           group by customer_id
  5         )
  6  /

CUSTOMER_ID TOTAL_AMOUNT
----------- ------------
          1          300
          2         1400
 

Inline views and query rewrite - ctd

Serge Shmygelsky, December 18, 2006 - 9:55 am UTC

Yes, looks like it really did. But I still have the same question - is it right or not? I wouldn't say it is correct.
P.S. It doesn't work in another versions (e.g. 8.1.7)

Tom Kyte
December 18, 2006 - 10:23 am UTC

I would say in this case - it is OK since that column is absolutely not referenced in the outer queries, it is as if it was not there at all.

Inline views and query rewrite

Michel Cadot, December 18, 2006 - 10:11 am UTC

In my opinion, it is a bug.

Why the optimizer can choose that
select customer_id, total_amount
from (select customer_id, co_id,
sum(amount) as total_amount
from test group by customer_id);

means
select customer_id, total_amount
from (select customer_id,
sum(amount) as total_amount
from test group by customer_id);

and not
select customer_id, total_amount
from (select customer_id, co_id,
sum(amount) as total_amount
from test group by customer_id, co_id);

There is no way to rewrite/interpret it in either version. The first query is just a wrong one and the query rewrite is incorrect. The optimizer is too optimistic assuming we write only well formed queries.
This is just my opinion.

Michel

Inline views and query rewrite - ctd

Serge Shmygelsky, December 18, 2006 - 10:45 am UTC

I would agree with Michel. It is a kind of MS Excel behavior - 'I can see that the query is incorrect but I can fix it as I'm smart and have my own rules so I'll do it'. :( IMHO, it is not the right idea

inline view

A reader, May 22, 2008 - 8:03 am UTC

Tom:

To get total quantity of customer orders by date, customer,category I had to create an inline view.

Is there a way to do a table join and SUM the quantity in one sql statement (no subquery) or i have to do it as below.





ordered_items
-------
order_date
cust_id
Ord_qty
part_no

parts
------
part_no
category

FOR X in (
SELECT order_Date,cust_id,category,sum(ord_Qty) total_qty FROM
(
SELECT a.order_Date,a.cust_id,b.category,ord_qty FROM ordered_items, parts b
WHERE a.part_no = b.part_no and order_date = v_order_Date
)
GROUP BY order_Date,cust_id,category
HAVING SUM(ord_qty) > 0
ORDER BY order_Date,cust_id,category
LOOP
.......code
Tom Kyte
May 23, 2008 - 7:54 am UTC

         SELECT order_Date,cust_id,category,sum(ord_Qty) total_qty FROM
         (
         SELECT a.order_Date,a.cust_id,b.category,ord_qty FROM ordered_items, 
parts b
         WHERE a.part_no = b.part_no and order_date = v_order_Date
         )
        GROUP BY order_Date,cust_id,category
        HAVING SUM(ord_qty) > 0
        ORDER BY order_Date,cust_id,category


is the same as

         SELECT a.order_Date,a.cust_id,b.category,sum(a.ord_qty)
           FROM ordered_items, parts b
          WHERE a.part_no = b.part_no and order_date = v_order_Date
          GROUP BY a.order_Date,a.cust_id,b.category
         HAVING SUM(a.ord_qty) > 0
        ORDER BY a.order_Date,a.cust_id,b.category

A reader, May 23, 2008 - 6:30 pm UTC

Tom:

sorry, I missed the decode statment. This is why I had to do like above. is there a better way to do in one SELECT with decode.

SELECT order_Date,cust_id,category,sum(ord_Qty) total_qty FROM
(
SELECT a.order_Date,a.cust_id,decode(b.category,'CD','X',b.category),ord_qty FROM ordered_items,
parts b
WHERE a.part_no = b.part_no and order_date = v_order_Date
)
GROUP BY order_Date,cust_id,category
HAVING SUM(ord_qty) > 0
ORDER BY order_Date,cust_id,category
Tom Kyte
May 23, 2008 - 6:47 pm UTC

         SELECT a.order_Date,a.cust_id,
                decode(b.category,'CD','X',b.category),
                sum(a.ord_qty)
           FROM ordered_items, parts b
          WHERE a.part_no = b.part_no and order_date = v_order_Date
          GROUP BY a.order_Date,a.cust_id,
                   decode(b.category,'CD','X',b.category)
         HAVING SUM(a.ord_qty) > 0
        ORDER BY a.order_Date,a.cust_id,decode(b.category,'CD','X',b.category)

inline view

A reader, May 23, 2008 - 11:28 pm UTC

Tom:

excellent. would this always be better because of ONE SELECT.

To test it, would you just turn autotrace ON and watch the stats on consistent gets and see which one is less.

I think you always have clear the shared pool with each test because subsequence calls seem to be less overhead.
Tom Kyte
May 24, 2008 - 6:57 pm UTC

you had one select, i had one select - they are the same - the optimizer would have recognized that and done the right thing, don't be afraid of using inline views necessarily to make things more readable - the optimizer does predicate pushing and view merging.

you would never clear the shared pool for this... I don't know why you think you might.

inline

A reader, May 25, 2008 - 12:03 pm UTC

Tom:

1) I guess i was wrong in thinking that one SELECT like you wrote would alway be better than having two SELECTs (inline view). Shall i leave it the same way since it is written now if it is the same thing.

For some reason, i thought an inline view would first run the inside selects and then another select on the subquery results.

2) When you want to see whether Query1 is better than Query 2 what is your standard way of performance comparison?

Do you set autotrace ON in sql*plus and run both and monitor a specific number? The reason i said you clear the shared pool because the first hit results in different stats than the subsequent hits since the sql is acached i assume.

thanks
Tom Kyte
May 27, 2008 - 7:47 am UTC

1) you only have ONE select there - an inline view does not make it "two" or N selects - you have one select....

2) you WANT the sql to be 'cached' - the plan, the execution path - because in real like it would be cached.

I think you are confusing the buffer cache with the shared pool - but even if you are, you want the buffer cache to be populated too because in real life the cache would never be devoid of information (it is more artificial to flush the caches then to not flush them in short)

TKPROF and sql tracing are my tools of preference - autotrace can be fine in some cases, but tkprof shows more detail.

To 'a reader'

A reader, May 26, 2008 - 11:19 am UTC

2) When you want to see whether Query1 is better than Query 2 what is your standard way of
performance comparison?


You can use explain plan, or take a look at http://asktom.oracle.com/~tkyte/runstats.html

regards,

MH

Special Query

A reader, June 10, 2008 - 10:23 am UTC

Sorry to ask you this question here. I have not found an equivalent question in this site and I have rarely the possibility to ask you a new question.

-- I have the following query

/* Formatted on 2008/06/10 16:16 (Formatter Plus v4.8.5) */
SELECT *
FROM t_emp
WHERE ename = 'SMITH' OR deptno = '20'

-- I would like to have only the SMITH record if ename = SMITH exists
-- and if ename=SMITH doesn't exist then I want to have all the
-- enames of department 20

EMPNO ENAME JOB
-------- ---------- ---------
7369 SMITH CLERK


-- But if I issue the following select
/* Formatted on 2008/06/10 16:16 (Formatter Plus v4.8.5) */
SELECT *
FROM t_emp
WHERE ename = 'TOM' -- doesn't exist
OR deptno = '20'

-- Then I would like to have the following results
EMPNO ENAME JOB
--------- ---------- ---------
7369 SMITH CLERK
7566 JONES MANAGER
7788 SCOTT ANALYST
7876 ADAMS CLERK
7902 FORD ANALYST

Tom Kyte
June 10, 2008 - 11:51 am UTC

ops$tkyte%ORA11GR1> exec :x := 'SMITH'; :y := 20

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> select empno, ename, job
  2    from scott.emp
  3   where ename = :x
  4   union all
  5  select empno, ename, job
  6    from scott.emp
  7   where deptno = :y
  8     and not exists (select null from scott.emp where ename = :x)
  9  /

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      CLERK

ops$tkyte%ORA11GR1> exec :x := 'TOM'; :y := 20

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR1> /

     EMPNO ENAME      JOB
---------- ---------- ---------
      7369 SMITH      CLERK
      7566 JONES      MANAGER
      7788 SCOTT      ANALYST
      7876 ADAMS      CLERK
      7902 FORD       ANALYST



create a custom_top in oracle apps

kishor, June 10, 2008 - 11:02 pm UTC

how can i create a custom_top in Oracle Apps ?could you please give me one example .

Relations ship between po,gl,ap

kishor, June 12, 2008 - 1:25 am UTC

Hi Tom,
My self Kishor. Can you give me the what are the most important join conditions between the
1)PO to GL 2)AP to GL
And which table contain the
2-way matching,
3-way matching,
4-way mathcing of data.could you please give me the table's name.


Thanks
kishor

how to create a sum functions

kishor, June 12, 2008 - 1:35 am UTC

Hi Tom,
I am kishor. if i create a function that work's alike function is SUM.Could please give me the logic. And give one example using our own sum function on table(emp) like.I hope best answer form you.


Thanks and Regards
Kishor




Tom Kyte
June 12, 2008 - 7:46 am UTC

why would you reinvent the wheel?

but, here is an example of creating a user defined aggregate that 'sums' (intervals)

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:5269744394385

A reader, December 22, 2008 - 4:55 am UTC

Is there any difference between performance of following queries.

1)SELECT EMP.*,DPT.DNAME
FROM EMP,(SELECT DEPTNO,MAX(DNAME) DNAME FROM DEPT GROUP BY DEPTNO) DPT
WHERE EMP.DEPTNO=10
AND EMP.DEPTNO=DPT.DEPTNO

2)SELECT EMP.*,DPT.DNAME
FROM EMP,(SELECT DEPTNO,MAX(DNAME) DNAME FROM DEPT WHERE DEPTNO=10 GROUP BY DEPTNO) DPT
WHERE EMP.DEPTNO=10
AND EMP.DEPTNO=DPT.DEPTNO

You may say that both are not correct, I just want to know that when we use inline view should we give the cretria to inline view as in query 2 given deptno=10.

Should in case of query 1 Dept table will be full scan and in query 2 only records of table 10 will be scaned.

Tom Kyte
December 29, 2008 - 2:52 pm UTC

you would use explain plan to see....


we can do this thing called "predicate pushing" and "view merging"

In general - when possible, the where clause is pushed down...


ops$tkyte%ORA10GR2> create table emp as select * from scott.emp where 1=0;

Table created.

ops$tkyte%ORA10GR2> create index emp_deptno_idx on emp(deptno);

Index created.

ops$tkyte%ORA10GR2> create table dept as select * from scott.dept where 1=0;

Table created.

ops$tkyte%ORA10GR2> create index dept_deptno_idx on dept(deptno);

Index created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> exec dbms_stats.set_table_stats( user, 'EMP', numrows => 1000000, numblks => 100000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> exec dbms_stats.set_table_stats( user, 'DEPT', numrows => 100000, numblks => 10000 );

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> @at
ops$tkyte%ORA10GR2> column PLAN_TABLE_OUTPUT format a72 truncate
ops$tkyte%ORA10GR2> set autotrace traceonly explain
ops$tkyte%ORA10GR2> SELECT EMP.*,DPT.DNAME
  2  FROM EMP,(SELECT DEPTNO,MAX(DNAME) DNAME FROM DEPT GROUP BY DEPTNO) DPT
  3  WHERE EMP.DEPTNO=10
  4  AND EMP.DEPTNO=DPT.DEPTNO
  5  /

Execution Plan
----------------------------------------------------------
Plan hash value: 2341458429

------------------------------------------------------------------------
| Id  | Operation                      | Name            | Rows  | Bytes
------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |    10M|  1039
|*  1 |  HASH JOIN                     |                 |    10M|  1039
|   2 |   VIEW                         |                 |  1000 | 22000
|   3 |    HASH GROUP BY               |                 |  1000 | 35000
|   4 |     TABLE ACCESS BY INDEX ROWID| DEPT            |  1000 | 35000
|*  5 |      INDEX RANGE SCAN          | DEPT_DEPTNO_IDX |   400 |
|   6 |   TABLE ACCESS BY INDEX ROWID  | EMP             | 10000 |   849
|*  7 |    INDEX RANGE SCAN            | EMP_DEPTNO_IDX  |  4000 |
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("EMP"."DEPTNO"="DPT"."DEPTNO")
   5 - access("DEPTNO"=10)
   7 - access("EMP"."DEPTNO"=10)

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> SELECT EMP.*,DPT.DNAME
  2  FROM EMP,(SELECT DEPTNO,MAX(DNAME) DNAME FROM DEPT WHERE DEPTNO=10 GROUP BY DEPTNO) DPT
  3  WHERE EMP.DEPTNO=10
  4  AND EMP.DEPTNO=DPT.DEPTNO
  5  /

Execution Plan
----------------------------------------------------------
Plan hash value: 2341458429

------------------------------------------------------------------------
| Id  | Operation                      | Name            | Rows  | Bytes
------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |                 |    10M|  1039
|*  1 |  HASH JOIN                     |                 |    10M|  1039
|   2 |   VIEW                         |                 |  1000 | 22000
|   3 |    HASH GROUP BY               |                 |  1000 | 35000
|   4 |     TABLE ACCESS BY INDEX ROWID| DEPT            |  1000 | 35000
|*  5 |      INDEX RANGE SCAN          | DEPT_DEPTNO_IDX |   400 |
|   6 |   TABLE ACCESS BY INDEX ROWID  | EMP             | 10000 |   849
|*  7 |    INDEX RANGE SCAN            | EMP_DEPTNO_IDX  |  4000 |
------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("EMP"."DEPTNO"="DPT"."DEPTNO")
   5 - access("DEPTNO"=10)
   7 - access("EMP"."DEPTNO"=10)

ops$tkyte%ORA10GR2> set autotrace off


same plans = same performance = same thing....

Inline View or Outer Join

A reader, April 03, 2010 - 4:54 am UTC

which one is better inline view or outer join.
Tom Kyte
April 05, 2010 - 1:24 pm UTC

I cannot see how this makes sense?

This is like asking "which is better, a hammer or a carpenter".

give me an example of what you might mean...

inline view

A reader, April 05, 2010 - 3:10 pm UTC


A reader, April 07, 2010 - 5:40 am UTC

which one is better of following:

SELECT e.ename, e.sal, d.dname
FROM emp e, dept d
WHERE e.deptno = d.deptno(+)


SELECT e.ename, e.sal, (SELECT dname
FROM dept d
WHERE d.deptno = e.deptno) dname
FROM emp e
Tom Kyte
April 12, 2010 - 8:36 pm UTC

there are two answers to all technical questions

a) why, why are you trying to do that?
b) it depends

The answer to this is (b), it depends.


Do you want all rows from the query as fast as possible? Probably, the first query is "best", use a big bulk hash join and two full scans. Fastest way to the last row but you have to wait for a full scan of one table to complete before getting the first row.

Do you want the first row as fast as possible? Probably, the second query is "best", get the first row from emp - look up (via an index probably) the row in dept if it exists, and return it. Fastest way to the first row, slowest way to the last row.



Inlineview and outer join

Jayadevan, July 24, 2011 - 11:40 pm UTC

Hi Tom,
We have a JOB_MASTER table, with one record for each job, and a JOB_STATUS table, with many records for each job. Each time the job status changes, it results in one record in this status table, with relevant details. We have to flatten the data from this status table so that the life of a job appears as a record, not as many records. SQLs for these tables are like this
CREATE TABLE "JOB_MASTER" 
  ( 
    "JOB_IDR"    NUMBER(15,0), 
    "JOB_NO"     VARCHAR2(25 BYTE) NOT NULL ENABLE, 
    "OFFICE_COD" VARCHAR2(10 BYTE) NOT NULL ENABLE, 
    "JOB_DATE" DATE NOT NULL ENABLE, 
    "CLIENT_COD"         NUMBER(10,0) NOT NULL ENABLE, 
    "COMPANY_COD"        NUMBER(10,0) NOT NULL ENABLE, 
    "BRAND_COD"          NUMBER(10,0), 
    "JOB_TYPE_COD"       VARCHAR2(25 BYTE), 
    "CURRENCY_COD"       VARCHAR2(10 BYTE) NOT NULL ENABLE, 
    "CLIENT_APPROVED"    VARCHAR2(1 BYTE), 
    "CLIENT_PO_RECEIVED" VARCHAR2(1 BYTE), 
    "CLIENT_PO_NO"       VARCHAR2(25 BYTE), 
    "CLIENT_PO_DATE" DATE, 
    "CLOSED_BY" NUMBER(10,0), 
    "CLOSED_ON" DATE, 
    "STATUS"    VARCHAR2(10 BYTE) NOT NULL ENABLE, 
    "CR_STATUS" VARCHAR2(10 BYTE) 
  ) 
  
CREATE TABLE "JOB_STATUS" 
  ( 
    "STATUS_IDR" NUMBER(20,0) NOT NULL ENABLE, 
    "JOB_COD"    NUMBER(15,0) NOT NULL ENABLE, 
    "STATUS"     VARCHAR2(50 BYTE) NOT NULL ENABLE, 
    "STATUS_DATE" DATE NOT NULL ENABLE, 
    "REMARKS" VARCHAR2(2000 BYTE) 
  )   


I tried 3 ways of doing this -
1)
SELECT JOB_IDR, 
    JOB_NO, 
    OFFICE_COD, 
    JOB_DATE, 
    CLIENT_COD, 
    COMPANY_COD, 
    BRAND_COD, 
    JOB_TYPE_COD, 
    CURRENCY_COD, 
    MAX(OPENED_ON) OPENED_ON, 
    MAX(APPROVED_ON) APPROVED_ON, 
    MAX(APPROVED_REMARKS) APPROVED_REMARKS , 
    MAX(ASSIGNED_ON) ASSIGNED_ON , 
    MAX(ASSIGNED_REMARKS) ASSIGNED_REMARKS , 
    MAX(DELIVERED_ON) DELIVERED_ON , 
    MAX(DELIVER_REMARKS) DELIVER_REMARKS, 
    MAX(CLOSED_ON) CLOSED_ON , 
    MAX(CLOSED_REMARKS) CLOSED_REMARKS 
  FROM 
    (SELECT JOB_MASTER.JOB_IDR, 
      JOB_MASTER.JOB_NO, 
      JOB_MASTER.OFFICE_COD, 
      JOB_MASTER.JOB_DATE, 
      JOB_MASTER.CLIENT_COD, 
      JOB_MASTER.COMPANY_COD, 
      JOB_MASTER.BRAND_COD, 
      JOB_MASTER.JOB_TYPE_COD, 
      JOB_MASTER.CURRENCY_COD, 
      DECODE(JOB_STATUS.status,'OPEN',STATUS_DATE,NULL) OPENED_ON, 
      DECODE(JOB_STATUS.status,'APPROVED',STATUS_DATE,NULL) APPROVED_ON, 
      DECODE(JOB_STATUS.status,'APPROVED',REMARKS,NULL) APPROVED_REMARKS, 
      DECODE(JOB_STATUS.status,'ASSIGNED',STATUS_DATE,NULL) ASSIGNED_ON, 
      DECODE(JOB_STATUS.status,'ASSIGNED',REMARKS,NULL) ASSIGNED_REMARKS, 
      DECODE(JOB_STATUS.status,'DELIVERED',STATUS_DATE,NULL) DELIVERED_ON, 
      DECODE(JOB_STATUS.status,'DELIVERED',REMARKS,NULL) DELIVER_REMARKS, 
      DECODE(JOB_STATUS.status,'CLOSED',STATUS_DATE,NULL) CLOSED_ON, 
      DECODE(JOB_STATUS.status,'CLOSED',REMARKS,NULL) CLOSED_REMARKS 
    FROM JOB_MASTER JOB_MASTER 
    LEFT OUTER JOIN JOB_STATUS 
    ON JOB_MASTER.job_idr=JOB_STATUS.JOB_COD 
    ) 
  GROUP BY JOB_IDR, 
    JOB_NO, 
    OFFICE_COD, 
    JOB_DATE, 
    CLIENT_COD, 
    COMPANY_COD, 
    BRAND_COD, 
    JOB_TYPE_COD, 
    CURRENCY_COD 

2)
  SELECT JOB_IDR, 
    JOB_NO, 
    OFFICE_COD, 
    JOB_DATE, 
    CLIENT_COD, 
    COMPANY_COD, 
    BRAND_COD, 
    JOB_TYPE_COD, 
    CURRENCY_COD, 
    (SELECT STATUS_DATE 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'OPEN' 
    ) OPENED_ON, 
    (SELECT STATUS_DATE 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'APPROVED' 
    ) APPROVED_ON, 
    (SELECT REMARKS 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'APPROVED' 
    ) APPROVED_REMARKS, 
    (SELECT STATUS_DATE 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'ASSIGNED' 
    ) ASSIGNED_ON, 
    (SELECT REMARKS 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'ASSIGNED' 
    ) ASSIGNED_REMARKS, 
    (SELECT STATUS_DATE 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'DELIVERED' 
    ) DELIVERED_ON, 
     (SELECT REMARKS 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'DELIVERED' 
    ) DELIVER_REMARKS, 
    (SELECT STATUS_DATE 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'CLOSED' 
    ) CLOSED_ON, 
    (SELECT REMARKS 
    FROM JOB_STATUS 
    WHERE JOB_COD = J.JOB_IDR 
    AND STATUS    = 'CLOSED' 
    ) CLOSED_REMARKS 
  FROM JOB_MASTER J ;         
  

3) I also tried aliasing the JOB_STATUS table a fw times for each of the different statuses and a left outer join.
All the 3 approaches seem to be taknig a lot of time in retrieving data. The status and job_cod columns of job_status and job_idr column of jom_master are indexed and tables analyzed. Is there a better approach?
Tom Kyte
July 27, 2011 - 8:02 pm UTC

if any indexes are being used - that is bad. You want two full scans and a hash join.

do you have that?

indexes will be SLOW in this case.


tell us - how big are these things?

Table sizes

Jayadevan, July 28, 2011 - 2:15 am UTC

Hi,
Thanks for the reply.
Master table is about 16 MB, 29500 records
Status table is about 4 MB, 72400 records
The tables have many more columns than the ones I showed. I was trying to keep it simple to explain the problem.
In the query where I used inline views, it went for the index. In the other one, it want for the full scan with hash join as you mentioned. But the issue is, it is taking about 35 seconds in both cases , and the cost is more lest for the inline view approach. My basic doubt is, is there another way of writing this to improve performance? The actual query which takes 37 seconds, uses this view. That query has many more tables and a filter on JOB_DATE in the master table. But if I remove this view and related columns, it comes back in a few seconds.

Tom Kyte
July 28, 2011 - 7:18 pm UTC

so, they are teeny tiny tables.

how long is long to you - this should not take more than a second or two on any reasonable machine.

what size is your machine?
what are your memory settings?
how many other concurrent users are doing things on this machine?

the plan doesn't really matter for such a tiny set of data - if you have a machine of any good size, this is a few seconds at most.

Correction

Jayadevan, July 28, 2011 - 3:25 am UTC

Sorry, "cost is more lest " - is actually cost is less for the inlineview approach

merge vs unnest

pranav, February 15, 2013 - 10:19 pm UTC

Hi Sir,

I have gone through the definitions of merge/unnest but I did not understand these terms(looks like both are used to combine the subquery to outer query?). Also what is the difference between inline view/subquery? I guess these are interrelated? Request you to show with an example please.

Thank you very much for your time.
Tom Kyte
February 16, 2013 - 9:59 am UTC

https://blogs.oracle.com/optimizer/entry/optimizer_transformations_subquery_unesting_part_1

that has a pretty good explanation of the unnest query rewrite.

More to Explore

CLOB/BLOB/etc

Complete documentation on Securefiles and Large Objects here