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)
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)