Select TOP N and BOTTOM N - oracle

Trying to fetch top n bottom n rows. Though it gives me result but, it takes lot of time. I believe it scans table twice.
Code used:
WITH TI AS
(SELECT * FROM
(SELECT
Column1,
Column2,
Colmn3
FROM TABLE
ORDER BY DESC
)
WHERE ROWNUM<=5),
T2 AS
(SELECT * FROM
(SELECT
Column1,
Column2,
Colmn3
FROM TABLE
ORDER BY ASC
)
WHERE ROWNUM<=5)
SELECT * FROM T1
UNION ALL
SELECT * FROM T2
How can i fetch this in more faster way??
Considering that tables are updated regularly.

The best way to solve this problem depends in part on your Oracle version. Here is a very simple (and, I suspect, very efficient) solution using the match_recognize clause, added in version 12.1.
I illustrate it using the EMPLOYEES table in the standard HR schema, ordering by SALARY. The only trick here is to select the top and bottom five rows, and to ignore everything in between; that (the "ignoring") is what the {- ... -} operator does in the pattern sub-clause.
select employee_id, first_name, last_name, salary
from hr.employees
match_recognize(
order by salary desc
all rows per match
pattern ( a{5} {- a* -} a{5} )
define a as 0 = 0 -- For reasons known only to Oracle, DEFINE is required.
);
EMPLOYEE_ID FIRST_NAME LAST_NAME SALARY
----------- -------------------- ------------------------- ----------
100 Steven King 24000
101 Neena Kochhar 17000
102 Lex De Haan 17000
145 John Russell 14000
146 Karen Partners 13500
135 Ki Gee 2400
127 James Landry 2400
136 Hazel Philtanker 2200
128 Steven Markle 2200
132 TJ Olson 2100

You can combine into a single query and a single pass over the table using analytic functions, generating two pseudocolumns in this case:
select column1, column2, column3,
row_number() over (order by column1 desc) rn_desc,
row_number() over (order by column1 asc) rn_asc
from your_table;
and then filtering using that query as an inline view (or CTE):
select column1, column2, column3
from (
select column1, column2, column3,
row_number() over (order by column1 desc) as rn_desc,
row_number() over (order by column1 asc) as rn_asc
from your_table
)
where rn_desc <=5
or rn_asc <= 5;
I've assumed your ordering is on column1, and picked your_table as a table name as you didn't include that either, so change as appropriate. Depending on how you want to handle ties, you might want to use the rank() or dense_rank() functions instead.
From #mathguy's comment, this may well perform better:
select column1, column2, column3
from (
select column1, column2, column3,
row_number() over (order by column1 desc) as rn,
count(*) over () as cnt
from your_table
)
where rn <=5
or cnt - rn < 5;

Related

Oracle How to make SELECT INSIDE A SELECT work?

Just wondering why the following select isn't working:
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
ERROR at line 2: ORA-00904: "A"."CUST": invalid identifier
Important to mention:
TABLE1 has as fields FIELD1, CUST.
TABLE2 has as fields PCN, PRIORITY, CUST.
Thanks in advance.
Your query shouldn't give you that error message, on when you remove the outer qiery this would happen
CREATE tABLE TABLE1 (FIELD1 int, CUST int)
INSERT INTO TABLE1 VALUES(1,1)
1 rows affected
CREATE TABLE TABLE2 (PCN int, PRIORITY int, CUST int)
INSERT INTO TABLE2 VALUES (1,1,1)
1 rows affected
SELECT
A.FIELD1
, (SELECT PCN FROM (select B.PRIORITY, B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY)
WHERE ROWNUM = 1) AS PCN
FROM TABLE1 A;
FIELD1
PCN
1
1
fiddle
You can't nest inline selects (more than one level) without losing the ability of the inner nested selects being able to reference the parent block. So your query on TABLE2 cannot see the columns from TABLE1 because of this nesting.
Try this:
SELECT a.field1,
pcn.pcn
FROM table1 a,
(SELECT b.cust,
b.priority,
b.pcn,
ROW_NUMBER() OVER (PARTITION BY b.cust ORDER BY b.priority DESC) seq
FROM table2 b) pcn
WHERE a.cust = pcn.cust(+)
AND pcn.seq(+) = 1
That will work well for report queries. If you end up adding a filter on a specific customer, then you would be better off using OUTER APPLY if you have a recent-enough version of Oracle that supports that.
You could try this:
SELECT
A.FIELD1
, (SELECT B.PCN
from
TABLE2 B
WHERE B.CUST= A.CUST
ORDER BY B.PRIORITY
FETCH FIRST 1 ROWS ONLY) AS PCN
FROM TABLE1 A;
FETCH FIRST 1 ROWS ONLY gets you the first ordered record. Works on 12c and up and supports nesting, and no 2nd subquery needed.
Yet another option might be a CTE.
Sample data:
SQL> with
2 table1 (field1, cust) as
3 (select 1, 100 from dual union all
4 select 2, 200 from dual
5 ),
6 table2 (pcn, priority, cust) as
7 (select 10, 1, 100 from dual union all
8 select 20, 2, 100 from dual union all
9 select 30, 1, 200 from dual
10 ),
Query begins here. Rank rows by priority, and then fetch the ones that rank as the highest (line #20):
11 temp as
12 (select a.field1,
13 b.pcn,
14 rank() over (partition by a.field1 order by b.priority desc) rnk
15 from table1 a join table2 b on a.cust = b.cust
16 )
17 select field1,
18 pcn
19 from temp
20 where rnk = 1;
FIELD1 PCN
---------- ----------
1 20
2 30
SQL>
You may use first aggregate function to achieve the same (assuming that you have completely deterministic order by) functionality without nested subquery:
select
a.field1
, (
select max(b.pcn) keep(dense_rank first order by b.priority)
from table2 b
where b.cust = a.cust
) as pcn
from table1 a
which for this sample data
insert into table1 values(1,1);
insert into table1 values(2,2);
insert into table2 values(1,1,1);
insert into table2 values(2,2,1)
returns
FIELD1
PCN
1
1
2
(null)
SQL fiddle

ORACLE SQL REPEAT SAME QUERY

It seems that I haven't been clear enough.
The query that seems to work is:
Select ((Select count (table1.id) from table1 where table1.code=2 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=2 and table1.name=7) as ‘name7’)
From table.1;
union
Select ((Select count (table1.id) from table1 where table1.code=5 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=5 and table1.name=7) as ‘name7’)
From table.1;
union
Select ((Select count (table1.id) from table1 where table1.code=15 and table1.name=5) as ‘name5’,
(Select count (table1.id) from table1 where table1.code=15 and table1.name=7) as ‘name7’)
From table.1;
….
Which gets an outcome like this:
name5 name7
52 47
42 84
61 11
My problem is that the table1.code has a thousand and more values other than 2,5 and 15 and I can not repeat a union statement for so many times.
Well it seems like you actually just want to group by the values in the code column, and you can use IN or EXISTS
select count(table1.id) as theCount, table1.code as theCode
from table1 where table1.code in ('code a','code b', 'etc...')
group by table1.code;
the output would be
theCount||theCode
code a || 8074
code b || 34
etc... || 9575
or something like that but with non notional numbers for counts
HTH
You could try to list all values in a nested select listing integers from a to your value, e.g. 100, like that:
select count table1.id from table1 one where table1.code in (
select rownum from all_objects where rownum < 100
);
or if you don't want to start at "1":
select count table1.id from table1 one where table1.code in (
select rownum n from dual connect by level 10 where n>3
);

return null if no rows found oracle query with IN clause

I have a table with three columns.
I query that table with IN clause.
select column1 from table1 where column1 in (1,2,3) order by column2, column3
The table1 contains only values 1 and 2 in column1. I want to return the not available value also in my result, and that should be sorted in the bottom.
example data
column1 column 2 column 3
1 100 11
2 101 50
output, the not available values should be in the last.
column1 column 2 column 3
1 100 11
2 101 50
3 null null
I tried with subquery with NVL, like select nvl((select.. in(1,2,3)),null) from dual, due to IN Clause, I am getting single row subquery returns more than one row issue, which is expected.
Also tried with the union but nothing works. Great if any help. Thanks
I think you can do it with a union all:
select column1 from table1 where column1 in (1,2,3) order by column2, column3
union all
select null from table1 where column1 not in (1,2,3) order by column2, column3
If you can't take 1,2,3 values from another table you can try with:
with t1 as (
select col1,col2,col3
from tab1
where cod_flusso in ('1','2','3')),
t2 as (
select '1' as col1,null,null
from dual
union
select '2',null,null
from dual
union
select '3',null,null
from dual)
select t2.col1,col2,col3
from t2
left outer join t1
on t1.col1= t2.col1
It's better if you can store 1,2,3 values in a second table, then use left outer join.

How to get only the one employee name from each department if the max salary is same from more than one employee

I am using below query:
SELECT rownum, job_id, employee_id, first_name, last_name, phone_number, salary
FROM employees OUTER
WHERE salary =
(
SELECT MAX(salary)
FROM employees
WHERE job_id = OUTER.job_id
GROUP BY job_id
)
AND ROWNUM < 6;
And getting below result:
1 AD_PRES 100 Steven King 515.123.4567 24000
2 AD_VP 101 Neena Kochhar 515.123.4568 17000
3 AD_VP 102 Lex De Haan 515.123.4569 17000
4 IT_PROG 103 Alexander Hunold 590.423.4567 9000
5 FI_MGR 108 Nancy Greenberg 515.124.4569 12008
But the problem is I want only one name for each JOB_ID. And that should be decided by alphabetical preference in FIRST_NAME.
One option would be to use a subquery which contains the job_id for the first name you want to retain. I wrapped your original query in a common table expression to make it more readable.
WITH t AS
(
SELECT rownum, job_id, employee_id, first_name, last_name,
phone_number, salary
FROM employees OUTER
WHERE salary =
(
SELECT MAX(salary)
FROM employees
WHERE job_id = OUTER.job_id
GROUP BY job_id
)
AND ROWNUM < 6;
)
SELECT t1.rownum, t1.job_id, t1.employee_id, t1.first_name, t1.last_name,
t1.phone_number, t1.salary
FROM t t1
INNER JOIN
(
SELECT job_id, MAX(first_name) AS max_name
FROM t
GROUP BY job_id
) t2
ON t1.job_id = t2.job_id AND t1.first_name = t2.max_name
Use analytic functions:
select * from (
SELECT job_id, employee_id, first_name, last_name, phone_number, salary,
RANK() over (
job_id
order by
salary desc,
first_name,
employee_id -- adding employe_id breaks ties in the ordering
) as rnk
FROM employees
) where rnk = 1;
This will probably also perform better then the subselect.
All this is written without a database at hand, so it might/will contain typos

Optimisation of an oracle query

I'm trying to make my query run as quickly as possible but i'm struggling to get it under 5 seconds.
I think it's because i'm referencing two linked databases
Here's my query
select column2, column3, column4
from table1#dev
where column1 in (
select distinct column2
from table2#dev
where column3 > 0
)
order by column1
Is there a way to optimise this query any more?
I've tried using join but it seems to make the query run longer
Thanks in advance
EDIT
From further investigation the DRIVING_SITE makes it run very quick like this
select /*+ DRIVING_SITE(table1) */ t1.column2, t1.column3, t1.column4
from table1#dev t1, table2#dev t2
WHERE t2.column3 > 0
But as soon as I add the distinct column2 in it makes it run really slow
First, no need for distinct. The query can be written as:
select *
from table1#dev
where column1 in (
select column2
from table2#dev
where column3 > 0
)
order by column1
Second, there are (at least) two more ways to write it. Either with JOIN:
select t1.*
from table1#dev t1
join table2#dev t2
where t2.column2 = t1.column1
and t2.column3 > 0
group by
t1.id, t1.column1, ...
order by t1.column1
or (my preference) with EXISTS:
select t1.*
from table1#dev t1
where exists
( select *
from table2#dev
where t2.column2 = t1.column1
and t2.column3 > 0
)
order by column1
In any case, you should check the execution plans for all of them.
I would expect performance to be best if you have an index on table1.column1 and for table2, either an index on column2 or a composite index on (column3, column2)
I agree with Shannon above , but are you able to create a view on the dev server ?
Also select * is a bit naughty - it is better to name the fields you really want. For very large datasets that will give you a performance improvement too.
Am I missing something in believing that this will work?
select t1.*
from table1 t1, table2 t2
where t1.column1 = t2.column2(+)
and t2.column3 > 0;

Resources