Reputation: 7946
I have a fairly complex Oracle query, getting data from multiple tables. In one of the joins, I want the best record, if there is one. Therefore, a left outer join. There is a start date field, so for most records, getting the max start date will get me the best record. However, occasionally there are records that have the same start date. In that case, there is also a status field. However, the best status value is not a min or a max. '20' is best, '05' or '40' are ok, and '70' is worst. How can I set up the query to find the best option when multiple records are returned?
So, if I have the following data
Table1 Table2
ID otherData ID date status otherData
1 stuffa 1 jan-1-13 20 stuff93
2 stuff3
3 stuff398 3 jan-2-13 20 stuff92
3 jan-2-13 70 stuff38
3 dec-3-12 20 stuff843
I will be able to query and get the following:
1 stuffa jan-1-13 20 stuff93
2 stuff3
3 stuff398 jan-2-13 20 stuff92
Right now, my query is as follows, which gets a second record 3 with the 70 status:
select *
from table1 t1
left outer join
(select *
from table2 t2a
where t2a.date = (select max(t2b.date)
from table2 t2b
where t2b.id = t2a.id)
) t2
on (t2.id = t1.id)
Is there a way to set an ordered enumeration or something like that within a select statement? Something like
rank() over ( partition by status order by ('20','05','40','70') rank
Upvotes: 0
Views: 128
Reputation: 191415
You could do something like:
select t1.id, t1.otherdata, t2.dt, t2.status, t2.otherdata
from table1 t1
left outer join (
select t2a.*,
row_number() over (partition by id order by dt desc,
case status
when '20' then 1
when '05' then 2
when '40' then 3
when '70' then 4
else 5 end) as rn
from table2 t2a
) t2 on t2.id = t1.id and t2.rn = 1
order by t1.id;
This assumes you want a single hit even if there are two with the same status; which of the two you get is indeterminate. If you wanted both you could use rank()
instead. Either way you're assigning a rank to each record based on the date (descending, since you want the max) and your own order for the status values, and then only ever picking the highest ranked in the join condition.
With data set up as:
create table table1(id number, otherdata varchar2(10));
create table table2(id number, dt date, status varchar2(2), otherdata varchar2(10));
insert into table1 values(1, 'stuffa');
insert into table1 values(2, 'stuff3');
insert into table1 values(3, 'stuff398');
insert into table2 values(1, date '2013-01-01', '20', 'stuff93');
insert into table2 values(3, date '2013-01-02', '20', 'stuff92');
insert into table2 values(3, date '2013-01-02', '70', 'stuff38');
insert into table2 values(3, date '2012-12-03', '20', 'stuff843');
... this gives:
ID OTHERDATA DT STATUS OTHERDATA
---------- ---------- --------- ------ ----------
1 stuffa 01-JAN-13 20 stuff93
2 stuff3
3 stuff398 02-JAN-13 20 stuff92
Upvotes: 0
Reputation: 21993
add the status to the order by like this;
select *
from (select t1.id, t1.otherdata otherdatat1, t2.date, t2.status, t2.otherdata otherdatat2,
rank() over (partition by t1.id order by t2.date desc,
case t2.status
when '20' then 1
when '05' then 2
when '40' then 3
when '70' then 4
else 5
end) rnk
from table1 t1
left outer join table2 t2
on t1.id = t2.id)
where rnk = 1;
Upvotes: 1
Reputation: 33381
If the ordered enumeration has few elements you can use this
........ order by
CASE status WHEN '20' THEN 1
WHEN '05' THEN 2
WHEN '40' THEN 3
WHEN '70' THEN 4
END) rank
Upvotes: 1