0

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.

Novice_Techie
  • 426
  • 3
  • 10
  • 21
  • What are you ordering by? Which version of Oracle; and what do you want to do if there are ties? (Say, 6 rows with the same.. whatever you're ordering by. Show all six? Pick five at random?) Sample data, a working query, and expected results would be helpful. – Alex Poole Nov 21 '17 at 19:04
  • Performance questions should include `EXPLAIN ANALYZE` and some information about table size, index, current time performance, desire time, etc. `Slow` is a relative term and we need a real value to compare. – Juan Carlos Oropeza Nov 21 '17 at 19:09
  • `ORDER BY DESC`? `ORDER BY ASC`? What does that mean? –  Nov 21 '17 at 19:11
  • What Oracle version? (Run `select * from v$version` to find out.) –  Nov 21 '17 at 19:23
  • It's 12 C enterprise version. – Novice_Techie Nov 21 '17 at 21:00
  • Please note, the c in 12c is just a marketing decoration (it stands for "cloud") and it does not help distinguish the sub-version. Most Oracle versions have sub-versions 1 and 2 (the current Oracle version is 12.2, I use 12.1 on my laptop). The best info is the FULL version number - mine is 12.1.0.2.0 - as reported in `v$version`. In this case it doesn't matter because `match_recognize` was added in 12.1, but for other features the full version number may matter. –  Nov 21 '17 at 21:52
  • It's 12.1.0.2.0 – Novice_Techie Nov 21 '17 at 21:55

2 Answers2

2

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
0

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;
Alex Poole
  • 183,384
  • 11
  • 179
  • 318
  • Alas, I believe Oracle is not smart enough to understand that the ordering is the same; even though it reads all rows one time, it orders them twice, which will take a lot of time. Better to use `row_number()` (with one ordering only) and analytic `count()` (with no ordering) and use both in the WHERE clause. –  Nov 21 '17 at 19:13
  • 2
    @JuanCarlosOropeza - In the WHERE clause, you can check for `rn <= 5 or rn >= cnt - 5`. –  Nov 21 '17 at 19:14
  • @Alex did you get a party from your 100k? if you didnt let me say gz for that ;). – Juan Carlos Oropeza Nov 21 '17 at 19:15
  • @JuanCarlosOropeza - thanks - I got a mug and a t-shirt *8-) – Alex Poole Nov 21 '17 at 19:16
  • Novice_Techie. If performance is an issue, add index for `column1` or whatever column you are sorting for. – Juan Carlos Oropeza Nov 21 '17 at 19:17
  • @mathguy - I've used both approaches but I've never compared them with a lot of data, so it would be interesting to find out how they behave. I can't imagine the count version would be *slower* anyway, so I'll have to remember to favour that one... – Alex Poole Nov 21 '17 at 19:18
  • Thanks! @AlexPoole and mathguy. I will try both approaches and share the results. – Novice_Techie Nov 21 '17 at 21:05