Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Prashant.

Asked: September 16, 2002 - 10:11 pm UTC

Last updated: April 14, 2011 - 10:14 am UTC

Version: 8.1.7

Viewed 1000+ times

You Asked

Hi,
I have a table named INTERACTION_LOG with following details.

specialist_id number(5)
account_no number(10)
date_created date


Data
Specialist ID Account No Date Created
1 100 15-SEP-2002 09:10:10
1 100 15-SEP-2002 09:11:10
1 100 15-SEP-2002 10:10:10
1 100 15-MAR-2001 09:10:10
1 100 15-JUN-2002 09:11:10
1 400 15-JUN-2002 09:11:11
2 100 15-SEP-2002 09:12:10
2 200 14-SEP-2002 10:10:10
2 200 14-SEP-2002 10:11:10
2 200 14-SEP-2002 19:10:10
2 200 14-SEP-2001 10:10:10
2 200 14-MAR-2001 19:10:10
2 500 15-JUN-2002 09:11:11

I need a listing of duplicate interaction within a difference
of 5 minutes for a given speciliast for the same account number
between a date range 10-SEP-2002 and 16-SEP-2002.

My output for the query should be
Specialist ID Account No Date Created
1 100 15-SEP-2002 09:10:10
1 100 15-SEP-2002 09:11:10
2 200 14-SEP-2002 10:10:10
2 200 14-SEP-2002 10:11:10

Is it possible through a single query or inline views?
Specialist ID number 1 is working on the same account number within
5 minutes difference of date created field. Same is for specialist
2 for account number 200.
Your help is appreciated.
Thanks,
Prashant Shah

and Tom said...

ops$tkyte@ORA817DEV.US.ORACLE.COM> select *
2 from (
3 select t.*,
4 count(*)
5 over (partition by specialist_id
6 order by date_created
7 range between 1/24/60*2.5 preceding and 1/24/60*2.5 following ) dups
8 from t
9 where date_created between to_date( '10-SEP-2002', 'dd-mon-yyyy' )
10 and to_date( '16-SEP-2002', 'dd-mon-yyyy' )
11 )
12 where dups > 1
13 /

SPECIALIST_ID ACCOUNT_NO DATE_CREATED DUPS
------------- ---------- -------------------- ----------
1 100 15-sep-2002 09:10:10 2
1 100 15-sep-2002 09:11:10 2
2 200 14-sep-2002 10:10:10 2
2 200 14-sep-2002 10:11:10 2


analytic functions totally rule, don't they.....


Rating

  (116 ratings)

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

Comments

Where can I find more

Guy, September 17, 2002 - 10:35 am UTC

Tom, Great solution. When can find the documentaion for these special queries (analytic functions)?

Tom Kyte
September 17, 2002 - 12:43 pm UTC

</code> http://docs.oracle.com/docs/cd/A87860_01/doc/server.817/a76994/analysis.htm#11237 <code>

(if you have my book "Expert one on one Oracle" i cover them in depth in there as well)

Swell analytic function usage, but what about that implicit arithmetic grouping?

Scott, September 17, 2002 - 10:44 am UTC

Yes indeed, analytic functions rule. However your use of implicit arithmetic precidence kinda scares me:

1/24/60*2.5

I have to either figure out what this is meant to resolve to from the context of the question or I have to go dig into Oracle's operator precidence resolution rules. And do they vary between the sql engine and the pl/sql engine? Ick.

1/(24*60*2.5)

Whew.

Apart from that, much like analytic functions, you rule Tom (lest my nit-picking be construed as undue criticism).

Cheers,
Scott

Excellent implementation ! Hats off !

Rama, September 17, 2002 - 11:08 am UTC


Question

Prashant, September 17, 2002 - 12:00 pm UTC

Can you please clarify "1/24/60*2.5"? Why you have 2.5?
Also I needed if account number is also duplicated.

Tom Kyte
September 17, 2002 - 12:48 pm UTC

1/24 = 1 hour

1/24/60 = 1 minute

2.5 * (1 minute) = 2 and 1/2 minutes.

We wanted a 5 minute "window" around the current row -- and to count all of the rows in that 5 minute window. That is what:

5 over (partition by specialist_id
6 order by date_created
7 range between 1/24/60*2.5 preceding and 1/24/60*2.5 following

does -- it breaks the result set up by specialist_id

Within each group of specialist-ids, it sorted by DATE_CREATED

The count(*) was then computed for all rows within + or -2 1/2 minutes of the current rows date_created (the range is always based on the order by column)



You could either partition by specialist_id, account_no to get it by specialist_id/acount_no

Can we use self join for this...

Anand, February 28, 2003 - 8:19 am UTC

Tom, as usual learnt a lot. Can we use self join to get the same done?

select a.specialist_id,a.account_no,to_chara.date_created,'dd-mon-yy hh:mi:ss')
from testtable a, testtable b
where
a.specialist_id=b.specialist_id
and a.date_created <> b.date_created
and ((a.date_created - b.date_created)*24*60* 60) between -150 and 150 --Window of 5 minutes
and trunc(a.date_created) between '10-SEP-2002' and '16-SEP-2002'
order by 2,3


Tom Kyte
February 28, 2003 - 10:09 am UTC

I usually use self joins as part two of examples with analytics to show how inhumanly inefficient they are to accomplish this task!


Sure -- you can do it.
But
It'll be WAY slow


put a couple thousand rows in there -- say 10-20k -- then check it out.



Note: word to the wise....

would you compare an apple to a toaster oven?

Then why compare a date to a string -- ALWAYS but ALWAYS compare dates to dates, strings to strings, numbers to number and NEVER NEVER store a date as a number/string or a number as a string.


data type ?

A reader, February 28, 2003 - 12:57 pm UTC

Hi tom,

do we have any fn that will tell me the datatype of the variable >

type () ?

if we try to create one how hard it is ?



Tom Kyte
February 28, 2003 - 1:55 pm UTC

with the new inheritance system for object types -- yes, as long as you have a routine that accepts the parent type -- you can ask "what type are you really"

for the scalar types -- it is pretty easy

ops$tkyte@ORA920> create or replace package what
  2  as
  3          function type_am_i( x in number ) return varchar2;
  4          function type_am_i( x in varchar2 ) return varchar2;
  5          function type_am_i( x in date ) return varchar2;
  6  end;
  7  /

Package created.

ops$tkyte@ORA920> create or replace package body what
  2  as
  3          function type_am_i( x in number ) return varchar2
  4          is begin
  5          return 'number'; end;
  6
  7          function type_am_i( x in varchar2 ) return varchar2
  8          is begin
  9          return 'varchar2'; end;
 10
 11          function type_am_i( x in date ) return varchar2
 12          is begin
 13          return 'date'; end;
 14
 15  end;
 16  /

Package body created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> declare
  2          a number;
  3          b varchar2(20);
  4          c date;
  5  begin
  6          dbms_output.put_line( what.type_am_i(a) );
  7          dbms_output.put_line( what.type_am_i(b) );
  8          dbms_output.put_line( what.type_am_i(c) );
  9  end;
 10  /
number
varchar2
date

PL/SQL procedure successfully completed.



But -- why?  the caller would already KNOW what type the thing was -- how could it "not" know???? 

just like that !

A reader, February 28, 2003 - 3:57 pm UTC


if it annoied you, I am sorry
I was just wondering if I need to find the type at run time or something.

by the way the "LET US KNOW" link is a the bottom "NOW"
that's very cool and convinient
thanks :)



Tom Kyte
February 28, 2003 - 4:46 pm UTC

you didn't annoy me -- i'm just wondering what piece of code you could have that would need to find the datatype at runtime -- I cannot imagine where or how this would be used.

I mean, you have a variable "X".

You wrote the code.
You defined X
You by definition know the datatype already.

compare

A reader, February 28, 2003 - 5:02 pm UTC

Wrote file afiedt.buf

  1  declare
  2            a number;
  3            b varchar2(20);
  4            c date;
  5    begin
  6      c := '01-JAN-2003';           -- 1
  7              dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
  8      c := sysdate;                 --2
  9              dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
 10      c := to_date('01-JAN-2003');   --3
 11              dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
 12      c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
 13              dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
 14*  end;
SQL> /
date 1:01-JAN-03
date 2:28-FEB-03
date 3:01-JAN-03
date 4:01-JAN-03

PL/SQL procedure successfully completed.


so in above what is implicit conversion 
(should I use it ?)
what is explicit conversion (should I use it ?)

explicitly with dates in chars 

Tom Kyte
February 28, 2003 - 7:29 pm UTC

1 - implicit
2 - no conversion
3 - explicit
4 - explicit


you should ALWAYS explicity convert a string to a date in my opinion. Do this:

alter session set nls_date_format = 'dd/mm/yyyy';

and then run your block and see what happens.

what's the point in doing that ?

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

Hi tom,

 I executed the block with change in the sesssion date format . what is the difference you were pointing ?

please explain !!



10:32:25 SQL> alter session set nls_date_format = 'dd/mm/yyyy';

Session altered.

Elapsed: 00:00:00.00
10:32:31 SQL>   declare
10:32:36   2              a number;
10:32:36   3              b varchar2(20);
10:32:36   4               c date;
10:32:36   5      begin
10:32:36   6        c := '01-JAN-2003';           -- 1
10:32:36   7                dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
10:32:36   8        c := sysdate;                 --2
10:32:36   9                dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
10:32:36  10        c := to_date('01-JAN-2003');   --3
10:32:36  11                dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
10:32:36  12        c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
10:32:36  13                 dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
10:32:36  14    end;
10:32:36  15  /
date 1:01/01/2003
date 2:03/03/2003
date 3:01/01/2003
date 4:01/01/2003

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00 

Tom Kyte
March 03, 2003 - 10:39 am UTC

ok, try this one

ops$tkyte@ORA920> alter session set nls_date_format = 'yyyy/mm/dd';

Session altered.


 

changed the nls_date format..

A reader, March 03, 2003 - 10:48 am UTC


10:32:55 SQL> alter session set nls_date_format = 'yyyy/mm/dd';

Session altered.

Elapsed: 00:00:00.00
10:45:37 SQL>    declare
10:45:43   2               a number;
10:45:43   3               b varchar2(20);
10:45:43   4                c date;
10:45:43   5       begin
10:45:43   6         c := '01-JAN-2003';           -- 1
10:45:43   7             dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
10:45:43   8         c := sysdate;                 --2
10:45:43   9             dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
10:45:43  10         c := to_date('01-JAN-2003');   --3
10:45:43  11             dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
10:45:43  12         c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
10:45:43  13              dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
10:45:43  14     end;
10:45:43  15   /
   declare
*
ERROR at line 1:
ORA-01858: a non-numeric character was found where a numeric was expected
ORA-06512: at line 6


Elapsed: 00:00:00.00
10:45:44 SQL> 


/*****************************************************/

obviously, as my sesssion date format has changed and
if i assign c := '01-JAN-03';

 it is not going to work, I have to 
      do something like this c := '03-JAN-01';
in the first case !! (because I selected to enter date in that format by changing the date format)


/*****************************************************/

Wrote file afiedt.buf

  1     declare
  2               a number;
  3               b varchar2(20);
  4                c date;
  5       begin
  6         c := '2003-01-01';           -- 1
  7                 dbms_output.put_line( what.type_am_i(c) ||' 1:'|| c);
  8         c := sysdate;                 --2
  9                 dbms_output.put_line( what.type_am_i(c) ||' 2:'|| c);
 10         c := to_date('2003-01-01');   --3
 11                 dbms_output.put_line( what.type_am_i(c) ||' 3:'|| c);
 12         c := to_date('01-JAN-2003','dd-MON-rrrr');  --4
 13                  dbms_output.put_line( what.type_am_i(c) ||' 4:'|| c);
 14*    end;
10:50:57 SQL> /
date 1:2003/01/01
date 2:2003/03/03
date 3:2003/01/01
date 4:2003/01/01

PL/SQL procedure successfully completed.
 

Tom Kyte
March 03, 2003 - 11:13 am UTC

that is why I say --


you should ALWAYS explicity convert a string to a date in my opinion.

Thanks !!

A reader, March 03, 2003 - 11:15 am UTC


jim, March 03, 2003 - 6:12 pm UTC

Analytical functions are execellent....
I'm still gettting the hang of writing them
but they save ALOT of coding..


"Then why compare a date to a string -- ALWAYS but ALWAYS compare dates to dates, strings to strings, numbers to number and NEVER NEVER store a date as a number/string or a number as a string."

I run a small mailing list answering Oracle problems
and one of the most common issues are people
using to_date on a a date, comparing dates to strings
and storing dates as strings


select a maximum date from table for update

Charlie, March 04, 2003 - 12:57 am UTC

Tom,
I have an update like,
Update emp a
set sal= 1.1*
where trunc(hire_date)=(select max(hire_date)
from emp b, dept d
where a.emp_id=b.emp_id
and b.dept_no = d.dept_no
and d.dept_no=10)

Is it possible to update this using analytic functions.
thanks


Tom Kyte
March 04, 2003 - 6:55 am UTC

sort of -- but the subquery doesn't make sense to me - emp_id, isn't that a primary key....

implicit and explicit Conversion

Marcio, November 13, 2003 - 3:19 pm UTC

you said: you should ALWAYS explicity convert a string to a date in my opinion.

Is there any performance issue about use of implicity/explicity conversion?

Tom Kyte
November 13, 2003 - 9:18 pm UTC

there are BUGS with regards (rely on implicit conversions and you have a bug)

other then that, nothing to consider here. You always want to use explicit conversions to avoid BUGS and erroneous code.

flaw in query

Daniel, November 21, 2003 - 11:14 am UTC

We can't assume a -2.5min ~ 2.5min window on current row, if the first two rows in sample data as follow:
1 100 15-SEP-2002 09:10:10
1 100 15-SEP-2002 09:15:00

I think they meet the requirement, but Tom's query will not pick them up.

Here is another approach,

select specialist_id, account_no, to_char(date_created, 'dd-MON-yyyy hh24:mi:ss') date_created
from (
select t.*,
abs(date_created -
nvl(lag(date_created)
over ( partition by specialist_id order by date_created ), date_created-1)
) interval1,
abs(date_created -
nvl(lead(date_created)
over ( partition by specialist_id order by date_created ), date_created+1)
) interval2
from interaction_log t
where date_created between to_date( '10-SEP-2002', 'dd-mon-yyyy' )
and to_date( '16-SEP-2002', 'dd-mon-yyyy' )
)
where interval1 <= 5/24/60 or interval2 <= 5/24/60
/



Question

PRS, December 04, 2003 - 10:54 am UTC

Tom,
I have a trading table named trade.
TRADE
CUST_ID
TRADE_ID
TRADE_DATE
I want to find out the average trade time for a customer. Is there any analytical function available to do this.

Thanks,
PRS

Tom Kyte
December 04, 2003 - 11:17 am UTC

Here is the "average time of creation of objects by owner" in my database.

substitute cust_id for owner, trade_date for created....

ops$tkyte@ORA920PC> select owner,
  2         avg( to_number( to_char(created,'sssss') ) ) ,
  3         to_char( to_date( round(avg( to_number( to_char(created,'sssss') ) ) ), 'sssss' ), 'hh24:mi:ss' )
  4    from all_objects
  5   group by owner
  6  /
 
OWNER                     AVG(TO_NUMBER(TO_CHAR(CREATED,'SSSSS'))) TO_CHAR(
------------------------- ---------------------------------------- --------
AQ                                                      36189.4783 10:03:09
BIG_TABLE                                               40086.6667 11:08:07
CTW                                                          26908 07:28:28
CTXSYS                                                  52019.7778 14:27:00
DEMO                                                       42547.5 11:49:08



SSSSS is the number of seconds past midnight, get the average of that, convert back to date and print it. 

question

PRS, December 04, 2003 - 11:45 am UTC

I did not understand the query totally. I have the trade_date difference in days. And you are printing it in HH24:MM:SS. Can you please clarify?
Thanks very much

Tom Kyte
December 04, 2003 - 12:17 pm UTC

now I don't understand.

i would have thought that trade_date was an oracle date datatype -- a timestamp if you will.

i took the time component (sssss) and averaged it. turned it back into a date and display the time portion (the average time of day they traded)

maybe you better explain your data better with an example


Details

PRS, December 04, 2003 - 12:41 pm UTC

Sorry for not explained properly.
Let me put the data below.
CUST_ID TRADE_ID TRADE_DATE
---------- ---------- ----------------------
1 1 8/26/2003 11:03:13 AM
1 2 9/5/2003 11:03:13 AM
1 3 9/15/2003 11:03:13 AM
1 4 9/25/2003 11:03:13 AM
1 5 10/15/2003 11:03:13 AM
1 6 5/18/2003 11:03:13 AM
1 7 7/17/2003 11:03:13 AM
1 8 6/17/2003 11:03:13 AM
1 9 3/29/2003 11:03:13 AM
2 1 8/26/2002 11:03:13 AM
2 2 9/5/2002 11:03:13 AM
2 3 9/15/2002 11:03:13 AM
2 4 9/25/2003 11:03:13 AM
2 5 10/15/2003 11:03:13 AM
3 1 8/26/2000 11:03:13 AM
3 2 9/5/2001 11:03:13 AM
3 3 9/15/2002 11:03:13 AM
3 4 9/25/2003 11:03:13 AM

So the output should be.
Cust Id Average Trade Time (Days)
1 23.32 (Just an Example)
2 100.10
3 360

I have just put the days as an example. It is not calculated for the above data.

Thanks in advance.
PRS

Tom Kyte
December 04, 2003 - 12:50 pm UTC

do you mean "the average number of days between consectutive trades by customer id"????



ops$tkyte@ORA920PC> select cust_id, avg( next_trade_date - trade_date )
  2   from (
  3  select cust_id, trade_date, lead(trade_date) over (partition by cust_id
  4  order by trade_date) next_trade_date
  5   from t
  6       )
  7  group by cust_id
  8  /
 
   CUST_ID AVG(NEXT_TRADE_DATE-TRADE_DATE)
---------- -------------------------------
         1                              25
         2                          103.75
         3                             375
 

Thanks.........

PRS, December 04, 2003 - 1:11 pm UTC

Thanks a lot. One of the best site for ORACLE questions. Really appreciate it.
PRS


Question

PRS, December 15, 2003 - 10:17 am UTC

I have a table named X and it has three columns LAST_NAME, FIRST_NAME, MIDDLE_NAME. Following is the test data.
Last Name First Name Middle Name
------------------------------------------
WHITE ROBERT R
KENNY JOHN U
I want to wirte the query in such a way that the output shown below.

Text Type
---------------------
WHITE L
KENNY L
ROBERT F
JOHN F
U M
R M

Appreciate your help.
Thanks,
PRS

Tom Kyte
December 15, 2003 - 10:40 am UTC

select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
type
from X, ( select 'L' type from dual UNION ALL
select 'F' type from dual UNION ALL
select 'M' type from dual )



Ome more question

PRS, December 15, 2003 - 10:35 am UTC

Also for the above query I need a output as shown below
Text Type
WHITE L
ROBERT F
R M
KENNY L
JOHN F
U M
Youe help is appreciated.

PRS

Tom Kyte
December 15, 2003 - 10:43 am UTC

select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
type
from (select rownum r, x.* from X), ( select 'L' type from dual UNION ALL
select 'F' type from dual UNION ALL
select 'M' type from dual )
order by r, decode( type, 'L', 1, 'F', 2, 'M' 3 )



First query no order by

PRS, December 15, 2003 - 11:15 am UTC

In the first query reply
select decode( type, 'L', last_name, 'F', first_name, 'M', middle_name ),
type
from X, ( select 'L' type from dual UNION ALL
select 'F' type from dual UNION ALL
select 'M' type from dual )

Middle Initial 'U' row does not come first. It come later. Can we just do an order by descending order for last_name,
first_name, middle_name?

Thanks,
PRS

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

add an appropriate order by. I did not know "order" was relevant (wasn't specified, in the second query is was more "obvious" since the only difference was order)

but you seem to have attached some meaning to the order of the rows in the table itself -- but there isn't any really, you seem to assume the rows will come out in order of insertion or something -- they do not, they are just random.

Date Breaks

Suleiman Akbar, December 27, 2003 - 9:17 am UTC

Tom,

I need ur help in solving this query.

looking for an output which breaks the dates. have a look at the query which is in the view. I have to call this view and have to make the date breaks.

View :

CREATE OR REPLACE VIEW EH_AF_CSTMKTFARE_VW AS
SELECT orgdst_type,
point_of_sale,
cst.point_of_sale
cst.destination,
mkp.booking_from_date,
mkp.booking_to_date,
mkp.travel_from_date,
mkp.travel_to_date,
cst.fare_id,
cst.rule_id,
mkt.market_fare_id,
mkt.rule_id market_rule_id,
mkt.booking_from_date mkt_booking_from_date,
mkt.booking_to_date mkt_booking_to_date,
mkt.travel_from_date mkt_travel_from_date,
mkt.travel_to_date mkt_travel_to_date
FROM eh_af_orgdst_cost cst, eh_af_markup mkp,
eh_af_mktfare_fare mkt
WHERE cst.fare_id = mkp.fare_id AND
cst.point_of_sale = cst.point_of_sale AND
cst.origin = mkt.origin(+) AND
cst.destination = mkt.destination(+) AND
cst.category = mkt.category(+) AND
cst.active_flag = mkt.active_flag(+) AND
( (mkp.booking_from_date BETWEEN
mkt.booking_from_date AND mkt.booking_to_date) OR
(mkp.booking_to_date BETWEEN mkt.booking_from_date
AND mkt.booking_to_date) OR
(mkt.booking_from_date BETWEEN
mkp.booking_from_date AND mkp.booking_to_date) OR
(mkt.booking_to_date BETWEEN mkp.booking_from_date
AND mkp.booking_to_date) OR
mkt.booking_from_date IS NULL
) AND
( (mkp.travel_from_date BETWEEN mkt.travel_from_date
AND mkt.travel_to_date) OR
(mkp.travel_to_date BETWEEN mkt.travel_from_date
AND mkt.travel_to_date) OR
(mkt.travel_from_date BETWEEN mkp.travel_from_date
AND mkp.travel_to_date) OR
(mkt.travel_to_date BETWEEN mkp.travel_from_date
AND mkp.travel_to_date) OR
mkt.travel_from_date IS NULL
)




WHAT iam looking for is.....

In the table EH_AF_MARKUP, will as the dates as follows:

BkgFrm BkgTo DepFrm DepTo
01/04/2003 31/03/2004 01/04/2003 31/03/2004


Corresponding entries of dates exists in EH_AF_MARKET_FARE table as

BkgFrm BkgTo DepFrm DepTo
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003


Now I want the date result as

BkgFrm BkgTo DepFrm DepTo
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003
01/04/2003 31/03/2004 01/01/2004 31/03/2004

I really appreciate if you can answer either in SQL / PLSQL ...

Thxs in advance.
Suleiman Akbar.



Tom Kyte
December 27, 2003 - 9:55 am UTC

sorry -- a tad confusing here. when I'm taking new questions, feel free to ask there but you would want to provide a tiny example and explain how you get from your inputs to your desired outputs.

unless the depfrm in the last row there in the outputs is a typo -- i don't get it.

if it is a typo -- read about "union all".

Follow-up for ur queries

Suleiman akbar, December 27, 2003 - 11:34 am UTC

Tom,

If the dates are like this in Table 1
BkgFrm BkgTo DepFrm DepTo
01/04/2003 31/03/2004 01/04/2003 31/03/2004

And few dates are like this in Table 2
BkgFrm BkgTo DepFrm DepTo
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003


Now I want the date result as

BkgFrm BkgTo DepFrm DepTo
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003
01/04/2003 31/03/2004 01/01/2004 31/03/2004
(last row of Booking From & To is taken from the table1 and Departure From is calculated as the end of the second row (departure to date) plus one (01/01/2004) and it's (3rd row) departure to will be the greatest of Table 1's or Table 2's Departure Date!

Pls. throw light with ur own example if u didn't get the above big query.

I really appreciate if you can answer either in SQL / PLSQL ...

Thxs in advance.
Suleiman Akbar.


Tom Kyte
December 27, 2003 - 11:52 am UTC

not fully baked -- don't understand 100% how you group the rows in t1 and t2 together, but I'll make the bold assumption that you group by bkgfrm (when equal, these are a "group") and you order that group by bkgto.

if so:
ops$tkyte@ORA9IR2> create table t1 ( bf date, bt date, df date, dt date );
 
Table created.
 
ops$tkyte@ORA9IR2> create table t2 ( bf date, bt date, df date, dt date );
 
Table created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> alter session set nls_date_format = 'dd/mm/yyyy';
 
Session altered.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t1 values ( '01/04/2003','31/03/2004','01/04/2003','31/03/2004' );
 
1 row created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> insert into t2 values ( '01/04/2003','30/06/2003','01/04/2003','30/06/2003' );
 
1 row created.
 
ops$tkyte@ORA9IR2> insert into t2 values ( '01/04/2003','31/12/2003','01/07/2003','31/12/2003' );
 

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select bf, bt,
  2         decode( oc, 1, df, lag(dt+1) over (partition by bf order by bf, bt, oc) ) df,
  3             dt
  4    from (
  5  select 2 oc, t1.* from t1
  6  union all
  7  select 1 oc, t2.* from t2
  8         )
  9   order by bf, bt, oc
 10  /
 
BF         BT         DF         DT
---------- ---------- ---------- ----------
01/04/2003 30/06/2003 01/04/2003 30/06/2003
01/04/2003 31/12/2003 01/07/2003 31/12/2003
01/04/2003 31/03/2004 01/01/2004 31/03/2004
 

Confusion

S.Akbar, December 27, 2003 - 12:01 pm UTC

Tom,

We are not using any Partioned Tables, I think the logic what u have enclosed will work for me. Pls. assume that it's an ordinary table without any partitions.

Got confused with "Lag". It's better if u can show the same results without using partitions.

Thxs in advance.

S.Akbar

Tom Kyte
December 27, 2003 - 12:15 pm UTC

lag is an analytic function.

the partition clause has nothing to do with physically partitioning a table.

it is just SQL (my example has not partitioned tables either!)


see the data warehousing guide for analytic functions or if you have my book "Expert one on one Oracle" i have a lengthly chapter on them. Coolest thing to happen to sql since the keyword select.

how to insert rows between empty rows and rowsdatavalues

suresh, December 28, 2003 - 12:56 am UTC

Hi tom,
In database table somebody inserting the rows into the table like this 1 to 10 rows values,next 20 to 30 rows are values,next 40 to 50 rows have values and in between 10 to 20 is empty rows(nodata), 30 to 40 empty rows.
Question :
when i insert the row into the table my first row is inserted into 11 th,next 12 th....upto 20th.
next 30 th row,next 31 st row......like this upto 40th row...

How to insert my row values into the firstempty row in the table(insert 11 th row,next 12 th row...like this?

pls reply,

Regards
suresh


Tom Kyte
December 28, 2003 - 10:54 am UTC

there is no such thing as the "N'th row in a table"


I hope you aren't thinking that data comes out in the order it went in or anything like that.

In short -- i'm not following your question at all, but I think you think data has "some order" associated with it which is 100% false.

how to retrive the empty rows only.

Suresh, December 28, 2003 - 1:19 am UTC

hi tom,
i created a sequence for one column in database table, i inserted some rows(2 rows,seq.no:1,2) into that dbtable and commited and again i inserted some rows(2 rows seq.no:3,4) and rollback these rows.
again i inserted some rows(2 rows seq.no:5,6) and commit.
to Query the dbtable(seq.no's 1,2,5,6....) rows are displayed only .

this process is going on for some rows...

Question :

How to retrive the empty rows(null rows)
(rows like seq.no : 3,4,7,8.....) ?

pls reply,

Regards,
Suresh.

Tom Kyte
December 28, 2003 - 11:03 am UTC

ahh, I see, you want to get all of the "gaps".

the most efficient is to actually just get the start/stop and let your "mind" fill in the rest.  For example:

ops$tkyte@ORA9IR2> select * from t;
 
         X
----------
         1
         2
         5
         6
         8
         9
        15
 
7 rows selected.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select x+1, next_x-1
  2    from (
  3  select x, lead(x) over (order by x) next_x
  4    from t
  5         )
  6   where x <> next_x-1
  7  /
 
       X+1   NEXT_X-1
---------- ----------
         3          4
         7          7
        10         14


shows that 3, 4, 7, 10, 11, 12, 13, 14 are "missing" in this set.

Else, you have to synthesize a result set that is "as large as the difference between the max and min values" and then join.  It could look like this:

ops$tkyte@ORA9IR2> create or replace type myTableType as table of number
  2  /
 
Type created.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> create or replace function result_set(p_iterations in number) return myTableType
  2  pipelined
  3  as
  4  begin
  5      for i in 1 .. p_iterations
  6      loop
  7          pipe row(i);
  8      end loop;
  9      return;
 10  end;
 11  /
 
Function created.
 
ops$tkyte@ORA9IR2> select * from table( result_set( (select max(x)-min(x)+1 from t)) );
 
COLUMN_VALUE
------------
           1
           2
           3
           4
           5
           6
           7
           8
           9
          10
          11
          12
          13
          14
          15
 
15 rows selected.
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select a.column_value
  2    from table( result_set( (select max(x)-min(x)+1 from t)) ) a
  3         left join t on ( a.column_value = t.x )
  4   where t.x is null
  5   order by a.column_value
  6  /
 
COLUMN_VALUE
------------
           3
           4
           7
          10
          11
          12
          13
          14
 
8 rows selected.
 

OK

Ram, December 28, 2003 - 2:30 am UTC

Dear sir,
well and wish the same from you.I tried the query shown
below
sql>select count(*) as cnt from dual
where exists(select 'x' from emp where deptno = 10);
cnt
-----
1
The query output comes as 1 not as 3.I use version 8.0.
How to correct this?
Bye!

Tom Kyte
December 28, 2003 - 11:07 am UTC

how many rows does dual have?

1

you are counting the rows in dual., not in emp or dept.

Why do you want to use dual?

Sami, December 28, 2003 - 11:05 am UTC

Can't you write
select count(*) from emp where deptno=10;
OR
select (select count(*) from emp where deptno=10) cnt from dual;



OK

Ram, December 29, 2003 - 8:17 am UTC

Dear sir,
Thanks for your response.I wanted the value obtained from
the subquery to be passed to the dual table using that
EXISTS condition.Is that possible?
Bye!

Tom Kyte
December 29, 2003 - 10:41 am UTC

nope, that is not what exists does.

How to invoke one form to another .

Suresh, January 08, 2004 - 11:01 am UTC


Hi tom,
In a database(Forms 6i) User(A) developed one form(say form1), another user(B) to developed the form(say form2) in another schema.

How User A invokes form2(this is User B Form) through form1(User A Form). How to do this ?

It is possible through Open_Form(Parameters),Call_Form(),
New_Form() ? what is the diff. of these 3 built-in's by
Calling the above scenario.(Forms in diif. Schema) ?

Pls reply,

Regards,
Suresh.

Tom Kyte
January 08, 2004 - 2:49 pm UTC

sorry -- been over 8 years, going on 9 years (in about 2 months) since I've actually used forms.

suggestion:

a) read documentation. I know these three functions are still documented.

b) see otn.oracle.com -> discussion forums after reading the documentation if you still cannot see the way.

Nice

Rosleyn, January 09, 2004 - 12:51 am UTC

Dear Tom,
I need a query in the following format.
Use emp table in scott schema
Format:
Mgr_ename Sub_ename
--------- ---------

Please do reply.
Bye!


Tom Kyte
January 09, 2004 - 8:29 am UTC

"join"

just "join" emp to emp by emp1.mgr = emp2.empno

Dynamic parameter in query

Natasha, January 13, 2004 - 10:06 pm UTC

How to dynamically select an Arithmatic Operator and join it to our sql query for these below mentioned four column (Booking & Travel dates). Assume that a table has got the structure as

Table EMP
--------
FLTNO PLS_INTEGER,
FLTNAME VARCHAR2(20),
AMOUNT NUMBER(12,2),
BOOKING_FROM_DATE DATE,
BOOKING_TO_DATE DATE,
TRAVEL_FROM_DATE DATE,
TRAVEL_TO_DATE DATE
)


And from the front-end, he passes all date parameters and their respective arithematic operators, then how to properly set the sql statments in SQL query.

Booking_From_Date =
Booking_To_Date <=
Travel_From_Date =
Travel_To_Date <=

Currently I am using a variable by putting some conditions and concatenating the desired arithematic operators (as per the parameter) and then passing it to REF cursor variable which I don't want to do it.

Appreciate, if you can make it simpler.

Tom Kyte
January 14, 2004 - 1:07 am UTC

there is ONE and only ONE way to have a dynamic predicate like that.....

Using dynamic sql..

If you don't "like" a ref cursor:


open l_cursor for some_query_youve_built;

you can use the dbms_sql package to accomplish the same, albeit with lots more code.


the ref cursor approach is simple and elegant.

Oracle 7.3.4 Gap finder

Luis Soto, January 14, 2004 - 10:49 am UTC

Do Know any way to find the gaps in Oracle 7.3.4

Thanks

Question

PRS, February 06, 2004 - 9:49 am UTC

Hi Tom,
     I have built a following view named PS_WSI_USER_BU_VW_FILL. 

CREATE OR REPLACE VIEW ps_wsi_user_bu_vw (name1,
                                          person_id,
                                          business_unit,
                                          sales_user_type,
                                          first_name,
                                          last_name,
                                          rb_work_func_id,
                                          wsi_tls_grp_name,
                                          oprid,
                                          setid,
                                          provider_grp_id,
                                          wsi_group
                                         )
AS
   SELECT b.first_name || ' ' || b.last_name, a.person_id, e.business_unit,
          e.sales_user_type, b.first_name, b.last_name, c.rb_work_func_id,
          d.wsi_tls_grp_name, a.oprid, f.setid, f.provider_grp_id, g.descr
     FROM psopralias a,
          ps_rd_person_name b,
          ps_rb_worker_func c,
          ps_wsi_rd_prsn_fld d,
          ps_rsf_suser e,
          ps_rf_grp_member f,
          ps_rb_wrk_func_tbl g,
          ps_rb_worker h
    WHERE b.person_id = a.opraliasvalue(+)
      AND a.opraliastype = 'PER'
      AND b.primary_ind = 'Y'
      AND b.person_id = c.person_id(+)
      AND b.person_id = d.person_id
      AND b.person_id = e.person_id
      AND b.person_id = f.person_id(+)
      AND c.rb_work_func_id = g.rb_work_func_id
      AND b.person_id = h.person_id
/

   All tables are analyzed by percent 35. Indexes are analyzed by 100%. It is using cost based optimizer.

   I have following query which comes back in 1 second. It used the right indexes.
SQL> l
  1  SELECT
  2  fill.name1, fill.person_id, fill.business_unit, fill.sales_user_type,
  3         fill.first_name, fill.last_name, fill.rb_work_func_id,
  4         fill.wsi_tls_grp_name, fill.oprid, fill.setid, fill.provider_grp_id,
  5         fill.wsi_group
  6    FROM ps_wsi_user_bu_vw fill
  7   WHERE oprid = 'James Mayfield'
  8*     OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
SQL> /

Execution Plan
----------------------------------------------------------                      
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=745 Card=2 Bytes=348          
          )                                                                     
                                                                                
   1    0   CONCATENATION                                                       
   2    1     NESTED LOOPS (Cost=12 Card=1 Bytes=174)                           
   3    2       NESTED LOOPS (Cost=11 Card=1 Bytes=144)                         
   4    3         NESTED LOOPS (OUTER) (Cost=9 Card=1 Bytes=132)                
   5    4           FILTER                                                      
   6    5             NESTED LOOPS (OUTER)                                      
   7    6               NESTED LOOPS (Cost=6 Card=1 Bytes=81)                   
   8    7                 NESTED LOOPS (Cost=8 Card=1 Bytes=113)                
   9    8                   NESTED LOOPS (Cost=5 Card=1 Bytes=63)               
  10    9                     TABLE ACCESS (FULL) OF 'PSOPRALIAS' (Cost=2 Card=1 Bytes=37)                                                                                                                                  
  11    9                     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RD_ PERSON_NAME' (Cost=3 Card=1 Bytes=26)                                                                                                                 
  12   11                       INDEX (RANGE SCAN) OF 'PSDRD_PERSON_NAME' (NON-UNIQUE) (Cost=2 Card=1)                                      
  13    8                   INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)                                                                  
  14    7                 TABLE ACCESS (BY INDEX ROWID) OF 'PS_RSF_SUSER' (Cost=1 Card=1 Bytes=18)                                          
  15   14                   INDEX (UNIQUE SCAN) OF 'PS_RSF_SUSER' (UNIQUE)                                                                  
  16    6               TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER' (Cost=2 Card=1 Bytes=22)                                        
  17   16                 INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER' (NON-UNIQUE) (Cost=1 Card=1)                                             
  18    4           INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE)(Cost=1 Card=1 Bytes=19)                                             
  19    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2 Card=1 Bytes=12)                                            
  20   19           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1 Card=1)                                                    
  21    2       TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL'(Cost=1 Card=1 Bytes=30)                                              
  22   21         INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)          
  23    1     NESTED LOOPS (Cost=12 Card=1 Bytes=174)                           
  24   23       NESTED LOOPS (Cost=11 Card=1 Bytes=144)                         
  25   24         NESTED LOOPS (OUTER) (Cost=9 Card=1 Bytes=132)                
  26   25           NESTED LOOPS (Cost=8 Card=1 Bytes=113)                      
  27   26             FILTER                                                    
  28   27               NESTED LOOPS (OUTER)                                    
  29   28                 NESTED LOOPS (Cost=6 Card=1 Bytes=81)                 
  30   29                   NESTED LOOPS (Cost=5 Card=1 Bytes=63)               
  31   30                     TABLE ACCESS (BY INDEX ROWID) OF 'PSOPRALIAS' (Cost=2 Card=1 Bytes=37)                                        
  32   31                       INDEX (UNIQUE SCAN) OF 'PS_PSOPRALIAS' (UNIQUE) (Cost=1 Card=4526)                                          
  33   30                     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RD_PERSON_NAME' (Cost=3 Card=1 Bytes=26)                                 
  34   33                       INDEX (RANGE SCAN) OF 'PSDRD_PERSON_NAME' (NON-UNIQUE) (Cost=2 Card=1)                                                                                                                      
  35   29                   TABLE ACCESS (BY INDEX ROWID) OF 'PS_RSF_SUSER' (Cost=1 Card=1 Bytes=18)                                        
  36   35                     INDEX (UNIQUE SCAN) OF 'PS_RSF_SUSER' (UNIQUE)                                                                
  37   28                 TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER' (Cost=2 Card=1 Bytes=22)                                      
  38   37                   INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER' (NON-UNIQUE) (Cost=1 Card=1)                                           
  39   26             INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)            
  40   25           INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE)(Cost=1 Card=1 Bytes=19)                                             
  41   24         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2 Card=1 Bytes=12)                                            
  42   41           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1 Card=1)                                                    
  43   23       TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL' (Cost=1 Card=1 Bytes=30)                                              
  44   43         INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)          
SQL> spool off

Now I have following query in which I add extra where clause with OR condition and it takes more than 2 minutes. It changes the
entire explain plan. It does not use the index on PS_RD_PERSON_NAME which has more than 3million rows.

SQL> SELECT
  2         fill.name1, fill.person_id, fill.business_unit, fill.sales_user_type,
  3         fill.first_name, fill.last_name, fill.rb_work_func_id,
  4         fill.wsi_tls_grp_name, fill.oprid, fill.setid, fill.provider_grp_id,
  5         fill.wsi_group
  6    FROM ps_wsi_user_bu_vw fill
  7   WHERE oprid = 'James Mayfield'
  8      OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
  9      OR provider_grp_id = '100841'
 10  /

Execution Plan
----------------------------------------------------------                      
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6947 Card=1 Bytes=174)                                                                    
   1    0   NESTED LOOPS (Cost=6947 Card=1 Bytes=174)                           
   2    1     NESTED LOOPS (OUTER) (Cost=6946 Card=1 Bytes=144)                 
   3    2       NESTED LOOPS (Cost=6945 Card=1 Bytes=125)                       
   4    3         NESTED LOOPS (Cost=6943 Card=1 Bytes=113)                     
   5    4           NESTED LOOPS (OUTER) (Cost=6925 Card=9 Bytes=684)           
   6    5             HASH JOIN (Cost=6907 Card=9 Bytes=486)                    
   7    6               TABLE ACCESS (FULL) OF 'PS_RSF_SUSER' (Cost=7 Card=3881 Bytes=69858)                                                
   8    6               NESTED LOOPS (Cost=6898 Card=7962 Bytes=286632)                                                                                                                                                     
   9    8                 TABLE ACCESS (FULL) OF 'PS_RD_PERSON_NAME' ( Cost=6898 Card=3387373 Bytes=88071698)                                
  10    8                 INDEX (UNIQUE SCAN) OF 'PS_RB_WORKER' (UNIQUE)                                                                    
  11    5             TABLE ACCESS (BY INDEX ROWID) OF 'PS_RF_GRP_MEMBER' (Cost=2 Card=1 Bytes=22)                                          
  12   11               INDEX (RANGE SCAN) OF 'PSARF_GRP_MEMBER' (NON-UNIQUE) (Cost=1 Card=1)                                               
  13    4           TABLE ACCESS (BY INDEX ROWID) OF 'PSOPRALIAS' (Cost=2 Card=1 Bytes=37)                                                  
  14   13             INDEX (RANGE SCAN) OF 'PSBPSOPRALIAS' (NON-UNIQUE) (Cost=1 Card=1)                                                    
  15    3         TABLE ACCESS (BY INDEX ROWID) OF 'PS_WSI_RD_PRSN_FLD' (Cost=2 Card=1 Bytes=12)                                            
  16   15           INDEX (UNIQUE SCAN) OF 'PS_WSI_RD_PRSN_FLD' (UNIQUE) (Cost=1 Card=1)                                                    
  17    2       INDEX (RANGE SCAN) OF 'PS_RB_WORKER_FUNC' (UNIQUE) (Cost=1 Card=1 Bytes=19)                                                 
  18    1     TABLE ACCESS (BY INDEX ROWID) OF 'PS_RB_WRK_FUNC_TBL' (Cost=1 Card=1 Bytes=30)                                                
  19   18       INDEX (UNIQUE SCAN) OF 'PS_RB_WRK_FUNC_TBL' (UNIQUE)            

SQL> spool off

Can you please show how to resolve this 2nd query? It is just optimized is unsing a different explain plan.

Thanks for your help.

PRS 

Tom Kyte
February 07, 2004 - 1:13 pm UTC

quick comment:

WHERE b.person_id = a.opraliasvalue(+)
AND a.opraliastype = 'PER'

hmmm, if a.* is "made up", a.* will all be null and a.opraliastype = 'PER' will never be true (or false :). Hence, the (+) on A is not only "not relevant", it only stops the optimizer from considering other plans...

Anytime you see an outer join, ask yourself "why, do i really need it, and if so, why do i really really need it". In this case, you obviously do not need it!

Hmm, another one:

AND b.person_id = c.person_id(+)
...
AND c.rb_work_func_id = g.rb_work_func_id

if c.* is made up, c.rb_work_func_id will be null and that'll never be equal to g.rb_work_func_id so -- again, lose the outer join!


Lets start there first.


Changed

PRS, February 09, 2004 - 9:24 am UTC

Hi Tom,
I totally agree with your suggestion on outer join. I removed all outer join and recreated the view as shown below.
create or replace view ps_wsi_user_bu_vw as
SELECT b.first_name || ' ' || b.last_name name1, a.person_id, e.business_unit,
e.sales_user_type, b.first_name, b.last_name, c.rb_work_func_id,
d.wsi_tls_grp_name, a.oprid, f.setid, f.provider_grp_id, g.descr wsi_group
FROM psopralias a,
ps_rd_person_name b,
ps_rb_worker_func c,
ps_wsi_rd_prsn_fld d,
ps_rsf_suser e,
ps_rf_grp_member f,
ps_rb_wrk_func_tbl g,
ps_rb_worker h
WHERE b.person_id = a.opraliasvalue
AND a.opraliastype = 'PER'
AND b.primary_ind = 'Y'
AND b.person_id = c.person_id
AND b.person_id = d.person_id
AND b.person_id = e.person_id
AND b.person_id = f.person_id
AND c.rb_work_func_id = g.rb_work_func_id
AND b.person_id = h.person_id

But the explain plan remains identical for the query 2. There is no change in the plan.

I do not understand why optimizer is doing this way. Can you help?

Thanks,
PRS

Tom Kyte
February 09, 2004 - 9:49 am UTC

if you look at the two predicates, they are very very very very different. Might not seem so to you -- but they are:

7 WHERE oprid = 'James Mayfield'
8 OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
10 /

versus

7 WHERE oprid = 'James Mayfield'
8 OR (oprid LIKE '%Branch %' AND provider_grp_id = '100841')
9 OR provider_grp_id = '100841'
10 /


now, you do understand "lots of tables", "no idea of what indexes are where", "big query" makes this a bit hard


but what about just union all'ing


where oprid = 'xxxxx'

with

where provider_grp_id = '100841'


as the other predicate piece is "already covered" and just confusing the issue.


Sorry

PRS, February 09, 2004 - 9:54 am UTC

Hi Tom,
I wrongly stated my answer in the previous reply. Sorry for that.

But When I removed all outer joins 2nd query works fine.
But I cannot remove an outer join for following condition.
AND b.person_id = f.person_id(+)
As long as this outer join is there it is doing a full table scan on PS_RD_PERSON_NAME(3+ million rows) and plan remains almost identical and query takes 2 minutes. So I do not know what's going on.

Is their any Standard pll or package available to generate web reports in Forms??

rajkumar, April 01, 2004 - 1:18 am UTC

hi tom,

Is their any standard PLL or Package available in
Forms 4.5/5.0/6i(Developer 2000) to generate the
excel-report in web by capturing the form values at runtime ?
eg : i run dept-Form based on dept table, (on button click) at runtime to capture all the form values(deptno-10,dname-Accounts,loc-India) including lables(deptno,dname,loc) and these values to be displayed in IE-browser in excel-format(grids)in the web? how to achieve this scenario ?

Any Information related to above is their any Standard PLL available ? Pls posted immd..

Thanks & Regards,
rajkumar.


Tom Kyte
April 01, 2004 - 10:03 am UTC

don't know -- good question for the forms discussion forums on otn.oracle.com

I haven't touched forms since march, 1995 myself (when this web thing came into being...)

How to make rows as columns ??

rajkumar, April 07, 2004 - 12:32 am UTC


hi tom,

How to make rows as columns ? ie, suppose i have a Questions table(Q.No - Primary key) like:
Q.No(PK) Question
1 Q1
2 Q2
..........

and MultiChoices table(Q.No - Foreign key) like :

Q.No(FK) Choices
1 C1
1 C2
1 C3
1 C4
2 C1
2 C2
2 C3
2 C4
.............

based on these two tables to generate one more table Ques_Choices like :

Q.No Choice1 Choice2 Choice3 Choice4
1 C1 C2 C3 C4
2 C1 C2 C3 C4
...........................................

How to generate this table(Write the Query)? Pls Posted Immd.. any information related to this ..make rows as Columns and Columns as rows ????

Thanks & Regards,
rajkumar.



Tom Kyte
April 07, 2004 - 9:07 am UTC

I think I answer this one about 3 times a day.



search this site for

PIVOT

Question

PRS, August 26, 2004 - 12:48 pm UTC

I want to retrieve the data from a table where each person id amount is not nullified. (i.e There is no matching
negative value for the given positive value)

I have following data
create table t1 (person_id number(10),date_created date,amount number(5,2))

insert into t1 values(1,sysdate-365,1);
insert into t1 values(1,sysdate-365,-1);
insert into t1 values(1,sysdate-300,3.25);
insert into t1 values(1,sysdate-365,-3.25);
insert into t1 values(1,sysdate-200,1);
insert into t1 values(1,sysdate-200,1.25);
insert into t1 values(2,sysdate-365,1);
insert into t1 values(2,sysdate-365,-1);
insert into t1 values(2,sysdate-300,2);
insert into t1 values(2,sysdate-300,-2);
insert into t1 values(2,sysdate-200,3);
insert into t1 values(2,sysdate-200,-3);
insert into t1 values(3,sysdate-365,1);
insert into t1 values(3,sysdate-365,-1);
insert into t1 values(3,sysdate-300,1.25);
insert into t1 values(3,sysdate-300,-1.25);
insert into t1 values(3,sysdate-200,2.23);
insert into t1 values(3,sysdate-200,1);
commit

Output should be
Person ID Date Created Amount
1 2/8/2004 1
1 2/8/2004 1.25
3 2/8/2004 2.23
3 2/8/2004 1

Thanks,
PRS


Tom Kyte
August 26, 2004 - 3:17 pm UTC

why pick the 2/8/2004 for person id = 1, why not 27-aug-2003?

seems rather "arbitrary"

but here we go -- I'll build this one.  

<b>First, we assign row_number after partitioning the data by person_id and amount -- ordering by date (tend to keep the "last date")

this will take person_id = 1, amount = 1 and assign the numbers 1, 2, 3, ... to them -- sequence all of the "1"s for that person and do the same for -1 and so on:
</b>

ops$tkyte@ORA9IR2> select *
  2    from (
  3  select person_id, date_created, amount,
  4         row_number() over (partition by person_id, amount order by date_created) rn
  5    from t1
  6         )
  7   order by person_id, abs(amount), rn
  8  /
 
 PERSON_ID DATE_CREA     AMOUNT         RN
---------- --------- ---------- ----------
         1 27-AUG-03         -1          1
         1 27-AUG-03          1          1
         1 08-FEB-04          1          2
         1 08-FEB-04       1.25          1
         1 27-AUG-03      -3.25          1
         1 31-OCT-03       3.25          1
         2 27-AUG-03         -1          1
         2 27-AUG-03          1          1
         2 31-OCT-03         -2          1
         2 31-OCT-03          2          1
         2 08-FEB-04         -3          1
         2 08-FEB-04          3          1
         3 27-AUG-03         -1          1
         3 27-AUG-03          1          1
         3 08-FEB-04          1          2
         3 31-OCT-03      -1.25          1
         3 31-OCT-03       1.25          1
         3 08-FEB-04       2.23          1
 
18 rows selected.

<b>Now we have that.  We partition by person_id and the ABS(AMOUNT) -- treating 1 and -1 as the "same" -- and that RN.  So, it groups the first 1/-1 together (person_id = 1, abs(amount)=1 and rn=1 will either be a singleton or a pair..</b>

 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select person_id, date_created, amount, rn,
  2         lag(amount) over (partition by person_id, abs(amount), rn order by amount) last_amt
  3    from (
  4  select person_id, date_created, amount,
  5         row_number() over (partition by person_id, amount order by date_created) rn
  6    from t1
  7         )
  8  /
 
 PERSON_ID DATE_CREA     AMOUNT         RN   LAST_AMT
---------- --------- ---------- ---------- ----------
         1 27-AUG-03         -1          1
         1 27-AUG-03          1          1         -1
         1 08-FEB-04          1          2
         1 08-FEB-04       1.25          1
         1 27-AUG-03      -3.25          1
         1 31-OCT-03       3.25          1      -3.25
         2 27-AUG-03         -1          1
         2 27-AUG-03          1          1         -1
         2 31-OCT-03         -2          1
         2 31-OCT-03          2          1         -2
         2 08-FEB-04         -3          1
         2 08-FEB-04          3          1         -3
         3 27-AUG-03         -1          1
         3 27-AUG-03          1          1         -1
         3 08-FEB-04          1          2
         3 31-OCT-03      -1.25          1
         3 31-OCT-03       1.25          1      -1.25
         3 08-FEB-04       2.23          1
 
18 rows selected.

<b>now, if we filter out just amount>0 after pairing:</b>
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from (
  3  select person_id, date_created, amount, rn,
  4         lag(amount) over (partition by person_id, abs(amount), rn order by amount) last_amt
  5    from (
  6  select person_id, date_created, amount,
  7         row_number() over (partition by person_id, amount order by date_created) rn
  8    from t1
  9         )
 10         )
 11   where amount > 0
 12  /
 
 PERSON_ID DATE_CREA     AMOUNT         RN   LAST_AMT
---------- --------- ---------- ---------- ----------
         1 27-AUG-03          1          1         -1
         1 08-FEB-04          1          2
         1 08-FEB-04       1.25          1
         1 31-OCT-03       3.25          1      -3.25
         2 27-AUG-03          1          1         -1
         2 31-OCT-03          2          1         -2
         2 08-FEB-04          3          1         -3
         3 27-AUG-03          1          1         -1
         3 08-FEB-04          1          2
         3 31-OCT-03       1.25          1      -1.25
         3 08-FEB-04       2.23          1
 
11 rows selected.
 
<b>we are almost there.... just keep "last_amount is NULL" and we got it I think</b>

ops$tkyte@ORA9IR2> select *
  2    from (
  3  select person_id, date_created, amount, rn,
  4         lag(amount) over (partition by person_id, abs(amount), rn order by amount) last_amt
  5    from (
  6  select person_id, date_created, amount,
  7         row_number() over (partition by person_id, amount order by date_created) rn
  8    from t1
  9         )
 10         )
 11   where amount > 0
 12     and last_amt is null
 13  /
 
 PERSON_ID DATE_CREA     AMOUNT         RN   LAST_AMT
---------- --------- ---------- ---------- ----------
         1 08-FEB-04          1          2
         1 08-FEB-04       1.25          1
         3 08-FEB-04          1          2
         3 08-FEB-04       2.23          1
 

Another one using Outer Join

Logan Palanisamy, August 26, 2004 - 8:10 pm UTC

Tom,

Here is another solution using Outer Join. Performance wise, how will it compare with your analytics solution

SQL> l
  1  select * from (
  2  select a.*, b.amount bamount
  3  from (select * from t1 where amount > 0) a
  4  full outer join
  5  (select * from t1 where amount < 0) b
  6  on a.person_id = b.person_id
  7  and trunc(a.date_created) = trunc(b.date_created)
  8  and a.amount = -b.amount)
  9  where bamount is null
 10* order by person_id
SQL> /

 PERSON_ID DATE_CREA     AMOUNT    BAMOUNT
---------- --------- ---------- ----------
         1 08-FEB-04          1
         1 08-FEB-04       1.25
         3 08-FEB-04       2.23
         3 08-FEB-04          1
 

Tom Kyte
August 26, 2004 - 8:31 pm UTC

your's makes an assumption mine did not, that the date_created would be the same.

Another shot with Full Outer Join with no date constraint

Logan Palanisamy, August 27, 2004 - 1:16 am UTC

Tom,

How does it compare, now that I have removed the date restriction?

SQL> select person_id, date_created, amount from
  2  (select p.*, n.amount namount
  3  from
  4  (select person_id, amount,  date_created,
  5  row_number() over (partition by person_id, amount order by date_created) rn from t1 where amount > 0 ) p
  6  full outer join
  7  (select person_id, amount,  date_created,
  8  row_number() over (partition by person_id, amount order by date_created) rn from t1 where amount < 0 ) n
  9  on p.person_id = n.person_id
 10  and p.rn = n.rn
 11  and p.amount = -n.amount)
 12  where namount is null
 13  order by person_id, amount;

 PERSON_ID DATE_CREA     AMOUNT
---------- --------- ----------
         1 08-FEB-04          1
         1 08-FEB-04       1.25
         3 08-FEB-04          1
         3 08-FEB-04       2.23 

Tom Kyte
August 27, 2004 - 7:41 am UTC

benchmark it and let us know?

query in matrix format

friend, August 27, 2004 - 8:47 am UTC

hai,

i want a query o/p as shown in this format

street-a street-b street-c
a b c a b c a b c


quantity 10 20 30 40 99 34 12 34 56


is this format possible with sql query ???????

thanks in advance


Tom Kyte
August 27, 2004 - 8:55 am UTC

virtually anything is possible...

but without data, nothing is probable.

you see, we have no idea what "a", "b", "c" are or what your table looks like...

create table ....
insert into....
insert into.....

Your approach wins hands down

Logan Palanisamy, August 27, 2004 - 2:12 pm UTC

Tom,

Your approach wins like it does most of the time. Here is the test.

SQL> select *
  2  from (
  3  select person_id, date_created, amount, rn,
  4  lag(amount) over (partition by person_id, abs(amount), rn order by
  5  amount) last_amt
  6  from (
  7  select person_id, date_created, amount,
  8  row_number() over (partition by person_id, amount order by
  9  date_created) rn
 10  from t1
 11  )
 12  )
 13  where amount > 0
 14  and last_amt is null;

 PERSON_ID DATE_CREA     AMOUNT         RN   LAST_AMT
---------- --------- ---------- ---------- ----------
         1 08-FEB-04          1          2
         1 08-FEB-04       1.25          1
         3 08-FEB-04          1          2
         3 08-FEB-04       2.23          1


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE
   1    0   VIEW
   2    1     WINDOW (SORT)
   3    2       VIEW
   4    3         WINDOW (SORT)
   5    4           TABLE ACCESS (FULL) OF 'T1'




Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        582  bytes sent via SQL*Net to client
        423  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
          4  rows processed

SQL> 
SQL> select person_id,
  2  date_created,
  3  amount from
  4  (select p.*,
  5  n.person_id nperson_id, n.date_created ndate_created, n.amount namount
  6  from
  7  (select person_id, amount,  date_created,
  8  row_number() over (partition by person_id, amount order by date_created) rn
  9  from t1 where amount > 0 ) p
 10  full outer join
 11  (select person_id, amount,  date_created,
 12  row_number() over (partition by person_id, amount order by date_created) rn
 13  from t1 where amount < 0 ) n
 14  on p.person_id = n.person_id
 15  and p.amount = -n.amount
 16  and p.rn = n.rn)
 17  where namount is null
 18  order by person_id, amount;

 PERSON_ID DATE_CREA     AMOUNT
---------- --------- ----------
         1 08-FEB-04          1
         1 08-FEB-04       1.25
         3 08-FEB-04          1
         3 08-FEB-04       2.23


Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=45 Card=5 Bytes=240)
   1    0   SORT (ORDER BY) (Cost=45 Card=5 Bytes=240)
   2    1     VIEW (Cost=34 Card=5 Bytes=240)
   3    2       UNION-ALL
   4    3         FILTER
   5    4           HASH JOIN (OUTER)
   6    5             VIEW (Cost=15 Card=4 Bytes=192)
   7    6               WINDOW (SORT) (Cost=15 Card=4 Bytes=140)
   8    7                 TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=4 B
          ytes=140)

   9    5             VIEW (Cost=15 Card=4 Bytes=156)
  10    9               WINDOW (SORT) (Cost=15 Card=4 Bytes=140)
  11   10                 TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=4 B
          ytes=140)

  12    3         FILTER
  13   12           TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=1 Bytes=3
          5)

  14   12           FILTER
  15   14             TABLE ACCESS (FULL) OF 'T1' (Cost=2 Card=1 Bytes
          =35)





Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          9  consistent gets
          0  physical reads
          0  redo size
        444  bytes sent via SQL*Net to client
        423  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          3  sorts (memory)
          0  sorts (disk)
          4  rows processed

SQL>  

Strange behavior of Full Outer Joins when using inline views

Logan Palanisamy, August 27, 2004 - 3:20 pm UTC

Tom,

I thought in Full Outer Joins, the order of the tables in the FROM clause don't matter. For example

select t1.*, t2.*
from t1 full outer join t2
on t1.c1 = t2.c1

is the same as 

select t1.*, t2.*
from t2 full outer join t1
on t1.c1 = t2.c1

But it seems to be true only if t1 and t2 are tables, not inline views.

Here is the scenario:
SQL> -- Outer Join Query 1. (With Inline Views)
SQL> select p.*, n.*
  2  from
  3  (select person_id, amount,  date_created,
  4  row_number() over (partition by person_id, amount order by date_created) rn
  5  from t1 where amount > 0 ) p
  6  full outer join
  7  (select person_id, amount,  date_created,
  8  row_number() over (partition by person_id, amount order by date_created) rn
  9  from t1 where amount < 0 ) n
 10  on p.person_id = n.person_id
 11  and p.amount = -n.amount
 12  and p.rn = n.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        3       2.23 08-FEB-04   1
        3          1 08-FEB-04   2
                                           1         -1 27-AUG-03
                                           1      -3.25 27-AUG-03
                                           2         -1 27-AUG-03
                                           2         -2 31-OCT-03
                                           2         -3 08-FEB-04
                                           3         -1 27-AUG-03
                                           3      -1.25 31-OCT-03

18 rows selected.

SQL> 
SQL> -- Outer Join Query 2. (With Inline Views. Same as 1 except n and p are just reversed)
SQL> select p.*, n.*
  2  from
  3  (select person_id, amount,  date_created,
  4  row_number() over (partition by person_id, amount order by date_created) rn
  5  from t1 where amount < 0 ) n
  6  full outer join
  7  (select person_id, amount,  date_created,
  8  row_number() over (partition by person_id, amount order by date_created) rn
  9  from t1 where amount > 0 ) p
 10  on p.person_id = n.person_id
 11  and p.amount = -n.amount
 12  and p.rn = n.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        1          1 27-AUG-03
        1       3.25 27-AUG-03
        1          1 08-FEB-04
        1       1.25 08-FEB-04
        2          1 27-AUG-03
        2          2 31-OCT-03
        2          3 08-FEB-04
        3          1 27-AUG-03
        3       1.25 31-OCT-03
        3       2.23 08-FEB-04
        3          1 08-FEB-04

18 rows selected.

SQL> 
SQL> -- Tables corresponding to the inline views
SQL> drop table p;

Table dropped.

SQL> create table p as
  2  select person_id, amount,  date_created,
  3  row_number() over (partition by person_id, amount order by date_created) rn
  4  from t1 where amount > 0 ;

Table created.

SQL> 
SQL> drop table n;

Table dropped.

SQL> create table n as
  2  select person_id, amount,  date_created,
  3  row_number() over (partition by person_id, amount order by date_created) rn
  4  from t1 where amount < 0;

Table created.

SQL> 
SQL> select * from p order by person_id, amount;

PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- ---
        1          1 27-AUG-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        1       3.25 27-AUG-03   1
        2          1 27-AUG-03   1
        2          2 31-OCT-03   1
        2          3 08-FEB-04   1
        3          1 27-AUG-03   1
        3          1 08-FEB-04   2
        3       1.25 31-OCT-03   1
        3       2.23 08-FEB-04   1

11 rows selected.

SQL> select * from n order by person_id, amount;

PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- ---
        1      -3.25 27-AUG-03   1
        1         -1 27-AUG-03   1
        2         -3 08-FEB-04   1
        2         -2 31-OCT-03   1
        2         -1 27-AUG-03   1
        3      -1.25 31-OCT-03   1
        3         -1 27-AUG-03   1

7 rows selected.

SQL> 
SQL> -- Outer Join Query 3. (With equivalent tables)
SQL> select p.*, n.*
  2  from p full outer join n
  3  on p.person_id = n.person_id
  4  and p.amount = -n.amount
  5  and p.rn = n.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        3       2.23 08-FEB-04   1
        3          1 08-FEB-04   2

11 rows selected.

SQL> 
SQL> -- Outer Join Query 4. (With equivalent tables. Same as 3 except n and p are just reversed)
SQL> select p.*, n.*
  2  from n full outer join p
  3  on p.person_id = n.person_id
  4  and p.amount = -n.amount
  5  and p.rn = n.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        3       2.23 08-FEB-04   1
        3          1 08-FEB-04   2

11 rows selected.

SQL> 
Notes:
Q1, Q2 use inline views
Q3, Q4 use equivalent tables
Q2 is the same as Q1 except for the reversal in the FROM clause.
Q3 is the same as Q1 except that Q3 uses equivalent tables
Q4 is the same as Q3 except for the reversal in the FROM clause.

Observations:
Output of Q1 and Q2 are different
Output of Q3 and Q1 are different
Output of Q4 is the same as Q3.

Questions:
Shouldn't the output of all the four queries be exactly the same?
Why are the outputs of Q1 and Q2 different?
Why are the outputs of Q3 and Q1 different?
Is it because of the query merge when using inline views?
Is there a hint or something which tells the optimizer not to merge?

Just curious.  

Tom Kyte
August 27, 2004 - 3:33 pm UTC

yes, that would be "a product issue" apparently.  


ops$tkyte@ORA9IR2> select * from t1;
 
 PERSON_ID DATE_CREA     AMOUNT
---------- --------- ----------
         1 28-AUG-03          1
         1 28-AUG-03         -1
 
ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select p.*, n.*
  2  from
  3  (select person_id, amount,  date_created,
  4  row_number() over (partition by person_id, amount order by date_created) rn
  5  from t1 where amount > 0 ) p
  6  full outer join
  7  (select person_id, amount,  date_created,
  8  row_number() over (partition by person_id, amount order by date_created) rn
  9  from t1 where amount < 0 ) n
 10  on p.person_id = n.person_id
 11  and p.amount = -n.amount
 12  and p.rn = n.rn;
 
 PERSON_ID     AMOUNT DATE_CREA         RN  PERSON_ID     AMOUNT DATE_CREA         RN
---------- ---------- --------- ---------- ---------- ---------- --------- ----------
         1          1 28-AUG-03          1          1         -1 28-AUG-03          1
                                                    1         -1 28-AUG-03


<b>is sufficient to show the problem.</b>

I'll file a bug.... wait, turns out I already did.

workaround I found then was to use WITH subquery factoring and it works.   

one more debug test case

Gabe, August 27, 2004 - 4:19 pm UTC

Seems to have nothing to do with the analytics ... just the full outer join with inline views (well, maybe something else as well!!) ...

flip@flop> select p.*, n.*
2 from ( select rownum x,person_id, amount, date_created
3 from t1
4 where amount > 0
5 ) p
6 full outer join
7 ( select rownum x,person_id, amount, date_created
8 from t1
9 where amount < 0
10 ) n
11 on p.person_id = n.person_id
12 and p.amount = -n.amount
13 ;
ERROR:
ORA-03113: end-of-file on communication channel

It works when both rownums (not just one) are taken out of the inline views.

SQL*Plus: Release 9.2.0.1.0
Oracle9i Enterprise Edition Release 9.2.0.4.0

WITH Sub-Query-Factoring workaround for Full Outer Join

Logan Palanisamy, August 27, 2004 - 4:31 pm UTC

Tom,

The "With Sub-Query-Factoring" workaround does indeed work in the place of inline views for Full Outer Joins.

SQL> WITH p1 as (select person_id, amount,  date_created,
  2  row_number() over (partition by person_id, amount order by date_created) rn
  3  from t1 where amount > 0 ),
  4  n1 as (select person_id, amount,  date_created,
  5  row_number() over (partition by person_id, amount order by date_created) rn
  6  from t1 where amount < 0 )
  7  select p1.*, n1.*
  8  from p1 full outer join n1
  9  on p1.person_id = n1.person_id
 10  and p1.amount = -n1.amount
 11  and p1.rn = n1.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        3       2.23 08-FEB-04   1
        3          1 08-FEB-04   2

11 rows selected.

SQL> 
SQL> WITH p1 as (select person_id, amount,  date_created,
  2  row_number() over (partition by person_id, amount order by date_created) rn
  3  from t1 where amount > 0 ),
  4  n1 as (select person_id, amount,  date_created,
  5  row_number() over (partition by person_id, amount order by date_created) rn
  6  from t1 where amount < 0 )
  7  select p1.*, n1.*
  8  from n1 full outer join p1
  9  on p1.person_id = n1.person_id
 10  and p1.amount = -n1.amount
 11  and p1.rn = n1.rn;

PERSON_ID     AMOUNT DATE_CREA  RN PERSON_ID     AMOUNT DATE_CREA  RN
--------- ---------- --------- --- --------- ---------- --------- ---
        1          1 27-AUG-03   1         1         -1 27-AUG-03   1
        1       3.25 27-AUG-03   1         1      -3.25 27-AUG-03   1
        2          1 27-AUG-03   1         2         -1 27-AUG-03   1
        2          2 31-OCT-03   1         2         -2 31-OCT-03   1
        2          3 08-FEB-04   1         2         -3 08-FEB-04   1
        3          1 27-AUG-03   1         3         -1 27-AUG-03   1
        3       1.25 31-OCT-03   1         3      -1.25 31-OCT-03   1
        1          1 08-FEB-04   2
        1       1.25 08-FEB-04   1
        3       2.23 08-FEB-04   1
        3          1 08-FEB-04   2

11 rows selected. 

OK

Raju, August 29, 2004 - 11:06 am UTC

Hello Tom,

Are there any other ways that the following query can be put?

SQL> select * from emp where deptno = 10 and job in (select job from emp where deptno = (select
  2  deptno from dept where dname = upper('sales')) )
  3  /

 

Tom Kyte
August 29, 2004 - 12:01 pm UTC

yes, lots. try some....

OK

Raju, August 30, 2004 - 1:36 am UTC

Dear Tom,
I find it difficult to arrive it.Could you please provide
some ways?
Bye!


Tom Kyte
August 30, 2004 - 8:33 am UTC

looks like homework, since if this was "work", you'd be done -- you have the answer.


hints for you to research:

o "where exists"
o inline views and joins.

row_number()

A reader, October 10, 2004 - 9:57 pm UTC

Welcome back!

I need to order a set of rows by 5 different criteria, some ASC, some DESC and get the first row for each pk.

row_number() is a perfect fit here.

select * from (
select pk,row_number() over (partition by pk order by c1,c2 desc,c3,c4,c5 desc) rn from Q
) where rn=1;

Question:

What kind of optimization does the CBO do here because of the "rn=1" predicate?

Does it fully materialize the query Q above before assigning the row_numbers() or does it make use of the fact that I need only the first row in each partition to reduce the work?

Thanks

Tom Kyte
October 11, 2004 - 7:36 am UTC

it will basically materialize the inner query and then just keep the first row from each partition.


partition by "pk" looks "strange" -- in fact -- that query would be the same as

select distinct pk, 1 rn from q;


as coded -- so there should be more to it?

How to execute the Job on week-day's only.....????

raja, November 23, 2004 - 1:52 pm UTC

Hi tom,
1. Using triggers, the Job is executed only on
monday to friday, not execute on saturday
and sunday,
where to write this condition in trigger's....??
the job is executed only "Mon to Fri"
and how to do this ....??

2. If the Select Query takes more time to run...
How to do the Query tunning...what are the step's
to follow ???

3. what is fine grain policy future....?

4. when u r using Index-organized tables (related
to Index's ?) at what king of scenario's ??

5. how to call the C-Programs in pl/sql ?

Tx,
raja.

Tom Kyte
November 23, 2004 - 2:50 pm UTC

use an interval of

case when to_char(sysdate+1,'DY') in ('SAT','SUN')
then sysdate+3
else sysdate+1
end


or whatever you need to do to sysdate to get it to be the next "time"


2) understand all of the tools available to you -- the sql language in full, the functions (analytics etc), have some amount of experience, understand how and why you index. I guess I would start with the concepts guide (i always do....)

I have another suggestion -- I have a book for you -- "Expert One on One Oracle" -- i cover these topics (although -- read the concepts manual, freely available on otn.oracle.com -- it too covers these basic features of the database)

How to use rownum in place of row_number()

Stalin, January 11, 2005 - 8:27 pm UTC

Hi Tom,

The query i wrote to pull the latest 1000 log entries for each account works great with smaller dataset but runs for hours on a 35million rows table.

here goes the query :

SELECT log_id, creation_date, account_id, ....
FROM (SELECT l.*, row_number() OVER (PARTITION BY l.account_id ORDER BY l.creation_date DESC) rn
FROM logs l) x
WHERE x.rn <= 1000

plan output :

----------------------------------------------------------------------------
| Id | Operation | Name |Rows | Bytes |TempSpc| Cost |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 35M| 260G| | 2902K|
|* 1 | VIEW | | 35M| 260G| | 2902K|
|* 2 | WINDOW SORT PUSHED RANK| | 35M| 4473M| 12G| 2902K|
| 3 | TABLE ACCESS FULL | LOGS | 35M| 4473M| | 115K|
----------------------------------------------------------------------------
Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
----------------------------------------------------

1 - filter("X"."RN"<=1000)
2 - filter(ROW_NUMBER() OVER ( PARTITION BY "L"."ACCOUNT_ID" ORDER BY
"L"."CREATION_DATE" DESC )<=1000)

It estimates 12G temp space for sorting and 38 hrs to complete from longops. I was wondering how to rewrite this query to use the advantage of rownum where it stops sorting once it reaches 1k rows.

Thanks,
Stalin

Tom Kyte
January 12, 2005 - 8:36 am UTC

you need the latest 1,000 log entries BY ACCOUNT.

you cannot use rownum for that (it is doing the pushed rank there -- it is stopping).

but if you have lots of accounts, thisis a huge result set -- do you see it is estimating that it'll get 35m rows as output there?



what is your sort sizes here, 35mill rows on a machine today shouldn't take very long at all. I know this is not exactly the same as your example (you give us no real idea the number of accounts and the average rows/account which would be pretty important) but -- this is one a rather small (as compared to a server) desktop pc:


big_table@ORA9IR2> delete from plan_table;

4 rows deleted.

big_table@ORA9IR2> explain plan for
2 select *
3 from ( select t.*, row_number() over (partition by owner order by created) rn
4 from big_table t
5 )
6 where rn <= 1000
7 /

Explained.

big_table@ORA9IR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 35M| 5140M| | 935K|
|* 1 | VIEW | | 35M| 5140M| | 935K|
|* 2 | WINDOW SORT PUSHED RANK| | 35M| 3337M| 8965M| 935K|
| 3 | TABLE ACCESS FULL | BIG_TABLE | 35M| 3337M| | 76995 |
--------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_001"."RN"<=1000)
2 - filter(ROW_NUMBER() OVER ( PARTITION BY "T"."OWNER" ORDER BY
"T"."CREATED")<=1000)

Note: cpu costing is off

18 rows selected.

big_table@ORA9IR2>
big_table@ORA9IR2> set timing on
big_table@ORA9IR2> set autotrace traceonly
big_table@ORA9IR2> select *
2 from ( select t.*, row_number() over (partition by owner order by created) rn
3 from big_table t
4 )
5 where rn <= 1000
6 /

22000 rows selected.

Elapsed: 00:04:44.37

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=935319 Card=35000000 Bytes=5390000000)
1 0 VIEW (Cost=935319 Card=35000000 Bytes=5390000000)
2 1 WINDOW (SORT PUSHED RANK) (Cost=935319 Card=35000000 Bytes=3500000000)
3 2 TABLE ACCESS (FULL) OF 'BIG_TABLE' (Cost=76995 Card=35000000 Bytes=3500000000)




Statistics
----------------------------------------------------------
0 recursive calls
471 db block gets
507329 consistent gets
588968 physical reads
720 redo size
886470 bytes sent via SQL*Net to client
16625 bytes received via SQL*Net from client
1468 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
22000 rows processed


big_table is just a copy of all_objects over and over and over to get to 35m rows. Seems it is a bit skinnier than your table (and the tmpspc guess -- is just that, a high end guess).


but -- it was only a couple of minutes.


sooo, what is your workarea size policy, pga aggregate target, sort area size set to.





Stalin, January 12, 2005 - 3:37 pm UTC

workarea_policy was set to auto, and PGA was set to 50m. however, after increasing PGA to 250m, i could get the same query to run in ~26mins with 8G of tempspace.

SELECT log_id, creation_date, account_id, ....
FROM (SELECT l.*, row_number() OVER (PARTITION BY l.account_id ORDER BY l.creation_date DESC) rn
FROM logs l) x
WHERE x.rn <= 1000
/

3606781 rows selected.

Elapsed: 00:25:48.16

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1246958 Card=3580870
0 Bytes=279773373100)

1 0 VIEW (Cost=1246958 Card=35808700 Bytes=279773373100)
2 1 WINDOW (SORT PUSHED RANK) (Cost=1246958 Card=35808700 By
tes=4690939700)

3 2 TABLE ACCESS (FULL) OF 'LOGS' (Cost
=115964 Card=35808700 Bytes=4690939700)

Statistics
----------------------------------------------------------
0 recursive calls
2788 db block gets
773627 consistent gets
1986398 physical reads
0 redo size
304203179 bytes sent via SQL*Net to client
2645475 bytes received via SQL*Net from client
240454 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
3606781 rows processed

Tom Kyte
January 12, 2005 - 4:05 pm UTC

If this is a batch query you have to do from time to time during off time (non-peak), you could consider

a) alter session to set the workarea policy to manual
b) alter session to up your sort area size to something really nice and big (you don't have to share the memory if this is off peak, just use it all)



Stalin, January 12, 2005 - 4:56 pm UTC

Thanks tom.

If i'd to get the latest 1,000 log entries BY ACCOUNT within last 30days, i should be able to get off by using range preceding right. I hope that makes a huge difference.

SELECT log_id, creation_date, account_id, ....
FROM (SELECT l.*, row_number() OVER (PARTITION BY l.account_id ORDER BY l.creation_date DESC range 30 preceding) rn
FROM logs l) x
WHERE x.rn <= 1000

Tom Kyte
January 12, 2005 - 6:10 pm UTC

that is not semantically equivalent -- if it even worked :)

not sure what "row_number" would mean with a range clause, probably that is why it only does partition and order by....


you'd be "whereing"


select ...
from ( select l.*, row_number() over (partition by accound_id order by creation_date desc) rn
from logs
WHERE CREATION_DATE >= sysdate-30 )
where rn <= 1000;



Stalin, January 12, 2005 - 8:01 pm UTC

yes, you are right. range 30 preceding within partition group didn't work.

sorry, i should have said latest 1,000 log entries BY ACCOUNT within last 30days for each account. That's why i was trying to put range 30 within account partition group thinking that it would just list me latest 1000 logs for each account and filter out the latest 1000 logs from outer where rn <= 1000. unfortunately oracle doesn't like that syntax.

to keep it simple we need to retain the latest 1000 logs or 30 day older logs, whichever comes first for each accout.

Thanks so much.

Tom Kyte
January 13, 2005 - 8:06 am UTC

"latest 1000 logs" -- understand that
"30 day older logs" -- don't get that.


If you need to retrieve upto 1000 log entries for each account, given that the entry was made in the last 30 days, the where clause above does that.

Query

PRS, April 07, 2005 - 10:48 am UTC

Hi Tom,
Please Look at the following data and query.
select count(*) count,title,reason
from
(select lzc.activity_date term_date,to_char(lzc.activity_date,'mm') term_month,
to_char(lzc.activity_date,'yyyy') term_year,
ee.employ_date hire_dt,(lzc.activity_date - ee.employ_date) days_worked,
f.employee_name,j.turnover_group title,r.rev_cyborg_desc reason
from tmp_cyb_lzc lzc,tmp_cyb_lzr lzr,tmp_cyb_lzd lzd,
wrktbl_cyb_ee ee,rev_ox_turnover_group j,wrktbl_cyb_f f,REV_SKR_CYB_CODES r
where lzc.full_employee_no = lzr.full_employee_no
and lzc.full_employee_no = lzd.full_employee_no
and lzc.full_employee_no = ee.full_employee_no
and lzc.full_employee_no = f.full_employee_no
and lzc.resulting_emp_stat in ('11','12','13','14','15','16','17','18','19')
and lzr.ctrl_three = '0048'
and to_char(lzc.activity_date,'yyyy') = '2005'
and lzc.effdt <= sysdate
and lzd.effdt <= sysdate
and lzr.effdt <= sysdate
and trim(j.jobid) = lzd.job_code
and r.rev_table_name = 'ACTIVITY_CODES'
and r.rev_cyborg_cd = lzc.activity_code)
group by title,reason


count title reason
1 Mechanics Term-Personal Reasns
2 Assemblers TERM FOR CAUSE
1 Assemblers Term-Attnd/Punctual
1 Assemblers Term-Job Abandonment
3 Assemblers Term-No Ret From LOA
1 Processors TERMED OTHER
1 Processors Term-Attnd/Punctual
2 Processors Term-No Ret From LOA
1 Processors Term-Personal Reasns
1 Miscellaneous RSGN-1ST SHIFT JOB
3 Miscellaneous Term-Attnd/Punctual
1 Miscellaneous Term-Job Abandonment
1 Warehouse Techs Term-Attnd/Punctual
2 Warehouse Techs Term-Job Abandonment
1 Warehouse Techs Term-No Ret From LOA
1 Machine Operators Term-Relocation
1 Machine Operators RSGN-1ST SHIFT JOB
5 Machine Operators TERM-FLD TRL PERIOD
3 Machine Operators Term-Attnd/Punctual
1 Machine Operators TERM-NO-RET-FROM-FML
5 Machine Operators Term-Job Abandonment
6 Machine Operators Term-Job Opportunity
2 Machine Operators Term-No Ret From LOA
2 Machine Operators Term-Personal Reasns
1 Pic Packers/Order Pickers Term-Job Opportunity


I want data like this


Reason Assemblers Processors

TERM FOR CAUSE 2 0

Reason is a database column. But Assembler,processors,etc.. are the column values of database column named title.
How can I do this?

Thanks,
PRS

Tom Kyte
April 07, 2005 - 11:40 am UTC

take your query, call it Q

select reason,
max( decode( title, 'Mechanics', count ) ) "Mechanics",
max( decode( title, 'Assemblers', count ) ) "Assemblers",
.....
max( decode( title, 'Processors', count ) ) "Processors"
from (Q)
group by reason;


use nvl( max(....), 0 ) if you like.


sql query

AD, April 13, 2005 - 5:11 pm UTC

Hi Tom,

First create table script etc.
 
create table xxxx
 (acc_no number,
  x   number,
  year_month number(6))
/

insert into xxxx values (111, 1, 200410);
insert into xxxx values (111, 2, 200411);
insert into xxxx values (111, 3, 200412);
insert into xxxx values (111, 15, 200501);
insert into xxxx values (111, 6, 200502);
insert into xxxx values (111, 7, 200503);
insert into xxxx values (111, 8, 200504);
insert into xxxx values (111, 9, 200505);
insert into xxxx vALUES (222, 10, 200501);
insert into xxxx vALUES (222, 11, 200502);
insert into xxxx vALUES (222, 12, 200503);
insert into xxxx vALUES (222, 13, 200504);
insert into xxxx vALUES (222, 14, 200505);

I would like to get the following o/p 

acc_no max_x_lst_3_mnths max_x_lst_6_mnths max_x_lst_9_mnths
====== ================= ================= ================
111           9                  15            15
222          14                14            14

The purpose is to get the max value of x for each acc_no  within the last three months, six months, nine months etc.
e.g. to get the max value of x by acc_no within the last three months, the result would be something like the following. 

  1  select acc_no,year_month,  max(x)  over (partition by acc_no) from xxxx
  2* where year_month >=200502
SQL> /

   ACC_NO YEAR_MONTH MAX(X)OVER(PARTITION BY ACC_NO)
---------- ---------- ------------------------------
       111     200502                              9
       111     200503                              9
       111     200504                              9
       111     200505                              9
       222     200502                             14
       222     200503                             14
       222     200504                             14
       222     200505                             14

Thanks for your time,
 

Tom Kyte
April 13, 2005 - 5:19 pm UTC

ugh, why why why do people do that to dates.  oh well, gotta convert it back, hope they are all valid.

ops$tkyte@ORA9IR2> select acc_no,
  2         max( case when
  3          months_between(sysdate,to_date(to_char(year_month),'yyyymm'))<=3  4             then x end ) max_3,
  5         max( case when
  6          months_between(sysdate,to_date(to_char(year_month),'yyyymm'))<=6  7             then x end ) max_6,
  8         max( case when
  9          months_between(sysdate,to_date(to_char(year_month),'yyyymm'))<=9 10             then x end ) max_9
 11    from xxxx
 12   group by acc_no;
 
    ACC_NO      MAX_3      MAX_6      MAX_9
---------- ---------- ---------- ----------
       111          9         15         15
       222         14         14         14
 

RE: original answer

Vinayak, April 15, 2005 - 4:33 pm UTC

This is regarding your answer to the original question.

Lets say I've this data:

vmahajan@ECADEV> SELECT * FROM t1 ORDER BY c1 ;

C1 C2
---------- ------------------
1 01-JAN-05 15:00:00
1 01-JAN-05 15:04:00
1 01-JAN-05 15:08:00
2 01-JAN-05 15:00:00
2 01-JAN-05 15:02:00
3 01-JAN-05 00:00:00
4 10-JAN-05 16:00:00
4 10-JAN-05 16:10:00

If we use the -2.5 minutes to +2.5 minute "windowing", we will get the following result set:

vmahajan@ECADEV> select * from (
2 select t1.*,
3 count(*)
4 over (partition by c1
5 order by c2
6 range between 1/24/60*2.5 preceding and 1/24/60*2.5 following) dups
7 from t1 )
8* where dups>1
vmahajan@ECADEV> /

C1 C2 DUPS
---------- ------------------ ----------
2 01-JAN-05 15:00:00 2
2 01-JAN-05 15:02:00 2

Which is not correct. Although for column c1=1, we have a fifference of 4 minutes (less than 5 minutes), we are still not picking that one.

I think we need to use LAG/LEAD functions to appropriately do it. something like:

1 SELECT c1,c2 FROM
2 (SELECT t1.*,
3 LAG(c2) OVER (PARTITION BY c1 ORDER BY c2) prev,
4 LEAD(c2) OVER (PARTITION BY c1 ORDER BY c2) nxt
5 FROM t1)
6* WHERE (c2-prev)*24*60 < 5 OR (nxt-c2)*24*60 < 5
vmahajan@ECADEV> /

C1 C2
---------- ------------------
1 01-JAN-05 15:00:00
1 01-JAN-05 15:04:00
1 01-JAN-05 15:08:00
2 01-JAN-05 15:00:00
2 01-JAN-05 15:02:00

Maybe you should edit your answer to correct this.

*) I agree completely with you that the ANALYTIC functions rock. I've used these recently and tested their performance versus writing queries the old way and they are VERY FAST.


Tom Kyte
April 15, 2005 - 6:20 pm UTC

if +- 2.5 minutes wasn't right to you, why wouldn't +- 5 minutes by the next answer?

You could read it as a 5 or 2.5 minute surrounding window, either or, it would be more obvious I would think to expand to a 5 minute window.

lag/lead won't work, what about when you have 3 observations in the same window.

Duplicate on one column but different on other column

Tracy, April 20, 2005 - 8:28 am UTC

My table looks like this:

USERNAME CARD DATECREAT T
---------- ---------- --------- -
User 1 Card 1 20-APR-05 P
User 2 Card 2 20-APR-05 L
User 3 Card 3 20-APR-05 L
User 4 Card 2 20-APR-05 X
User 1 Card 10 20-APR-05 X
User 1 Card 11 20-APR-05 X
User 3 Card 2 20-APR-05 L
User 3 Card 3 20-APR-05 L
User 3 Card 3 20-APR-05 L

In my query I want to return all columns from the table where the card is the same as the card in another row but the user is different.
So given the above data in the table I want to return:

User 2 Card 2 20-APR-05 L
User 4 Card 2 20-APR-05 X
User 3 Card 2 20-APR-05 L

Is this a job for analytics - I can't get it to work using 'simple' sql?

Tom Kyte
April 20, 2005 - 8:34 am UTC

no creates
no inserts
no testing by me, but


select *
from (
select a.*, count(distinct username) over (partition by card) cnt
from t a
)
where cnt > 1
/

is probably it.

No creates, no inserts, no testing !!?

Tracy, April 20, 2005 - 9:23 am UTC

Doesn't matter. Your sql works perfectly, thanks.

Expert one on one

Jorge, April 20, 2005 - 10:05 am UTC

Hi Tom!

Has been "Expert one to one" translated into other languages? (for instance Spanish).

Thanks a lot, you are a Genius.


Tom Kyte
April 20, 2005 - 8:53 pm UTC

yes, lots - russian, korean, philippine, among others -- "spain" spanish is one I believe it was done in.

Confused with rownum

A reader, April 20, 2005 - 4:19 pm UTC

>I got two queries as mentioned below.
>
>1. SELECT BchCtl_Bus_YMD,
> BchCtl_Mrg_Sts,
> BchCtl_Mrg_YMDHMS,
> BchCtl_IRN,
>
>TRUNC(BchCtl_Run_YMDHMS)
> INTO ValDte,
> MrgSts,
> MrgDte,
> BchCtlIRN,
> BchRunYMD
> FROM BchCtl_Tbl A
> WHERE BchCtl_IRN =
> (SELECT bchctl_irn
> FROM (SELECT
>MAX(BchCtl_IRN) bchctl_irn,
>
>BchCtl_Bus_YMD
> FROM
>BchCtl_Tbl bc
> WHERE
>bc.BchCtl_End_Sts = 'S' AND
>
>(BchCtl_Cod = 'ENX' || ExtBmkPvder.ENS_Cod OR
>
>(BchCtl_Cod = 'ENX' AND
>
>BchCtl_Job = ExtBmkPvder.ENS_Cod)) AND
>
>bc.BchCtl_Bus_YMD <= ActDte(PriActDt + 1)
> GROUP BY
>BchCtl_Bus_YMD
> ORDER BY
>BchCtl_Bus_YMD DESC)
> WHERE Rownum = 1);
>
>
>
>
>2. SELECT BchCtl_Bus_YMD,
> BchCtl_Mrg_Sts,
> BchCtl_Mrg_YMDHMS,
> BchCtl_IRN,
> TRUNC(
>BchCtl_Run_YMDHMS )
> INTO ValDte,
> MrgSts,
> MrgDte,
> BchCtlIRN,
> BchRunYMD
> FROM BchCtl_Tbl A
> WHERE BchCtl_IRN = ( SELECT
>MAX( BchCtl_IRN )
> FROM
>BchCtl_Tbl
> WHERE
>BchCtl_End_Sts = 'S'
> AND
>( ( BchCtl_Cod = 'ENX' || ExtBmkPvder.ENs_Cod )
>
>OR ( BchCtl_Cod = 'ENX'
>
>AND BchCtl_Job = ExtBmkPvder.ENS_Cod ) )
> AND
>BchCtl_Bus_YMD = ( SELECT MAX( BchCtl_Bus_YMD )
>
> FROM BchCtl_Tbl
>
> WHERE BchCtl_End_Sts = 'S'
>
> AND ( ( BchCtl_Cod = 'ENX' ||
>ExtBmkPvder.ENs_Cod )
>
> OR ( BchCtl_Cod = 'ENX'
>
> AND BchCtl_Job =
>ExtBmkPvder.ENS_Cod ) )
>
> AND BchCtl_Bus_YMD <= ActDte(
>PriActDt + 1 ) ) );
>
>
>I got the following questins.
>1. Are query 1 and 2 going to return the same results.
>2. Is Rownum always going to return the same rownum
> when I have order by in my subquery/sub_Table.
>3.
>a. select * from (select max(sal) from emp group by
>deptno order by deptno desc) where rownum=1
>
>b. select max(sal) from emp where deptno in (select
>max(deptno) from emp);
>
>Are a. and b. same. Is the result of a. predicatble.


Date Functions

sasanka, April 28, 2005 - 6:07 am UTC

Hi Tom,

is there any way we can find that " what are the dates fall on sunday in a particular month"

For example, In the current month April what are the dates comes on sunday?
------------------
the ouput should look like

3-APR-2005 SUNDAY
10-APR-2005 SUNDAY
17-APR-2005 SUNDAY
24-APR-2005 SUNDAY



Thanks,
sasanka.

Tom Kyte
April 28, 2005 - 8:27 am UTC

  1  select dt
  2  from (
  3  select next_day( to_date( 'apr-2005', 'mon-yyyy' )-1, 'SUN' )+l*7 dt
  4  from (select level-1 l from dual connect by level <= 5 )
  5  )
  6* where to_char(dt,'mon-yyyy')='apr-2005'
ops$tkyte@ORA9IR2> /
 
DT
---------
03-APR-05
10-APR-05
17-APR-05
24-APR-05

works in 9i and above.  'apr-2005' would of course be a bind variable. 

OK

Raju, April 28, 2005 - 1:55 pm UTC

Hi Tom,
Some times in queries we use

SQL> select * from dual where -1=-1

what is the purpose of using that "where clause" ?
Is there any documentation for that??

Please do reply.
Bye!
 

Tom Kyte
April 28, 2005 - 2:07 pm UTC

nope.

How to do sum ?

Parag Jayant Patankar, May 04, 2005 - 5:30 am UTC

Hi,

I am trying to write following SQL statement

select a.deptno, a.ename, a.sal, sum(b.sal)
from emp a, emp b
where a.deptno = b.deptno
and   a.sal >= b.sal
group by a.deptno, a.ename, a.sal
order by 1, 3

14:27:16 SQL> /

    DEPTNO ENAME             SAL SUM(B.SAL)
---------- ---------- ---------- ----------
        10 KING            -2000      -2000
        10 MILLER           1300       -700
        10 CLARK            2450       1750
        20 SMITH             800        800
        20 ADAMS            1100       1900
        20 JONES            2975       4875
        20 FORD             3000      10875
        20 SCOTT            3000      10875
        30 MARTIN           1250       2500
        30 WARD             1250       2500
        30 TURNER           1500       4000
        30 ALLEN            1600       5600
        30 BLAKE            2850       8450

13 rows selected.

This query is working fine for deptno, sal wise output.

But when I try to get sum of sal deptno, ename wise I am not getting correct result. I know this can be easily achived thru analytical function. Can you tell me what should be "where condition" for such SQL ?

I am asking you this question for my understanding of writing such type of SQLs. 

regards & thanks
pjp 

Tom Kyte
May 04, 2005 - 9:14 am UTC

do you just want to do a running total?

scott@ORA10G> select deptno, ename, sal, sum(sal) over (partition by deptno order by sal, empno ) running_total
2 from emp;

DEPTNO ENAME SAL RUNNING_TOTAL
---------- ---------- ---------- -------------
10 MILLER 1300 1300
10 CLARK 2450 3750
10 KING 5000 8750

20 SMITH 800 800
20 ADAMS 1100 1900
20 JONES 2975 4875
20 SCOTT 3000 7875
20 FORD 3000 10875

30 JAMES 950 950
30 WARD 1250 2200
30 MARTIN 1250 3450
30 TURNER 1500 4950
30 ALLEN 1600 6550
30 BLAKE 2850 9400

14 rows selected.



running total

parag jayant patankar, May 04, 2005 - 9:40 am UTC

Hi Tom,

Sorry my question was not precise. I want running total departmentwise order by deptno and ename not using by analytical function.

select a.deptno, a.ename, a.sal, sum(b.sal)
from emp1 a, emp1 b
where a.deptno = b.deptno
and a.sal >= b.sal
group by a.deptno, a.ename, a.sal
order by 1, 3
/

I want your help to modify this query to give running total department no, ename wise total.

regards & thanks
pjp

Tom Kyte
May 04, 2005 - 10:14 am UTC

why? I mean it would be totally and wholly inefficient to do this?

What is the technical reason we cannot do it with analytics?

running total

parag jayant patankar, May 04, 2005 - 10:32 am UTC

Hi Tom,

There is no reason which is preventing us from writing analytical function for running total. I agree with you, if I use analytical function then it is already better for perfomance.

It is just for my educational purpose how to write sql without using analytical function for running total.


regards & thanks
pjp

Tom Kyte
May 04, 2005 - 10:58 am UTC

do you have expert one on one Oracle?

I actually used this example in the analytics chapter :)


A quick example and an explanation of what exactly is happening will get us jump started here:

tkyte@TKYTE816> break on deptno skip 1
tkyte@TKYTE816> SELECT ename, deptno, sal,
2 SUM(sal) OVER
3 (ORDER BY deptno, ename) running_total,
4 SUM(sal) OVER
5 (PARTITION BY deptno
6 ORDER BY ename) department_total,
7 row_number() OVER
8 (PARTITION BY deptno
9 ORDER BY ename ) seq
10 FROM emp
11 order by deptno, ename
12 /

ENAME DEPTNO SAL RUNNING_TOTAL DEPARTMENT_TOTAL SEQ
---------- ---------- ---------- ------------- ---------------- ----------
CLARK 10 2450 2450 2450 1
KING 5000 7450 7450 2
MILLER 1300 8750 8750 3

ADAMS 20 1100 9850 1100 1
FORD 3000 12850 4100 2
JONES 2975 15825 7075 3
SCOTT 3000 18825 10075 4
SMITH 800 19625 10875 5

ALLEN 30 1600 21225 1600 1
BLAKE 2850 24075 4450 2
JAMES 950 25025 5400 3
MARTIN 1250 26275 6650 4
TURNER 1500 27775 8150 5
WARD 1250 29025 9400 6


14 rows selected.

So, as you can see - we were able to compute a RUNNING_TOTAL for the entire query. We did this by using the entire ordered result set via "SUM(SAL) OVER (ORDER BY DEPTNO, ENAME)". We were also able to compute a running total within each department - a total that would be reset at the beginning of the next department. The PARTITION BY DEPTNO in that SUM(SAL) caused that to happen, we specified a partitioning clause in the query to break the data up into groups. Just to see the data with a sequence number within each partition - we also added in a SEQ column. It shows the position of the row within a partition - the same partition we used to compute the department total. That lets me see that SCOTT was the "4th" row in department 20 when ordered by ENAME. This ROW_NUMBER() feature will have many uses elsewhere as we will see later - not just to display the fact that SCOTT is the 4th row - but to transpose or pivot result sets.

As you can see - if you are a pretty good SQL query developer now - this new set of functionality holds some pretty exciting possibilities. It opens up a whole new way of looking at the data. It will remove tons of procedural code and complex/inefficient queries you would have had to develop yourself to achieve the same result. Just to give you a flavor of how efficient this can be over the old "pure relational ways" we can see how the above query might work on 1,000 rows instead of just 14 rows performance wise. We'll test using both the new analytical functions and the "old" relational methods. The table I used is an EMP look alike:

tkyte@TKYTE816> create table t
2 as
3 select object_name ename,
4 mod(object_id,50) deptno,
5 object_id sal
6 from all_objects
7 where rownum <= 1000
8 /
Table created.

tkyte@TKYTE816> create index t_idx on t(deptno,ename);
Index created.

Those two statements setup our EMP like table with just the ENAME, DEPTNO and SAL columns. We created the only index really needed for this example - we are doing everything by DEPTNO and ENAME. We'll run the query from above using this new table using AUTOTRACE in trace only mode - just to see how much work is done:

tkyte@TKYTE816> set autotrace traceonly

tkyte@TKYTE816> SELECT ename, deptno, sal,
2 SUM(sal) OVER
3 (ORDER BY deptno, ename) running_total,
4 SUM(sal) OVER
5 (PARTITION BY deptno
6 ORDER BY ename) department_total,
7 row_number() OVER
8 (PARTITION BY deptno
9 ORDER BY ename ) seq
10 FROM T emp
11 order by deptno, ename
12 /
1000 rows selected.

Elapsed: 00:00:00.61

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 WINDOW (BUFFER)
2 1 TABLE ACCESS (BY INDEX ROWID) OF 'T'
3 2 INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
0 recursive calls
2 db block gets
292 consistent gets
66 physical reads
0 redo size
106978 bytes sent via SQL*Net to client
7750 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
1 sorts (disk)
1000 rows processed

So, that took 0.61 seconds and 294 logical IOS. Now, we'll do the same exact thing using only "standard" SQL functionality:

tkyte@TKYTE816> select ename, deptno, sal,
2 (select sum(sal)
3 from t e2
4 where e2.deptno < emp.deptno
5 or (e2.deptno = emp.deptno and e2.ename <= emp.ename )
6 ) running_total,
7 ( select sum(sal)
8 from t e3
9 where e3.deptno = emp.deptno
10 and e3.ename <= emp.ename
11 ) department_total,
12 ( select count(ename)
13 from t e3
14 where e3.deptno = emp.deptno
15 and e3.ename <= emp.ename
16 ) seq
17 from t emp
18 order by deptno, ename
19 /
1000 rows selected.

Elapsed: 00:00:06.89

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T'
2 1 INDEX (FULL SCAN) OF 'T_IDX' (NON-UNIQUE)

Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
665490 consistent gets
0 physical reads
0 redo size
106978 bytes sent via SQL*Net to client
7750 bytes received via SQL*Net from client
68 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1000 rows processed

tkyte@TKYTE816> set autotrace off

We get the same exact answer from both - but what a difference these functions can make. The run time is many times longer and the number of logical IOS is up by many orders of magnitude. The analytical functions processed the result set using significantly fewer resources and an astoundingly reduced amount of time. Not only that but once you understand the syntax of the analytic functions, you will find them easier to code with then the equivalent standard SQL - just compare the syntax of the above two queries to see what a difference they can make.


Calendar in SQL

Parag J Patankar, May 11, 2005 - 11:06 am UTC

Hi Tom,

Thanks for your great answer to me in this thread. Is it possible to generate calendar like unix

@mumsa128200: /f205/prod > cal 5 2005
May 2005
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31

thru SQL ?

regards & thanks
pjp

OK

Raj, July 14, 2005 - 1:52 pm UTC

Hi Tom,
Is there any predefined order to use the logical
operators namely AND,OR and NOT.
Whether NOT must be followed by AND or OR?
or any other fixed order???

Tom Kyte
July 14, 2005 - 1:59 pm UTC

only your logic can dictate where NOT belongs?

you can NOT an "OR"
you can NOT an "AND"
you can "AND" a "NOT NOT" with a "NOT"

your logical will tell you what you must do to get the right answer.

To find out greatest across columns

a reader, July 14, 2005 - 5:25 pm UTC

Hi Tom,


Could you help with the following:


create table v (c1 number , c2 number , c3 number);

Table created.

insert into v values (10, null, 12);
insert into v values (20, 15, 90);
insert into v values (30, null, null);

SQL> select * from v;

        C1         C2         C3
---------- ---------- ----------
        10                    12
        20         15         90
        30

I am trying to get an o/p like the below.


SQL>  select c1, greatest(c2, c3) from v

desired o/p

        C1 GREATEST(C2,C3)
---------- -----------------------------
        10                            12
        20                            90
        30                             

The following works in a way but then if both (c1, C2) are null, then I expect the result to return null instead of a specific value
SQL>  select c1, greatest(nvl(c2, 0), nvl(c3, 0) ) from v
  2  /

        C1 GREATEST(NVL(C2,0),NVL(C3,0))
---------- -----------------------------
        10                            12
        20                            90
        30                             0



Your help is very much appreciated.

Thanks 

Tom Kyte
July 15, 2005 - 7:27 am UTC

if you have more than 2 columns:

ops$tkyte-ORA9IR2> select c1,
  2         greatest( nvl(c2,coalesce(c2,c3)),
  3                   nvl(c3,coalesce(c2,c3)) ) g
  4    from t;
 
        C1          G
---------- ----------
        10         12
        20         90
        30
 
if you have just two columns:

ops$tkyte-ORA9IR2>
ops$tkyte-ORA9IR2> select c1,
  2         greatest( nvl(c2,c3),
  3                   nvl(c3,c2) ) g
  4    from t;
 
        C1          G
---------- ----------
        10         12
        20         90
        30
 
 

Small correction to the first solution

Peter Kinsella, July 15, 2005 - 7:48 am UTC

partition should be by specialist_id and account_no, not just specialist_id:

select *
from (
.....
----> (partition by specialist_id, account_no <-----
.....
where dups > 1

A reader, July 21, 2005 - 9:27 am UTC

Dear Tom
Please help me to write this query.

create table ptest
(id number(2) ,
cd number(2),
pid number(2),
cid number(2))
/

insert into ptest values(1,1,1,0)
/
insert into ptest values(1,1,2,1)
/
insert into ptest values(1,1,3,1)
/
insert into ptest values(1,1,4,0)
/
insert into ptest values(1,1,30,2)
/
insert into ptest values(1,1,70,3)
/
insert into ptest values(1,1,40,4)
/
insert into ptest values(1,1,46,40)
/
insert into ptest values(1,1,60,30)
/
insert into ptest values(1,1,72,70)
/


select * from ptest order by pid;

ID CD PID CID
---------- ---------- ---------- ----------
1 1 1 0
1 1 2 1
1 1 3 1
1 1 4 0
1 1 30 2
1 1 40 4
1 1 46 40
1 1 60 30
1 1 70 3
1 1 72 70

For eg: If I have the PID value 72, fetch the next row
with PID = previous row's CID value thats 70 and so on until CID =0

ie, how can I get an output like this?

ID CD PID CID
---------- ---------- ---------- ----------
1 1 1 0
1 1 3 1
1 1 70 3
1 1 72 70

Thanking you in advance
Raj

Tom Kyte
July 21, 2005 - 4:14 pm UTC

ops$tkyte@ORA9IR2>
ops$tkyte@ORA9IR2> select *
  2    from ptest
  3   start with pid = 72
  4  connect by prior cid = pid and prior cid <> 0
  5  order by pid
  6  /
 
        ID         CD        PID        CID
---------- ---------- ---------- ----------
         1          1          1          0
         1          1          3          1
         1          1         70          3
         1          1         72         70
 

Nice

Raju, July 31, 2005 - 10:42 pm UTC

Hi Tom,
Is there any limit in the number of columns that can be used in ORDER BY clause???

Tom Kyte
August 01, 2005 - 7:26 am UTC

none documented, 

ops$tkyte@ORA9IR2> drop table t;
 
Table dropped.
 
ops$tkyte@ORA9IR2> variable x refcursor
ops$tkyte@ORA9IR2> declare
  2          l_sql long;
  3  begin
  4          l_sql := 'create table t ( c0001 char(2000) default ''x''';
  5          for i in 2 .. 1000
  6          loop
  7                  l_sql := l_sql || ',c'||to_char(i,'fm0000') || ' char(2000) default ''x''';
  8          end loop;
  9          l_sql := l_sql || ')';
 10          execute immediate l_sql;
 11
 12          execute immediate 'insert into t (c0001) values (''x'')';
 13
 14          l_sql := 'select * from t order by c0001';
 15          for i in 2 .. 1000
 16          loop
 17                  l_sql := l_sql || ',c'||to_char(i,'fm0000');
 18          end loop;
 19          open :x for l_sql;
 20  end;
 21  /
 
PL/SQL procedure successfully completed.
 

at least 1,000 

quick question

Joe, August 08, 2005 - 11:48 am UTC

If a table contains following numbers in a column
1..24, 26, 29 and 30 ( where 1..24 is all int numbers between 1 and 24 inluding both 1 and 24)

Now given a number, lets say 27, I want to return all numbers from that table which are less than or equal to next higher number of 27. If 27 is already present in the table then return all numbers which are less than or equal to 27.
So when we send in 27, I should retrieve 1..24,26 and 29.
When I send in 29, I should retrieve 1..24,26 and 29.

Could it be done in ONE SQL?


Tom Kyte
August 08, 2005 - 8:08 pm UTC

select num
from t
where num <= nvl( (select min(num)
from t
where num >= ?), ?)


I think, no create table, no inserts, no testing.


if 27 is in there, 27 is >= 27 and will be the min.
if 27 is not in there, then the number number above 27 comes out.

if nothing above 27 exists, then return 27 (nvl)

Decode in Group By

Magesh, September 29, 2005 - 1:26 pm UTC

Tom,
I want to do a custom sort using decode in group by like the following:

select get_dname(deptno) Dname,count(*) from emp
group by decode(Deptno,10,1,30,2,20,3) dname;

But ofcourse group by does not let me to do this in this way, if I remove decode and simply say deptno, it sorts as 10,20,30. Is there a way to do what I want to do in Oracle?

Tom Kyte
September 30, 2005 - 8:32 am UTC

without an order by - you have NO SORT, you need to use order by in order to get sorted data. group by is not a replacement for order by!!!!!!!!!!!!!!!


Is the value returned by get_dname(deptno) UNIQUE over all deptno's (that is, two deptnos will never have the same name)??? If so


select get_dname(deptno), cnt
from ( select deptno, count(*) cnt
from emp
group by deptno )
order by decode( deptno, 10, 1, 30, 2, 20, 3 )

is probably what you are looking for.

Decode in group by

Magesh, September 29, 2005 - 1:58 pm UTC

Tom,
Please ignore my previous question. I figured it out as follows:

select get_dname(deptno) Dname,count(*) from emp
group by decode(deptno,10,1,30,2,20,3,deptno),deptno
/

Thanks anyway.

Tom Kyte
September 30, 2005 - 8:37 am UTC

(that doesn't sort.....)

Help

Bateson, September 30, 2005 - 1:14 pm UTC

Hi Tom,

I am getting Oracle error

ORA-01858 : a non-numeric character was found
            where a numeric was expected.

I have two subqueries which return data as follows


Subquery A
===========

 Country     User_Grade     User_Cnt
---------   -----------     --------
 US             A             670
 US             B             203


Subquery B
==========

 Country     User_Cnt
--------     --------
 US            540



My main query is as follows


SQL> select to_char(sysdate,'dd/mm/yyyy') as Today,
            a.country,
            a.grade,
            a.user_cnt,
            round(nvl(a.user_cnt/b.user_cnt)*100,0),2) as user_percent
            from (
                 A,B)

WHERE A and B are subqueries mentioned above.

when I put this query, I am getting the ORA-01838 error.

what can be the reason behind this??

How to correct this??

Please help me with a solution.
 

Tom Kyte
September 30, 2005 - 2:22 pm UTC

you give me no tables nor inserts....

Query

Ravi Thapliyal, October 05, 2005 - 6:37 am UTC

Hi Tom,

I have a query here, take the two system tables "all_tab_comments" and "all_col_comments". I need the result in this manner:-

table_name1 comment
col_name1 comment
col_name2 comment
col_name3 comment
table_name2 comment
col_name1 comment
col_name2 comment
col_name3 comment
......... ........
I will appreciate your help.

Regards
Ravi Thapliyal


Tom Kyte
October 05, 2005 - 7:50 am UTC

1 select decode( what, 'TABLE', '', ' ' )||data, comments
2 from (
3 select 1 oc, 'TABLE' what, table_name data, comments, table_name
4 from all_tab_comments
5 union all
6 select 2 oc, 'COLUMN', column_name, comments , table_name
7 from all_col_comments
8 )
9* order by table_name, oc, data


FIFO in SQL

Parag Jayant Patankar, October 05, 2005 - 8:51 am UTC

Hi Tom,

Excellent answer to question. Tom, I want to do subtract sales amount from purchase amount in SQL by FIFO method.

for e.g.

create table t
 (
 indicator    varchar2(1),
 dt           date,
 amt          number
 );

insert into t values ('p', sysdate-100, 20);
insert into t values ('p', sysdate-90, 30);
insert into t values ('p', sysdate-85, 70);
insert into t values('s', sysdate-85, 60);
insert into t values('p', sysdate-83, 100);
insert into t values('s', sysdate-84, 40);
insert into t values('s', '01-aug-05', 80);

commit;

So my table having following records
18:32:51 SQL> select * from t;

I DT               AMT
- --------- ----------
p 14-JUN-05         20
p 24-JUN-05         30
p 29-JUN-05         70
s 29-JUN-05         60
p 01-JUL-05        100
s 30-JUN-05         40
s 01-AUG-05         80

7 rows selected.

So I have to deduct sales amount 60, 40 and 80 from the purchase amount by FIFO method and print purchase and sales balance in next columns. for e.g. I want to deduct sales amount 60 from 20 so sales amount bal is 40. now balance 40 I want to deduct from 30 so purchase amount balance is 0 and sales amount balance is 10. ...so on.

So my desired output is

Ind Pur Dt    Pur Amt Sales Dt  Sales  Pur   sal
                                  Amt  Bal   Bal
 
p    14-Jun-05  20    29-Jul-05    60    0    40
p    24-Jun-05  30    29-Jul-05    40    0    10
p    29-Jun-05  70    29-Jul-05    10    60    0
                      30-Jun-05    40    20    0
...
...

Is it possible to generate such output from SQL ?

regards & thanks
pjp 

Tom Kyte
October 05, 2005 - 11:11 am UTC

continue the thought, what happens withthe 40/80 rows there.

FIFO in SQL

Parag Jayant Patankar, October 06, 2005 - 6:54 am UTC

Hi Tom,

Sorry I have not understood your answer, will you pl explain more

"continue the thought, what happens withthe 40/80 rows there."

total Sales amount will be always less than purchase amount, and in a month there will not be more than 50 records of purchase and sales.

regards & thanks
pjp

Tom Kyte
October 06, 2005 - 7:56 am UTC

finish the example. is the 40/80 row really to be treated as a single 120 row.

I'm not following your logic.

SQL

Parag Jayant Patankar, October 06, 2005 - 9:22 am UTC

Hi Tom,

Sorry for the confustion. 40/80 sales records can not be considered as 120 ( 40+80 ) as I required balance amount of sales as shown in the example.

regards & thanks
pjp

Tom Kyte
October 06, 2005 - 11:51 am UTC

but your example didn't process them, i didn't follow it - I don't see the 40/80

SQL

parag jayant patankar, October 06, 2005 - 12:17 pm UTC

Hi Tom,

In my desired output I have shown only partial results. Full output will be ( something similar )

18:32:51 SQL> select * from t;

I DT               AMT
- --------- ----------
p 14-JUN-05         20
p 24-JUN-05         30
p 29-JUN-05         70
s 29-JUN-05         60
p 01-JUL-05        100
s 30-JUN-05         40
s 01-AUG-05         80

7 rows selected.

Ind Pur Dt    Pur Amt Sales Dt  Sales  Pur   sal
                                  Amt  Bal   Bal
 
p    14-Jun-05  20    29-Jul-05    60    0    40 (20-60) p-s
p    24-Jun-05  30    29-Jul-05    40    0    10 (30-40) p-s
p    29-Jun-05  70    29-Jul-05    10    60    0 (70-10) p-s
                      30-Jun-05    40    20    0 (60-40) p-s
                      01-aug-05    80     0   60 (20-80)p-s 
p    01-jul-05 100    01-aug-05    60    40    0 (100-60)p-s

I hope my desired output and example is clear now.

regards & thanks
pjp 

Tom Kyte
October 06, 2005 - 12:34 pm UTC

the only way I can think to run this is from the bottom up and I don't think it is going to "work" as they say.

I have problems with the data in general here - not fully understanding how it is to be applied and why the 40/80 records are "not the same". The logic isn't "take this p record and look forward for the next S record" - it is sort of a cumulative thing.


sorry - I don't see it. I don't see a clean SQL solution for it either.

SQL

Parag Jayant Patankar, October 06, 2005 - 12:49 pm UTC

Hi Tom,

Thanks for your clear answer. Is it possible to do some simple calculation of deducting total sales amount ( instead of individual sales amount )from each purchase record and print balance of purchase for e.g.

18:32:51 SQL> select * from t;

I DT               AMT
- --------- ----------
p 14-JUN-05         20
p 24-JUN-05         30
p 29-JUN-05         70
s 29-JUN-05         60
p 01-JUL-05        100
s 30-JUN-05         40
s 01-AUG-05         80

Then my total sales amount is 180 (60+40+80). So can I have output like

Ind   Dt         Pur Amount Tot SalesAmt   Pur Balance
p     14-JUN-05         20           180         0
p     24-JUN-05         30           180         0 
p     29-JUN-05         70           180         0
p     01-jul-05        100           180        40

regards & thanks
pjp
 

Tom Kyte
October 06, 2005 - 1:29 pm UTC

ops$tkyte@ORA10GR2> select indicator, dt, amt, salesamt,
  2         greatest( runningTotal-salesamt, 0) purbal
  3    from (
  4  select indicator,
  5         dt,
  6         amt,
  7         (select sum(amt) from t where indicator = 's') salesamt,
  8             sum(amt) over (order by dt) runningTotal
  9    from t
 10   where indicator = 'p'
 11         )
 12  /
 
I DT               AMT   SALESAMT     PURBAL
- --------- ---------- ---------- ----------
p 28-JUN-05         20        180          0
p 08-JUL-05         30        180          0
p 13-JUL-05         70        180          0
p 15-JUL-05        100        180         40
 

OK

Raju, October 12, 2005 - 7:10 am UTC

Hi Tom,
Any other way to put this query??

SQL > select 'No Rows Found' from dual where 1 > (select count(*) from t)

If the table T has no rows it must return 'No Rows Found'.

Can we use Exists operator here??


Tom Kyte
October 12, 2005 - 7:39 am UTC

you use NOT exists and probably should

select 'no data' from dual where not exists (select null from t)

Help for SQL Query

kamal, June 01, 2006 - 8:13 am UTC

Hi Tom,

We have table with Group and User data.

Create table GroupUser
(
G varchar2(10),
U varchar2(10)
);

The Input to the Query will group names (example: 'G1','G2')

The Output should be only users who belong all the groups given as input.

thanks in advance
kamal

Tom Kyte
June 01, 2006 - 10:58 am UTC

well, with that table, we can just skip it - since it is empty.

Probably the answer is something like:


select u
from t
where g in ( 'G1', 'G2' )
group by u
having count(distinct g) = 2;



Using Analytics

Kamal, June 05, 2006 - 8:12 am UTC

Hi Tom

Thanks for your reply...i found out the same using analytical query

Select Distinct "User" from
(
Select G "Group", U "User", count(*) over (partition by U) cou
from Test_sql
where G in ('G2','G3')
)
Where cou = 2;

Kamal

Tom Kyte
June 05, 2006 - 9:39 am UTC

that would be the less effective way to do it, sure.

see above.

Get data with same id in one line

Sean, March 28, 2007 - 5:48 pm UTC

Hi Tom,

I would like to get data like this from the table t1. Thanks so much for your help. -- Sean


C1 C2 C2 C2

1 A AA AAA
2 B BB BBB




SQL> select * from t1;

C1 C2
---------- --------------------
1 A
1 AA
1 AAA
2 B
2 BB
2 BBB



create table t1(c1 number, c2 varchar(20));

insert into t1(c1, c2)
values(1, 'A');
insert into t1(c1, c2)
values(1, 'AA');
insert into t1(c1, c2)
values(1, 'AAA');

insert into t1(c1, c2)
values(2, 'B');
insert into t1(c1, c2)
values(2, 'BB');
insert into t1(c1, c2)
values(2, 'BBB');

Tom Kyte
March 30, 2007 - 11:48 am UTC

search for the word

pivot

on this site for many many many examples.

Efficient SQL

Ram, April 12, 2007 - 2:47 pm UTC

Hi tom ,

I have two tables
=======
Table A
========

UniqueName, Name

----------, ----

123 , Test

124 , Test1

125 , Test2

126 , Test3

=======
Table B
========
code , grade

-----, -----

123 , A

126 , B


I need the output from these tables in the below format

123, Test, 'EXIST'

124,Test1, 'NONEXIST'

125, test2, 'NONEXIST'

126, Test3,'EXIST'


I have written a query as listed below

SELECT distinct a.UNIQUENAME,'EXIST' FROM A a,B b
Where b.code=a.UNIQUENAME
union all
select distinct a.UNIQUENAME,'NONEXIST' from A a where
a.UNIQUENAME not in
( SELECT a.UNIQUENAME FROM A a,B b
Where b.code=a.UNIQUENAME)

But not sure whether this is a good approach or not , Please suggest.

Thanks

Tom Kyte
April 13, 2007 - 12:21 pm UTC

is code unique in tableb?

select a.*, case when b.code is null then 'NOT EXIST' else 'EXIST' end e
from a, b
where a.uniquename = b.code(+);


Efficient SQL

RAM, April 12, 2007 - 3:53 pm UTC

Hi tom ,

Also please tell me whether this approcah is good

SELECT a.ID,(case when b.code is null then 'notexists' else 'exists' end) value FROM A left outer join B b
on a.id=b.code
Tom Kyte
April 13, 2007 - 12:25 pm UTC

that would be good IF code in B is unique.

A reader, June 20, 2007 - 1:27 am UTC

In table i have following date ranges

ID From_Date To_Date
1 05/01/2007 05/30/2007
2 01/01/2007 12/31/2007
3 05/15/2007 05/25/2007
4 04/01/2007 05/12/2007
5 05/20/2007 10/06/2007
6 01/02/2007 04/30/2007
7 06/15/2007 09/15/2007

I want only those recored which are with in this provided input
From_Date =05/01/2007
To_Date =05/30/2007

The out put should contain following records.

ID From_Date To_Date
1 05/01/2007 05/30/2007
2 01/01/2007 12/31/2007
3 05/15/2007 05/25/2007
4 04/01/2007 05/12/2007
5 05/20/2007 10/06/2007


CREATE TABLE TEMP_DATE
(
ID NUMBER(10),
DATE_FROM DATE,
DATE_TO DATE
);

INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
2, TO_Date( '01/01/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '12/31/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
3, TO_Date( '05/15/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '05/25/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
4, TO_Date( '04/01/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '05/12/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
5, TO_Date( '05/20/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '10/06/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
6, TO_Date( '01/02/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '04/30/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
7, TO_Date( '06/15/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '09/15/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
INSERT INTO TEMP_DATE ( ID, DATE_FROM, DATE_TO ) VALUES (
1, TO_Date( '05/01/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'), TO_Date( '05/30/2007 12:00:00 AM', 'MM/DD/YYYY HH:MI:SS AM'));
COMMIT;

Currently i am using following query.

SELECT *
FROM TEMP_DATE
WHERE ((DATE_FROM >TO_DATE('05/01/2007','MM/DD/YYYY') AND DATE_TO <=TO_DATE('05/30/2007','MM/DD/YYYY'))
OR (DATE_FROM <TO_DATE('05/01/2007','MM/DD/YYYY') AND DATE_TO >=TO_DATE('05/30/2007','MM/DD/YYYY'))
OR (DATE_FROM =TO_DATE('05/01/2007','MM/DD/YYYY') OR DATE_TO =TO_DATE('05/30/2007','MM/DD/YYYY') )
OR (DATE_FROM <TO_DATE('05/01/2007','MM/DD/YYYY') AND DATE_TO >=TO_DATE('05/01/2007','MM/DD/YYYY') )
OR (DATE_FROM <TO_DATE('05/30/2007','MM/DD/YYYY') AND DATE_TO >TO_DATE('05/01/2007','MM/DD/YYYY') )
)

Is there some better way to avoid these ORS
Tom Kyte
June 20, 2007 - 9:27 am UTC

ops$tkyte%ORA10GR2> select *
  2    from temp_date
  3   where (date_from <= to_date(:p_ending,'mm/dd/yyyy') )
  4     AND (date_to   >= to_date(:p_beginning,'mm/dd/yyyy') )
  5   order by id
  6  /

        ID DATE_FROM DATE_TO
---------- --------- ---------
         1 01-MAY-07 30-MAY-07
         2 01-JAN-07 31-DEC-07
         3 15-MAY-07 25-MAY-07
         4 01-APR-07 12-MAY-07
         5 20-MAY-07 06-OCT-07

A reader, June 20, 2007 - 10:07 am UTC

Thanks a lot for prompt reply. It will make my life very easy at maintenance time. You are doing a lot for Oracle community.

Why optimizer avoiding index when using to_nchar function

V, June 21, 2007 - 4:13 pm UTC

Tom,
My question regarding why optimizer could not use index when I use function t_nchar on a string literal in predicate to compare the column value where optimizer can use an index when used to_char
Thanks for your help.
V

1* create table temp as select to_char(rownum) col1 from dba_objects
Table created.

1* create index i_temp on temp(col1)
SQL> /

Index created.

SQL> exec dbms_stats.gather_table_stats('XXX','TEMP',cascade=>TRUE);

PL/SQL procedure successfully completed.


SQL> set autotrace on explain
SQL> select * from temp where col1='1';

COL1
----------------------------------------
1


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=5)
1 0 INDEX (RANGE SCAN) OF 'I_TEMP' (INDEX) (Cost=1 Card=1 Byte
s=5)


1* select * from temp where col1=to_char(1)
SQL> /

COL1
----------------------------------------
1


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=1 Card=1 Bytes=5)
1 0 INDEX (RANGE SCAN) OF 'I_TEMP' (INDEX) (Cost=1 Card=1 Byte
s=5)

Wrote file afiedt.buf

1* select * from temp where col1=to_nchar(1)
SQL> /

COL1
----------------------------------------
1


Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=25 Card=490 Bytes=24
50)

1 0 TABLE ACCESS (FULL) OF 'TEMP' (TABLE) (Cost=25 Card=490 By
tes=2450)
Tom Kyte
June 22, 2007 - 10:14 am UTC

because you have an index on a CHAR value and when you compare a CHAR to an NCHAR - there is a conversion of the CHAR to a NCHAR.

where varchar_column = to_nchar(something)

is the same logically as:

where to_nchar(varchar_column) = to_nchar(something)


you have applied a function to the database column.

Same query above

A reader, June 22, 2007 - 12:37 pm UTC

Tom,
Same from the above question,
for the query below:
select * from temp where col1=to_nchar('1')
[where col1 in varchar2 and has index on col1]

Why can't optimizer think to
implicitly convert to_nchar('1') to varchar2 [as to to_char(to_nchar('1')) because col1 in varchar2 and use index on col1
instead of checking
to_nchar(col1) = to_nchar('1') which is causing to avoid index.

Thanks for your help.



Tom Kyte
June 22, 2007 - 5:14 pm UTC

because of the rules

the rule when you compare a number to a string is to convert the string to a number

where string_database_column = any_number

is the same as

where to_number(string_database_column) = any_number


that is the rule for string to number comparisions. the rule for varchar to nvarchar is convert the varchar into nvarchar (since converting the nvarchar into a varchar WOULD STAND TO CHANGE THE DATA)....

Followup from above

V, June 23, 2007 - 1:00 pm UTC

Tom,
Follow-up from the question above,
We are an ERP development company.
After doing analysis, for performance/storage reasons, we decided CJK customers should use NVARCHAR2 (AL16UTF16) and US/Europe use VARCHAR2 (and use AL32UTF8).
(Being AL16UTF16 not supported as character set that means UTF16 is not possible to use in VARCHAR2).
So we provide a tool to switch from VARCHAR2 to NVARCHAR2 to support NVARCHAR2 for CJK customers(simple statment alter table t1 modify(col varchar2).

My question here is how do we pass literals from platform (middleware). Should we prefix with N or not.
Like
Select * from temp where col1 = N'Hello' OR
Select * from temp where col1 = 'Hello'

If we N' Prfix we loose index capability due to implicit conversion from VARCHAR2. For CJK¿s, it will be Invalid comparison for NVARCHAR2 without N'.
Is there any better way under given circumstances
Your help is greatly appreciated.




Tom Kyte
June 23, 2007 - 2:06 pm UTC

you do NOT want to use literals, you will use BIND VARIABLES.

I do not think you made the right choice there - you'll need different indexing strategies and all.

You should just use a unicode character set and be done with it.


using literals - that, that would be the worst performace/security/scalability decision you could make.

V, June 23, 2007 - 9:14 pm UTC

Tom,
We use bind variables but there are certain cases where we may need to use literals to get advantages of skewed data ,overcome certain cases like bind peeking
Do you think literals absolutely avoided in OLTP application?
Thanks for your help.



Tom Kyte
July 02, 2007 - 8:47 am UTC

if you have an "OLTP" application that would need to use literals because of data skew (so that sometimes a full scan is taken over an index) - then I would say - you do not have an "OLTP" application anymore.

OLTP - characterized by short, predicable length queries returning tiny (relatively speaking) result sets. Hundreds/thousands of statements executed per second. Binding is the only way to do it.

Should we literals some time instead of bind variables in OLTP also

V, June 23, 2007 - 10:16 pm UTC

Hi Tom,
Your input is always very helpful to us.
From the question above do you think we should use literals instead of bind variables (OLTP app)where we knew that data highly skewed and few distinct values exists in the column.

My example for that scenario below :


SQL> select count(*),yes_no_fl from temp group by yes_no_fl;

COUNT(*) YES
---------- ---
195944 No
196 Yes

SQL> var l_var varchar2(3);

1 select count(*) from
2 (
3 (select rownum,yes_no_fl from temp where yes_no_fl = :l_var)
4* )
SQL> /

COUNT(*)
----------
0

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=80 Card=1)
1 0 SORT (AGGREGATE)
2 1 VIEW (Cost=80 Card=99554)
3 2 COUNT
4 3 TABLE ACCESS (FULL) OF 'TEMP' (TABLE) (Cost=80 Card=
99554 Bytes=298662)





Statistics
----------------------------------------------------------
304 recursive calls
0 db block gets
49 consistent gets
0 physical reads
0 redo size
378 bytes sent via SQL*Net to client
500 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
1 rows processed



SQL> exec :l_var := 'Yes';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> /

COUNT(*)
----------
196

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=80 Card=1)
1 0 SORT (AGGREGATE)
2 1 VIEW (Cost=80 Card=99554)
3 2 COUNT
4 3 TABLE ACCESS (FULL) OF 'TEMP' (TABLE) (Cost=80 Card=
99554 Bytes=298662)





Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
304 consistent gets
0 physical reads
0 redo size
380 bytes sent via SQL*Net to client
500 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed







1 select count(*) from
2 (
3 (select rownum,yes_no_fl from temp where yes_no_fl = 'Yes')
4* )
5 /

COUNT(*)
----------
196

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1)
1 0 SORT (AGGREGATE)
2 1 VIEW (Cost=4 Card=345)
3 2 COUNT
4 3 INDEX (RANGE SCAN) OF 'L_TEMP' (INDEX) (Cost=4 Card=
345 Bytes=1035)





Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
4 consistent gets
0 physical reads
0 redo size
380 bytes sent via SQL*Net to client
500 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
Tom Kyte
July 02, 2007 - 8:51 am UTC

those are NOT oltp queries, that would be a report maybe - run once in a long time. It doesn't look like something that

a) should be run
b) would be run much

why would you count yes/no's for every record in a table like that?

If it is a "common" requirement, then materialized views are likely part of the solution (so you would have a two row table). I use them myself for the asktom homepage to tell you how many articles have been added/modified recently.

Need your help

vikas, June 26, 2007 - 4:43 am UTC

Hi Tom,

Appreciate your help!

I have a table t with a column Request_term. Some sample data is listed for your reference:

boldly going where captain cook has gone before |: category_travel
Ambitious Brew : The Story of American Beer |: category_history |: category_food_and_wine

Its Values are Term and categories separater by a delimiter |:

So the Terms are :

1.boldly going where captain cook has gone before
2.Ambitious Brew : The Story of American Beer
and their associated categories are :
1.category_travel
2.category_history
2.category_food_and_wine

We need to formulate a SQL query to get the output as :

Category Count
category_travel 1
category_history 1
category_food_and_wine 1

Thanks

Here ya go Vikas

Yuan, June 26, 2007 - 2:59 pm UTC

Normalizing your data would make this a whole lot easier, but here:

create table yuantest (x varchar2(100));

insert into yuantest values ('boldly going where captain cook has gone before |: category_travel');
insert into yuantest values ('Ambitious Brew : The Story of American Beer |: category_history |: category_food_and_wine');
insert into yuantest values ('You gotta be kiddin |: category_history |: category_humor |: category_junk');

select cats, count(1)
from (select trim(substr(x, instr(x, '|:', 1, level) + 2, decode(connect_by_isleaf, 1, 100, instr(x, '|:', 1, level + 1) - instr(x, '|:', 1, level) -2))) cats from yuantest connect by prior x = x and level <= (length(x) - length(replace(x, '|:', NULL))) / 2 AND PRIOR dbms_random.string ('p', 10) IS NOT NULL)
group by cats

PRIOR dbms_random.string ('p', 10) IS NOT NULL

Yuan, June 26, 2007 - 3:05 pm UTC

Tom, in my solution for Vikas, I stole the connect by clause:

PRIOR dbms_random.string ('p', 10) IS NOT NULL

from your response in another post on this site. Before I added that I was getting ORA-01436: CONNECT BY loop in user data. I see that adding this clause fixes that, but to be honest I don't understand how. When you get a chance, can you explain?

Thanks a ton!

Vikas, June 28, 2007 - 6:24 am UTC

Hi Yaun,

Too good a solution, it fits my need. Has not even thought in that direction to solve that query.

Once again, wonderful! Its best to learn some extremely good fundamentals as to how to avoid a loop using PL/SQL and do that stuff in SQL.

It challenges you!

Regards,

Question

PRS, January 12, 2008 - 9:58 am UTC

Tom,
I know that you may delete this question. But I needed a help, So I am putting a question.
I have table order_stg as under.
Order_ID event_num rcvng_desk_id custm_3_txt
1 0 ABC
1 1 DEF
1 2 JKH
2 0 DEF
2 1 JKH

I have a stored procedure to update 15M rows in order_Stg table as shown below. It takes 2Hrs to do that.
I have an option of BULK collect. But is there any other way I can bring down this less than 30 minutes using a SQL.

Any help is appreciated.

PROCEDURE UPD_AGG_ORDER_STG IS

CURSOR C_CUR IS
select a.ordr_id,a.rcv_desk_id rcvdesk,b.agg_id aggid,b.desk_id deskid
from order_stg a,desk_agg_map b
where a.rcv_desk_id = b.desk_id
and a.src_sys = 'AGO'
and a.ordr_evnt_num = 0;

V_SQLCODE NUMBER;
V_SQLERRM VARCHAR2(500);

BEGIN
FOR REC IN C_CUR
LOOP
UPDATE ORDER_STG X
SET X.CUSTOM_3_TEXT = rec.aggid
WHERE X.ordr_id = REC.ordr_ID ;
END LOOP;
COMMIT;

EXCEPTION
WHEN OTHERS THEN
V_SQLCODE := SQLCODE;
V_SQLERRM := SQLERRM;
dbms_output.put_line('There is other problem');
raise_application_error(-20002, substr(V_SQLERRM,1,512));
rollback;
return;

END UPD_AGG_ORDER_STG;


Thanks,
PRS
Tom Kyte
January 14, 2008 - 3:30 pm UTC

first, remove - erase - delete - get rid of that when others

it does NOTHING. It does actually less than something, it removes information from the caller.


second, then erase all of the procedural code and replace it with a single MERGE or UPDATE statement

merge into (thing you are updating)
using (thing you fetch from)
on (the key to put them together)
when matched the update set ....;




I can't use merge

PRS, January 14, 2008 - 5:56 pm UTC

Tom,
I can't use MERGE as it is selecting the record for order vent type zero for a given order id and then updates all records(i.e all events) of that order with that value.

I tried to do with the update in corelated subquery but there it does not help much in performance improvement.

Please let me know if anything else can be done. These table are having 15M to 20M rows per day.
Tom Kyte
January 15, 2008 - 7:05 am UTC

did you even try the merge or are you presuming you cannot use it???

you give me no tables and inserts to work with to demonstrate how to use merge.

but it will look something like this:

merge into order_stg x
using (
select a.ordr_id,a.rcv_desk_id rcvdesk,b.agg_id aggid,b.desk_id deskid
from order_stg a,desk_agg_map b
where a.rcv_desk_id = b.desk_id
and a.src_sys = 'AGO'
and a.ordr_evnt_num = 0
) REC
on
( rec.ordr_id = x.ordr_id )
when matched then update set custom_3_text = rec.aggid;


sort of like this:

merge into order_stg x
using (
 select  a.ordr_id,a.rcv_desk_id rcvdesk,b.agg_id aggid,b.desk_id deskid
        from order_stg a,desk_agg_map b
       where a.rcv_desk_id = b.desk_id
         and a.src_sys = 'AGO'
         and a.ordr_evnt_num = 0
) REC
on
(  rec.ordr_id = x.ordr_id )
when matched then update set custom_3_text = rec.aggid;

We are on 9.2.07

PRS, January 15, 2008 - 10:46 am UTC

Tom,
Thanks for the response. But we are on 9.2.0.7 and that tell me that I have to have when not matched clause.

Thanks,
Prashant R Shah
Tom Kyte
January 15, 2008 - 12:59 pm UTC

then just add one

look, you are merging into a table from the same table - hence you will never hit the "when not matched" bit *ever* so, just add:

when not matched /* this never happens, please delete when we get current */
then insert (primary_key) values ( NULL );


where primary key is your primary key column...

It is impossible for this query:
 select  a.ordr_id,a.rcv_desk_id rcvdesk,b.agg_id aggid,b.desk_id deskid
        from order_stg a,desk_agg_map b
       where a.rcv_desk_id = b.desk_id
         and a.src_sys = 'AGO'
         and a.ordr_evnt_num = 0


to generate a row that doesn't already exist in ORDER_STG

Thanks a Lot

PRS, January 15, 2008 - 1:50 pm UTC

Tom,
Thanks a lot.
PRS

CONVERSION

VINEELA, January 22, 2008 - 7:43 am UTC

oracle sql COLUMNS TO ROW CONVERSION

selecting rows..

Manoj Kaparwan, February 08, 2011 - 6:14 am UTC

Tom,
Thanks for your time.



mndba@ora10g> create table t1 as select * from all_users;

Table created.

mndba@ora10g> create table t2 as select * from all_objects;

Table created.



Using Java we run the  querries of the following nature  and data is stored into flat files.




mndba@ora10g> select * from t1 where username =?



mndba@ora10g> select * from t2 where owner in ( select username from t1 where username = ?);


Question/s

a) How can we avoid running  the subquery in the second query as we have alreday computed the same in first query ( making use of that would be more effiecint here?)

b) I have given as an example for 02 level query.. ( t1 and t2 ..) in real life we may have to read the data upto level t5
   i.e. read all related rows from t3 computed from t2 or t1 ( depending upon the relation) in previous steps...likewise.
  

Tom Kyte
February 10, 2011 - 3:34 pm UTC

a) that is as efficient as it gets - you are doing it right.

b) that is the way it works, separate queries are just that - separate queries. Unless you can write it as a single query - you do what you are doing.

In 11g, you could use the result_cache hint to have the result sets "cached" - but in general, what you are doing is fine.


selecting rows..

Manoj Kaparwan, February 10, 2011 - 10:30 pm UTC

Many thanks Tom.

regards

selecting rows..

Manoj Kaparwan, February 28, 2011 - 10:31 pm UTC

Tom
Continuing above question..

as I said we have the querries of the following nature.

select * from t1 where username =?

select * from t2 where owner in ( select username from t1 where username = ?);

on t3 we will have subquerries relating t2 and t1.. and likewise.

something like...

select * from t3 where id in ( select id from t2 where id in ( select id from t1) )



Now optmising these queries.

how can I

a) Avoid Subquerries ?
b) IN may be better than EXISTS and vice versa ?

Since these would be dynamic querries , how can we frame optmise querries based on the data it finds during runtime.

regards

Tom Kyte
March 01, 2011 - 8:59 am UTC

a) why? they are quite natural in SQL and they do what you need them to do.

could we turn them into joins? sure, but the result sql would be ugly. And besides, the optimizer will do it for us if it makes sense.

b) in and exists are semantically equivalent and the optimizer is aware of that and will rewrite the query as appropriate.

see
http://www.oracle.com/technetwork/issue-archive/2006/06-may/o36asktom-099107.html


analytic functions totally rule, don't they.....

Suresh A, March 03, 2011 - 2:21 pm UTC

Hi Tom,

This question is related to the first Question in this series

I have an audit trail table which contains the following values


Id Date ProjectType
-------------- --------- ----------
5969 08-MAY-10
5969 23-MAY-10 TargetDate
5969 07-JUN-10 TargetDate
5969 22-JUN-10 TargetDate
5969 05-NOV-10 TargetDate
5969 16-NOV-10 Velocity
5969 18-NOV-10 Velocity
5969 23-NOV-10 Velocity
5969 23-NOV-10
5969 23-NOV-10 TargetDate
5969 23-NOV-10 TargetDate
5969 23-NOV-10 TargetDate
5969 23-NOV-10 Velocity
5969 23-NOV-10 Velocity
5969 24-NOV-10 Velocity
5969 24-NOV-10 Velocity
5969 24-NOV-10 Velocity
5970 08-MAY-10
5970 23-MAY-10 TargetDate
5970 07-JUN-10 TargetDate
5970 22-JUN-10 TargetDate
5970 05-NOV-10 TargetDate
5970 16-NOV-10 Velocity
5970 18-NOV-10 Velocity
5970 23-NOV-10 Velocity
5970 23-NOV-10
5970 23-NOV-10 TargetDate
5970 23-NOV-10 TargetDate
5970 23-NOV-10 TargetDate
5970 23-NOV-10 Velocity
5970 23-NOV-10 Velocity
5970 24-NOV-10 Velocity
5970 24-NOV-10 Velocity
5970 24-NOV-10 Velocity

I would like to get the following o/p


Id Date ProjectType
-------------- --------- ----------
5969 08-MAY-10
5969 23-MAY-10 TargetDate
5969 16-NOV-10 Velocity
5969 23-NOV-10
5969 23-NOV-10 TargetDate
5969 23-NOV-10 Velocity
5970 08-MAY-10
5970 23-MAY-10 TargetDate
5970 16-NOV-10 Velocity
5970 23-NOV-10
5970 23-NOV-10 TargetDate
5970 23-NOV-10 Velocity


I need the o/p by removing the duplicate in a series for each Id. (primary key is Id + Date)
How we can achieve this using analytic functions ?

I am using oracle 8i

thanks in advance...


Tom Kyte
March 03, 2011 - 7:07 pm UTC

not even going to look to see if this is possible since I don't see a create table nor insert into table statements to set up the example.


Also, do you have a case whereby you would/could have:
          Id   Date      ProjectType
-------------- --------- ----------
          5969 08-MAY-10           
          5969 23-MAY-10 TargetDate
          5969 07-JUN-10 TargetDate
          5969 22-JUN-10 TargetDate
          5969 05-NOV-10 TargetDate
          5969 16-NOV-10 Velocity
          5969 18-NOV-10 Velocity          
          5969 23-NOV-10 Velocity
          5969 23-MAY-11 TargetDate
          5969 07-JUN-11 TargetDate
          5969 22-JUN-11 TargetDate



where the output would be 4 records for that id, with target date twice?

Date Range

A Reader, March 09, 2011 - 7:09 am UTC

Hi Tom,

I need to write a query to retrive a date range (previous Monday - Sunday) for a given date.

For e.g. if it is today i.e. 09-Mar-2011(Wednesday), the query should return 28-Feb-2011(Monday) & 06-Feb-2011(Sunday).

If it is 06-Mar-2011(Sunday), the query should return 28-Feb-2011(Monday) & 06-Feb-2011(Sunday).

Could you please help.
Tom Kyte
March 09, 2011 - 9:42 am UTC

ops$tkyte%ORA11GR2> select dt, next_day( dt-14, 'mon' ), next_day( dt-14, 'sun' )
  2    from (
  3  select sysdate+level-1 dt
  4    from dual
  5  connect by level <= 10
  6         )
  7  /

DT              NEXT_DAY(DT-14, NEXT_DAY(DT-14,
--------------- --------------- ---------------
Wed 09-mar-2011 Mon 28-feb-2011 Sun 27-feb-2011
Thu 10-mar-2011 Mon 28-feb-2011 Sun 27-feb-2011
Fri 11-mar-2011 Mon 28-feb-2011 Sun 27-feb-2011
Sat 12-mar-2011 Mon 28-feb-2011 Sun 27-feb-2011
Sun 13-mar-2011 Mon 28-feb-2011 Sun 06-mar-2011
Mon 14-mar-2011 Mon 07-mar-2011 Sun 06-mar-2011
Tue 15-mar-2011 Mon 07-mar-2011 Sun 06-mar-2011
Wed 16-mar-2011 Mon 07-mar-2011 Sun 06-mar-2011
Thu 17-mar-2011 Mon 07-mar-2011 Sun 06-mar-2011
Fri 18-mar-2011 Mon 07-mar-2011 Sun 06-mar-2011

10 rows selected.

Date Range

A Reader, March 09, 2011 - 10:45 am UTC

Thanks Tom.

Maybe I haven't explained the requirement well. The query should return Monday to Sunday of the previous week.

In other words, the output should be as below:

DT NEXT_DAY(DT-14, NEXT_DAY(DT-14,
--------------- --------------- ---------------
Mon 07-mar-2011 Mon 28-feb-2011 Sun 06-feb-2011
Tue 08-mar-2011 Mon 28-feb-2011 Sun 06-feb-2011
Wed 09-mar-2011 Mon 28-feb-2011 Sun 06-feb-2011
Thu 10-mar-2011 Mon 28-feb-2011 Sun 06-feb-2011
Fri 11-mar-2011 Mon 28-feb-2011 Sun 06-mar-2011
Sat 12-mar-2011 Mon 28-feb-2011 Sun 06-mar-2011

Regards

Tom Kyte
March 09, 2011 - 12:46 pm UTC

I'm very very very confused.

You wrote:

For e.g. if it is today i.e. 09-Mar-2011(Wednesday), the query should return  28-Feb-2011(Monday) & 
06-Feb-2011(Sunday).

If it is 06-Mar-2011(Sunday), the query should return 28-Feb-2011(Monday) & 06-Feb-2011(Sunday).



well, I did that. Did you really mean:

if today is 9-mar-2011, I want a SET of rows representing all of the days from 28-feb-2011 to 06-feb-2011.

NOT just the two scalar values 28-feb-2011 & 06-feb-2011?

ops$tkyte%ORA11GR2> select trunc(sysdate),
  2         next_day( trunc(sysdate)-14, 'mon') +level-1
  3    from dual
  4  connect by level <= 7;

TRUNC(SYSDATE)  NEXT_DAY(TRUNC(
--------------- ---------------
Wed 09-mar-2011 Mon 28-feb-2011
Wed 09-mar-2011 Tue 01-mar-2011
Wed 09-mar-2011 Wed 02-mar-2011
Wed 09-mar-2011 Thu 03-mar-2011
Wed 09-mar-2011 Fri 04-mar-2011
Wed 09-mar-2011 Sat 05-mar-2011
Wed 09-mar-2011 Sun 06-mar-2011

7 rows selected.

Date Range - typo?

Ditto, March 09, 2011 - 2:49 pm UTC

re: Date Range discussion with "A Reader", from Mar 9.

He writes:
"I need to write a query to retrive a date range (previous Monday - Sunday) for a given date."

So based on this, Mar 9 input, should result in range of Feb 28 (Mon) - Mar 6 (Sun).

However, he also writes:
"09-Mar-2011(Wednesday), the query should return 28-Feb-2011(Monday) &
06-Feb-2011(Sunday)."

Feb 6!?!? Huh .. I think he means Mar 6 ... ??

If so ... I think a quick mod to Tom's original query gives the answer:

SQL > l
1 select dt, next_day( dt-14, 'mon' ), next_day( dt-14, 'mon' ) + 6
2 from (
3 select sysdate+level-1 dt
4 from dual
5 connect by level <= 10
6* )
SQL > /
------more------

DT NEXT_DAY(DT NEXT_DAY(DT
----------- ----------- -----------
09-MAR-2011 28-FEB-2011 06-MAR-2011
10-MAR-2011 28-FEB-2011 06-MAR-2011
11-MAR-2011 28-FEB-2011 06-MAR-2011
12-MAR-2011 28-FEB-2011 06-MAR-2011
13-MAR-2011 28-FEB-2011 06-MAR-2011
14-MAR-2011 07-MAR-2011 13-MAR-2011
15-MAR-2011 07-MAR-2011 13-MAR-2011
16-MAR-2011 07-MAR-2011 13-MAR-2011
17-MAR-2011 07-MAR-2011 13-MAR-2011
18-MAR-2011 07-MAR-2011 13-MAR-2011

10 rows selected.


Maybe? Maybe not ... :)

Date Range

A reader, March 10, 2011 - 4:05 am UTC

Thanks Ditto for pointing out, it was indeed a typo. Thanks to both of you, I have got the answer now.

kumar, April 12, 2011 - 9:34 am UTC

Hi,

i am trying to write a query in sql server. if anybody know plz help me.

table has 3 columns(name,dept,id).write a query delete name having id value is 'emp' keep in mind if any dept has one employee and having id value is 'emp' then that should not be deleted.
Tom Kyte
April 13, 2011 - 9:36 am UTC

i am trying to write a query in sql server

Hmm, you might possibly be confused then - the domain here is "oracle.com"....


this smells just like homework. Homework is meant to be done by the student.

pc, April 14, 2011 - 7:14 am UTC

Hi,

I have one doubt.

HOW TO CONNECT FROM SQL SERVER 2008 TO DB2 UDB ?


Tom Kyte
April 14, 2011 - 10:14 am UTC

ASK MICROSOFT - THEY PROBABLY KNOW SINCE THEY WROTE SQL SERVER.

You can delete this if you don't have time

PRS, October 22, 2014 - 6:56 pm UTC

I just needed a help and had to come back to this site again. Below query runs for 2 Hrs. Volumes are 50M for order and 15 M for execution. Any help in tuning this is appreciated. If no time, it's ok.

with i0 as (
select distinct a.alert_track, a.scrty_intrl_id, a.trdr_agg_unit from kdd_mnr.FUT_SPFNG_ALERT_HILGT_TMP a
where alert_track in (2,4,6,8)
),
i1 as (
select /*+ index(o1, ORDR_SCRTYID_IDX) */ s.scrty_intrl_id, s.trdr_agg_unit, o1.orig_id, o1.ordr_plcmt_dt, o1.ordr_plcmt_tm new_tm, o1.ordr_seq_id, o1.ordr_orig_unit_qt, o1.cncld_ordr_dt cxl_dt, o1.cncld_ordr_tm cxl_tm,
sum(e1.last_exctn_unit_qt) over (partition by e1.ordr_seq_id) fill_qty
from business.ordr o1, business.execution e1,
(select clndr_dt from mantas.kdd_cal where clndr_nm = 'SYSCAL' and bus_day_age between 1 and 5 and bus_day_fl = 'Y') no_of_days, i0 s
where o1.scrty_intrl_id = s.scrty_intrl_id and o1.cncld_ordr_dt is not null and o1.ordr_plcmt_dt = no_of_days.clndr_dt
and o1.last_sbmsn_chnl_tx in ('CQG','TB','CFOX','ULLink','CME','TT','COMET','MANUAL')
and o1.ordr_seq_id = e1.ordr_seq_id(+)
),
i2 as (
select scrty_intrl_id, orig_id, ordr_plcmt_dt
from i1
where (nvl(fill_qty,0) = 0 or ((nvl(fill_qty,0)*100)/ordr_orig_unit_qt <= 75))
and (f_duration_date_time((select kc.clndr_dt from MANTAS.kdd_cal kc where kc.clndr_day_age = 0 and kc.clndr_nm = 'SYSCAL'), '000000000', cxl_dt, cxl_tm|| '000', 'mmm')/1000) -
(f_duration_date_time((select kc.clndr_dt from MANTAS.kdd_cal kc where kc.clndr_day_age = 0 and kc.clndr_nm = 'SYSCAL'), '000000000', ordr_plcmt_dt, new_tm|| '000', 'mmm')/1000) <= 5
),
i3 as (
select o.scrty_intrl_id, s.trdr_agg_unit, o.ordr_plcmt_dt, count(*) total_cxl_orders
from i2 o, i0 s
where o.scrty_intrl_id = s.scrty_intrl_id
and ((o.orig_id in
(select emp_intrl_id from kdd_mnr.rltd_user where active_flag = 'Y' and (scnro_id is null or (scnro_id = 200000002 and track is null) or (scnro_id = 200000002 and track = 'SEQ-A'))
and rltd_trdr_grp = s.trdr_agg_unit))
OR orig_id = s.trdr_agg_unit )
group by o.scrty_intrl_id, s.trdr_agg_unit, o.ordr_plcmt_dt
),
i4 as (
select /*+ index(o1, ORDR_SCRTYID_IDX) */ s.scrty_intrl_id, s.trdr_agg_unit, o1.ordr_plcmt_dt, o1.ordr_seq_id
from business.ordr o1, i0 s, (select clndr_dt from mantas.kdd_cal where clndr_nm = 'SYSCAL' and bus_day_age between 1 and 5 and bus_day_fl = 'Y') no_of_days
where o1.scrty_intrl_id = s.scrty_intrl_id
and ((o1.orig_id in
(select emp_intrl_id from kdd_mnr.rltd_user where active_flag = 'Y' and (scnro_id is null or (scnro_id = 200000002 and track is null) or (scnro_id = 200000002 and track = 'SEQ-A'))
and rltd_trdr_grp = s.trdr_agg_unit ))
OR o1.orig_id = s.trdr_agg_unit
)
and o1.last_sbmsn_chnl_tx in ('CQG','TB','CFOX','ULLink','CME','TT','COMET','MANUAL')
and o1.ordr_plcmt_dt = no_of_days.clndr_dt
),
i5 as (
select scrty_intrl_id, trdr_agg_unit, ordr_plcmt_dt, count(*) total_orders
from i4
group by scrty_intrl_id, trdr_agg_unit, ordr_plcmt_dt
)
select a1.scrty_intrl_id, a1.trdr_agg_unit, a1.ordr_plcmt_dt, a1.total_orders, c1.total_cxl_orders
from i3 c1, i5 a1
where a1.scrty_intrl_id = c1.scrty_intrl_id (+) and a1.trdr_agg_unit = c1.trdr_agg_unit (+) and a1.ordr_plcmt_dt = c1.ordr_plcmt_dt (+)

More to Explore

Analytics

Analytic SQL got you confused? Check out Connor McDonald's complete video course.