Selecting top n elements of a group in Oracle
Question
I have an Oracle table which has a name,value,time columns.Basically the table is for logging purposes to store what are the changes made to a particular name,what was the previous value and what time the change was made.
I need to formulate a query to fetch the top n changes for a particular name,and the output should have all the names in the table. Any help/suggesstions?
Edit:
Name Value Time Harish Pass 1-Nov-2011 Ravi Fail 2-Nov-2011 Harish Absent 31-Oct-2011 Harish Attended 31-Aug-2011 Harish Present 31-Jul-2011
I need to select details of Harish on 1st Nov,Oct 31st,31st Aug and Ravi.
Solution
Is this what you are after?
My test set-up:
SQL> alter session set nls_date_format = 'DD-Mon-YYYY HH24:Mi:SS';
Session altered.
SQL> drop table so_test;
Table dropped.
SQL> create table so_test (
2 n varchar2(32)
3 , v varchar2(32)
4 , t date );
Table created.
SQL>
SQL> insert into so_test values ( 'X' , 'Test1', to_date('01-Jan-2011 12:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL> insert into so_test values ( 'X' , 'Test2', to_date('01-Jan-2011 13:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL> insert into so_test values ( 'X' , 'Test3', to_date('01-Jan-2011 14:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL> insert into so_test values ( 'Y' , 'Test5', to_date('02-Jan-2011 12:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL> insert into so_test values ( 'Y' , 'Test6', to_date('03-Jan-2011 12:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL> insert into so_test values ( 'Y' , 'Test7', to_date('04-Jan-2011 12:00:00','DD-Mon-YYYY HH24:Mi:SS') );
1 row created.
SQL>
Here is the query:
SQL> select n,v,t from (
2 select n, v , t , rank() over ( partition by n order by t desc) r
3 from so_test
4 ) where r <= 2;
N V T
-------------------------------- -------------------------------- --------------------
X Test3 01-Jan-2011 14:00:00
X Test2 01-Jan-2011 13:00:00
Y Test7 04-Jan-2011 12:00:00
Y Test6 03-Jan-2011 12:00:00
SQL>
OTHER TIPS
select * from (select name, value,
time, ROW_NUMBER OVER (PARTITION BY name ORDER BY name) change_no
from table )
where change_no <= 100 AND name ="abc"
ORDER BY TIME
Assuming name remain same, and changes are made to the "value".
Matthew Watson's answer is not always valid, if ordering column is duplicated, query returns more than "r" rows. The solution is to concatenate an unique value to ordering column, it can be used a primary key of the table. Example:
SELECT * FROM (
SELECT
t.*,
RANK() OVER (PARTITION BY object_type ORDER BY (to_char(created,'YYYYMMDDHH24MISS') || object_id) DESC) rank
FROM ALL_OBJECTS t
)
WHERE rank <= 3
I offer my quick fix. This query groups and counts in descending order (in the inner query). The Outer query simply allows you to define the number of rows to display (:pRows).
Select * from
(Select
group_field,
count(*) Cnt
From
record_source(s)
Where
Conditions
Order by
Count(*) Desc) x
Where
rownum < :pRows+1;
select name, VALUE, TIMESTAMP
from (select name, VALUE, TIMESTAMP, rank() over (partition by NAME order by TIMESTAMP DESC) rank
from logs)
where rank <= 3