George -- Thanks for the question regarding "Compressing multiple UNION ALL query", version 10.2.0
Submitted on 22-Oct-2007 14:19 Central time zone
Last updated 9-Sep-2008 7:20
You Asked
Dear Tom,
The query below has be gnawing at me for a year.
It works well but is so huge! (often the recordset it returns is larger than the query itself).
So my question to you is:
Q:Is it possible to write it in more compact form but
WITHOUT SACRIFICING PERFORMANCE , and if only read-only access to the database is possible (e.g. no create table, no iserts, no updates, etc...) ?
Assume the table 'customer' contains typical distribution of first names and surnames, there is a composite index on fname,sname,id (in that order), and the column 'id' is unique and non-null (columns 'fname and 'sname' are nullable and non-unique).
The query returns 10 records of fname beginning with each letter of the alphabet, sorted by fname, sname, id.
I tried, I read, I asked around, and eventually came to you...
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='A' AND fname<'B' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='B' AND fname<'C' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='C' AND AND fname<'D' rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='D' AND fname<'E' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='E' AND AND fname<'F' rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='F' AND fname<'G' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='G' AND fname<'H' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='H' AND fname<'I' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='I' AND fname<'J' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='J' AND fname<'K' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='K' AND fname<'L' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='L' AND fname<'M' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='M' AND fname<'N' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='N' AND fname<'O' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='O' AND fname<'P' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='P' AND fname<'Q' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='Q' AND fname<'R' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='R' AND fname<'S' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='S' AND fname<'T' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='T' AND fname<'U' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='U' AND fname<'V' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='V' AND fname<'W' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='W' AND fname<'X' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='X' AND fname<'Y' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='Y' AND fname<'Z' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='Z' AND rownum <= 10
ORDER BY fname,sname,id)
and we said...
well, technically, your query is "wrong"
select *
from (select * from t1 order by x)
union all
select *
from (select * from t2 order by y)
is very very different from
select 1 order_col, t1.* from t1
union all
select 2 order_col, t2.* from t2
order by order_col, x
You have to expect that upon a query plan change (out of YOUR control), the optimizer if free to ignore your order by's in the inline views - they are not necessary to return the correct answer - it can be optimized away - so be aware of that.
Also, you compute row_number but seem to use rownum????? and you use rownum before you sort - hence you get 10 rows and sort, instead of sorting and taking the first ten.
So, I believe this is not really your query after all is it? It looks a bit like it - but it is wrong...
You wrote:
The query returns 10 records of fname beginning with each letter of the alphabet, sorted by fname, sname, id.
but that query above does not do that
UNION ALL
SELECT * FROM
(SELECT /*+ INDEX(customer customer(fname,sname,id)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname,
id
FROM customer
WHERE fname>='Y' AND fname<'Z' AND rownum <= 10
ORDER BY fname,sname,id)
UNION ALL
that gets 10 rows, then sorts them. You are maybe getting lucky because it is accidentally getting the data sorted due to an index hint (which may or may not be obeyed as things change...)
this is a slippery slope you are on - lots of reliance on accidents to get the right answer.
I think your query bits should be:
UNION ALL
select *
from (
select fname, sname, id,
row_number() over (partition by fname,sname order by fname,sname,id) i
from customer
where fname >= 'A' and fname < 'B'
order by fname, sname, id
)
where rownum <= 10
and and the END of the entire thing:
order by fname, sname, id
to ensure it is sorted.
and given your rather unique reqirements - it is likely that your implementation once fixed will do the least amount of work in the database.
A 7bit ascii database only of course given that names do not have to start with A, B, C, ... Z in real life (there is that as well... but maybe that is OK for you)
Why not like this?
October 23, 2007 - 10am Central time zone
Reviewer: martina from vienna, austria
I would write the whole query like that:
select *
from (
select fname, sname, id,
row_number() over (partition by substr(fname,1,1) order by fname,sname,id) i
from customer
)
where i <= 10
order by fname, sname, id
regards, martina
More compact SQL
October 23, 2007 - 10am Central time zone
Reviewer: Keith from Ohio
If I understand the requirements correctly, I think this is a more compact way of writing
the query, and has the advantage of allowing first characters to be something other
than A..Z. I'm not certain, however, if it will perform as well:
select * from
(
select fname, sname, id
row_number() over (partition by substr(fname,1,1)
order by fname, sname, id) i
from customer
)
where i <=10
order by fname, sname, id
October 23, 2007 - 10am Central time zone
Reviewer: George Robinson
Thanks for pointing out how "lucky" I was that this query worked at all. I thought that the order
of the UNION ALL subqueries was maintained in the resulting recordset.
Also, I have an additional side question:
Q: Is it possible to write this query without repeating the almost identical queries 26 times (one
for each letter of the alphabet), without sacrificing performace ?
6KB query seems a little silly to me. (especially one that produces such simple output).
In other RDBMS systems, I can use a list of constants in lieu of an inner query, in order to feed
an outer query with variable parameters, such as:
{(A,B),(B,C),(C,D),(D,E), (E,F),...,(Y,Z)}
Can Oracle do that ?
I am looking for a general solution to such problems, not one relevant to my specific task, so
let's assume 7-bit database for simplicity, where lowercase and unicode are unheard of.
Thanks for your time,
George
October 23, 2007 - 10am Central time zone
Reviewer: George Robinson
Dear Maritna & Keith,
Thank you for your input.
Unfortunately the compact query below perfoms 10x slower than the original 26-part UNION ALL query.
The Full Index Scan and Window Sort Pushed Rank is dragging it down.
Also the meaning if the "i" column is different in the query you proposed. In the original query
"i" is a number in the fname,sname,id sort order that distinguishes between the same pairs of
fname,sname.
select * from
(
select /*+ INDEX(customer customer(fname,sname,id)) */
fname,
sname,
id
row_number() over (partition by substr(fname,1,1) order by fname, sname, id) i
from customer
)
where i <=10
order by fname, sname, id
Regards,
George
Followup October 23, 2007 - 11am Central time zone:
they are also trying to point out your use of rownum is entirely wrong!!!! they thought they were fixing a bug of yours
Given your rather unique requirement of A...Z only - your query is correct and appropriate, stop losing sleep over it, but read about ROWNUM please!!!
http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
if the size of the query is not pleasing, just hide it in a view.... it doesn't have to be in the code itself.
Maybe something, Maybe nothing.
October 25, 2007 - 4pm Central time zone
Reviewer: Tyler from Vancouver BC.
Hey George,
I'm not sure if this can be of any use to you or not (apologies if it falls into the latter).
WITH AlphaBet AS
(
SELECT
CHR((ROWNUM + 64)) AS Character
FROM DUAL
CONNECT BY LEVEL <= 26
),
LetterSets AS
(
SELECT
Character AS CurrentLetter,
LEAD(Character) OVER (ORDER BY 1) AS NextLetter
FROM AlphaBet
ORDER BY Character ASC
)
SELECT *
FROM LetterSets;
Would give you a paring of letters like used in your 26 queries now, then you would do something
along the lines of...
FROM customer c, LetterSets l
WHERE c.fname >= l.CurrentLetter
AND (fname < l.NextLetter OR l.NextLetter IS NULL)
October 30, 2007 - 12pm Central time zone
Reviewer: George Robinson
Dear Tyler,
It's rather something than nothing!
Thanks for addressing the core of the problem rather than the dumb mistake I made with rownum.
Indeed your method is the thing I've been hoping for as an alternative to the list of constants
available in other rdbmses.
I pretty much undestand all of your query except the 'ORDER BY 1' clause. Could you elaborate ?
Regards,
George
Followup October 30, 2007 - 1pm Central time zone:
I think his use of "order by 1" is a "bug" waiting to happen.
He has coded:
SELECT
Character AS CurrentLetter,
LEAD(Character) OVER (ORDER BY 1) AS NextLetter
FROM AlphaBet
ORDER BY Character ASC
and I think he wants to have each row have the currentletter and the next letter sorted alphabetically.
But, order by 1 in this context is just like "order by 'x'" or "order by <ANY_CONSTANT>"
consider:
ops$tkyte%ORA10GR2> create table t as select * from scott.emp order by hiredate;
Table created.
ops$tkyte%ORA10GR2> select ename, lead(ename) over (order by 1) from scott.emp
2 /
ENAME LEAD(ENAME
---------- ----------
MILLER
MILLER SMITH
SMITH ALLEN
ALLEN WARD
WARD JONES
JONES MARTIN
MARTIN BLAKE
BLAKE CLARK
CLARK KING
KING TURNER
TURNER ADAMS
ADAMS JAMES
JAMES FORD
FORD SCOTT
SCOTT
15 rows selected.
ops$tkyte%ORA10GR2> select ename, lead(ename) over (order by 1) from t
2 /
ENAME LEAD(ENAME
---------- ----------
SMITH
WARD
WARD JONES
JONES BLAKE
BLAKE CLARK
CLARK TURNER
TURNER MARTIN
MARTIN KING
KING FORD
FORD JAMES
JAMES MILLER
MILLER SCOTT
SCOTT ADAMS
ADAMS ALLEN
ALLEN
15 rows selected.
arguably - we have the SAME data (t is a copy of scott.emp after all) but because of the random ordering of the data on disk we get different answers (if you order by CONSTANT, the sorted data can be in ANY order of course!!!)
I think he meant: order by Character
October 30, 2007 - 5pm Central time zone
Reviewer: George Robinson
Tom, Tyer
Indeed the accident happened just like Tom predicted.
I had a table with less than ideal ordering of data, and the result differed from the one intended.
Ordering by 'Character' fixed it.
Nonetheless, excellent solution !
Does Oracle have anything like a list of constants that can be used in place of a subquery?
If not, do you think it would be a useful feature ? e.g.:
SELECT fname, sname
FROM customer, list{'E', 'T', 'A', 'O', 'N', 'I'} a
OUTER JOIN customer a
ON substr(customer.fname,1,1) = a.list
...or some similar syntax.
Regards,
George
Followup November 1, 2007 - 3pm Central time zone:
ops$tkyte%ORA10GR2> create or replace type myVarcharList as table of varchar2(30)
2 /
Type created.
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from table( myVarcharList( 'Hello', 'world', 'how', 'are', 'you' ) );
COLUMN_VALUE
------------------------------
Hello
world
how
are
you
you would need an order by on that (order by column_value) if order was relevant
For George
October 31, 2007 - 3pm Central time zone
Reviewer: SeánMacGC from Ireland
Apologies George, if this is not what you're intending,
But instead of:
SELECT fname, sname
FROM customer, list{'E', 'T', 'A', 'O', 'N', 'I'} a
OUTER JOIN customer a
ON substr(customer.fname,1,1) = a.list
(which actually JOINs to the table customer twice in both an ANSI and non-ANSI way)
Do you not mean something simply like:
SELECT fname, sname
FROM customer
WHERE SUBSTR(customer.fname,1,1) IN ('E', 'T', 'A', 'O', 'N', 'I')
26 Range scans
October 31, 2007 - 5pm Central time zone
Reviewer: George Robinson
Tyler,
Do you have an idea how to merge your query with the other query below so Oracle returns 10 first
fnames >= to each letter of the AlphaBet, using 26 individual concatenated index range scans and 26
separate Count Stopkeys ?
/*---------------------------------------------*/
WITH AlphaBet AS
(
SELECT
CHR((ROWNUM + 64)) AS Character
FROM DUAL
CONNECT BY LEVEL <= 26
),
LetterSets AS
(
SELECT
Character AS CurrentLetter,
LEAD(Character) OVER (ORDER BY Character) AS NextLetter
FROM AlphaBet
ORDER BY Character ASC
)
SELECT *
FROM LetterSets;
/*---------------------------------------------*/
SELECT
i,fname,sname
FROM (
SELECT /*+ INDEX(customer customer(fname,sname,id)) FIRST_ROWS */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,id) i,
fname,
sname
FROM customer
WHERE fname>=LetterSets.currentletter AND fname< LetterSets.nextletter
ORDER BY fname,sname,id
) a
WHERE rownum<=10
/*---------------------------------------------*/
When I try to make a nested query, I always get a full index scan at best.
From the experience with my huge 26-part UNION ALL query, I know that 26 index range scans and
Stopkeys are much faster in this case.
Regards,
George
Followup November 2, 2007 - 11am Central time zone:
George, your original query is the way to do your exact thing. It is "very precise"
Hide it in a view, then it can "look nice" in the code as well.
Query For George
October 31, 2007 - 9pm Central time zone
Reviewer: Tyler from Vancouver
Hey George,
First off, thanks to Tom for pointing out my error in not ordering the LEAD portion properly, wasn't really production ready code :D)
As for incorporating the query to meet your requirement i had envisioned something along the lines of...
WITH AlphaBet AS
(
SELECT
CHR((ROWNUM + 64)) AS Character
FROM DUAL
CONNECT BY LEVEL <= 26
),
LetterSets AS
(
SELECT
Character AS CurrentLetter,
LEAD(Character) OVER (ORDER BY Character ASC) AS NextLetter
FROM AlphaBet
ORDER BY Character ASC
),
FinalResultz AS
(
SELECT
c.fname,
c.sname,
L.currentletter,
ROW_NUMBER() OVER (PARTITION BY c.fname, c.sname, L.currentletter ORDER BY c.fname, c.sname, c.id) AS The_RowNum
FROM customer c, LetterSets L
WHERE c.fname >= L.currentletter
AND (c.fname < L.nextletter OR L.nextletter IS NULL)
)
SELECT *
FROM FinalResultz
WHERE The_RowNum <= 10
ORDER BY currentletter, fname, sname;
You'd need the L.nextletter IS NULL portion because when you're on Z your next letter will be NULL, so you need to account for that.
I don't think it'll be fair to compare the execution paths of this to the original since they will produce different results (your original query had rownum in the "wrong" place) so the 26 range scans with stopkeys probably shouldn't ever have been something you used as your benchmark :D).
Hopefully the query runs (don't have your tables so can't run it here) and produces the correct results in a decent amount of time.
Hope that's useful!
November 1, 2007 - 12pm Central time zone
Reviewer: George Robinson
Tyler,
Thanks for giving it a try :) I didn't think about a three part WITH clause.
Unfortunately this query does not limit the output properly.
For example there should be only:
- 10 records with fname between 'A' and 'B'
- 10 records with fname between 'B' and 'C'
- 10 records with fname between 'C' and 'D'
etc...
Also, the purpose of the 'i' column in my original query ('The_RowNum' in yours) is to disambiguate
and count records having the same 'fname' and 'sname', as definded by the sorting order
'fname,sname,id' taking advantage of the fact that 'id' is unique.
For the record: When posting the original 26-part UNION ALL query, I made a mistake by blindly
copying from an old file (from the times when I did not understand rownum yet)
It should've been:
SELECT * FROM
(
SELECT /*+ INDEX(customer customer(fname,sname,ID)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,ID) i,
fname,
sname,
ID
FROM customer
WHERE fname>='A' AND fname<'B'
ORDER BY fname,sname,ID
)
WHERE rownum <=10
UNION ALL
SELECT * FROM
(
SELECT /*+ INDEX(customer customer(fname,sname,ID)) */
row_number() OVER (PARTITION BY fname,sname ORDER BY fname,sname,ID) i,
fname,
sname,
ID
FROM customer
WHERE fname>='B' AND fname<'C'
ORDER BY fname,sname,ID
)
WHERE rownum <=10
UNION ALL
etc...
etc...
etc...
etc...
etc... 26 times...
Regards,
George
OR and UNION
September 7, 2008 - 3pm Central time zone
Reviewer: A reader
Hi Tom
I have a query like this :
SELECT
cic.inventory_item_id ITEM_ID,
msi.segment1 ITEM_NUMBER,
msi.item_type ITEM_TYPE,
msi.primary_uom_code UOM_CODE,
DECODE (TO_CHAR (msi.planning_make_buy_code), 1, 'M', 2, 'B' ) MAKE_BUY_CODE,
SUBSTR (msi.description, 1, 100) DESCRIPTION,
NVL (cic.material_cost, 0.00000) + NVL (cic.outside_processing_cost, 0.00000) MATERIAL_COST,
NVL (cic.material_overhead_cost, 0.00000) MATERIAL_OVH_COST,
NVL (cic.resource_cost, 0.00000) RESOURCE_COST,
NVL (cic.overhead_cost, 0.00000) OVH_COST, 0.00000 VAR_OVH_COST,
NVL (cic.item_cost, 0.00000) ITEM_COST,
-- :b6 cost_type,
cic.last_update_date LAST_UPD_DATE,
a.itm_id_type PART_FLAG,
a.itm_material_class MATERIAL_CLASS,
a.itm_software_flg SOFTWARE_FLAG,
a.itm_prim_source_fk PRIMARY_SOURCE,
a.trp_cur_tran_price,
a.trp_cur_mmicv ,
a.itm_item_num_pk
from apps.cst_item_costs cic,
apps.mtl_system_items_b msi
,cost_temp a
where cic.organization_id = 2550
and cic.cost_type_id = 1 -- b6
and
( cic.last_update_date >= sysdate-15 or
a.trp_cur_last_updt_dt >= sysdate-15 )
and msi.inventory_item_id = cic.inventory_item_id
and msi.organization_id = cic.organization_id
and msi.segment1 = a.itm_item_num_pk(+)
and msi.item_type = 'GP CONSUMABLES' --b2
and msi.inventory_asset_flag = 'Y'
and msi.costing_enabled_flag = 'Y'
and msi.organization_id = 2550
it takes at least an hour to give the results (200 rows).
when i re-wrote that query like below then it runs in few seconds.
SELECT leading(cic) parallel(cic,4) */
cic.inventory_item_id ITEM_ID,
msi.segment1 ITEM_NUMBER,
msi.item_type ITEM_TYPE,
msi.primary_uom_code UOM_CODE,
DECODE (TO_CHAR (msi.planning_make_buy_code), 1, 'M', 2, 'B' ) MAKE_BUY_CODE,
SUBSTR (msi.description, 1, 100) DESCRIPTION,
NVL (cic.material_cost, 0.00000) + NVL (cic.outside_processing_cost, 0.00000) MATERIAL_COST,
NVL (cic.material_overhead_cost, 0.00000) MATERIAL_OVH_COST,
NVL (cic.resource_cost, 0.00000) RESOURCE_COST,
NVL (cic.overhead_cost, 0.00000) OVH_COST, 0.00000 VAR_OVH_COST,
NVL (cic.item_cost, 0.00000) ITEM_COST,
-- :b6 cost_type,
cic.last_update_date LAST_UPD_DATE,
a.itm_id_type PART_FLAG,
a.itm_material_class MATERIAL_CLASS,
a.itm_software_flg SOFTWARE_FLAG,
a.itm_prim_source_fk PRIMARY_SOURCE,
a.trp_cur_tran_price,
a.trp_cur_mmicv ,
a.itm_item_num_pk
from apps.cst_item_costs cic,
apps.mtl_system_items_b msi
,cost_temp a
where cic.organization_id = 2550
and cic.cost_type_id = 1 -- b6
and cic.last_update_date >= sysdate-15
and msi.inventory_item_id = cic.inventory_item_id
and msi.organization_id = cic.organization_id
and msi.segment1 = a.itm_item_num_pk(+)
and msi.item_type = 'GP CONSUMABLES' --b2
and msi.inventory_asset_flag = 'Y'
and msi.costing_enabled_flag = 'Y'
and msi.organization_id = 2550
union
SELECT
cic.inventory_item_id ITEM_ID,
msi.segment1 ITEM_NUMBER,
msi.item_type ITEM_TYPE,
msi.primary_uom_code UOM_CODE,
DECODE (TO_CHAR (msi.planning_make_buy_code), 1, 'M', 2, 'B' ) MAKE_BUY_CODE,
SUBSTR (msi.description, 1, 100) DESCRIPTION,
NVL (cic.material_cost, 0.00000) + NVL (cic.outside_processing_cost, 0.00000) MATERIAL_COST,
NVL (cic.material_overhead_cost, 0.00000) MATERIAL_OVH_COST,
NVL (cic.resource_cost, 0.00000) RESOURCE_COST,
NVL (cic.overhead_cost, 0.00000) OVH_COST, 0.00000 VAR_OVH_COST,
NVL (cic.item_cost, 0.00000) ITEM_COST,
-- :b6 cost_type,
cic.last_update_date LAST_UPD_DATE,
a.itm_id_type PART_FLAG,
a.itm_material_class MATERIAL_CLASS,
a.itm_software_flg SOFTWARE_FLAG,
a.itm_prim_source_fk PRIMARY_SOURCE,
a.trp_cur_tran_price,
a.trp_cur_mmicv ,
a.itm_item_num_pk
from apps.cst_item_costs cic,
apps.mtl_system_items_b msi
,cost_temp a
where cic.organization_id = 2550
and cic.cost_type_id = 1 -- b6
and a.trp_cur_last_updt_dt >= sysdate-15
and msi.inventory_item_id = cic.inventory_item_id
and msi.organization_id = cic.organization_id
and msi.segment1 = a.itm_item_num_pk(+)
and msi.item_type = 'GP CONSUMABLES' --b2
and msi.inventory_asset_flag = 'Y'
and msi.costing_enabled_flag = 'Y'
and msi.organization_id = 2550
Few facts -the table cic and msi have millions of rows.
the table cost_temp has 500K rows.
there are indexes
cic - > inventory_item_id,organization_id,cost_type_id
msi - > inventory_item_id,organization_id
msi - > segment1
cost_temp ->itm_item_num_pk
cost_temp -> trp_cur_last_updt_dt
there is no index on last_update_date colum of cic.
my question is :
please note that the numner rows last_update_date >= sysdate-15 will return will not be more than
200
and similary a.trp_cur_last_updt_dt >= sysdate-15 will also not return more than 100 rows..
sometime trp_cur_last_updt_date can be equal to cic.last_update_date...that is why i used UNION.
questiones :
1. do you think this is a valid approach.
2. is there any other way to write the original query.
thanks
PS - i will post the tkprof tommorow.
Followup September 8, 2008 - 3pm Central time zone:
ps: about the tkprof (Please don't, it is, well, too large to look at as a review/followup and would take create tables, indexing schemes, etc etc etc etc - and that is not appropriate here)
September 8, 2008 - 4pm Central time zone
Reviewer: A reader
Tom,
sorry about PS part.
Can i make it a just concept question and ask you here.
like the example above..
if i have 2 big tables , and then I am joining them on the indexed colums and then I have
a condtion like this
(a.last_update_date >= sysdate-1 OR b.last_update_date >= sysdate-1)
a.last_update_date and b.last_update_date are most selective filters used as the filetr.
so how would oracle handles this..considering both the data fields are not indexed.
is OR alwaya equal to UNION.
in my case - answer I found is NO.is it due to data.
can you please elobarete what could be that data condition.
i wish i could post it as a new question but then it is really hard to find the opportunity to post
even i am in India .
Regards
Followup September 9, 2008 - 7am Central time zone:
a)
last_update_date and b.last_update_date are most selective filters used as
the filetr.
so how would oracle handles this..considering both the data fields are not
indexed.
it would have to join AND THEN filter.
b) a UNION adds "distinct"
since
select DISTINCT .... from ....
is not the same as
select .... from ....
union and or are in general not "swappable"
ops$tkyte%ORA11GR1> select * from t;
X
----------
5
5
6
6
7
ops$tkyte%ORA11GR1> select * from t where x = 5 or x = 6;
X
----------
5
5
6
6
ops$tkyte%ORA11GR1> select * from t where x = 5 union select * from t where x = 6;
X
----------
5
6
c)
i am in India . where most of the questions actually seem to originate. I don't work on a fixed schedule either and cover many time zones....