Given a data set for example as follows:
PERSON - the person allocating funds (key field)
EFFECTIVE DATE - the effective date of the percentage distribution
RECIPIENT - the person receiving a certain percentage of the funds
PERCENTAGE - the percentage that the person receives
(so for any given effective date, a person allocates a total of 100% to any given combination of recipients)
PERSON EFFECTIVE DATE RECIPIENT PERCENTAGE
---------- --------------- ------------- -----------
MONICA 2015-10-01 BARNEY 100% +
MONICA 2015-09-01 BARNEY 50% +
MONICA 2015-09-01 MARSHALL 20% +
MONICA 2015-09-01 LILY 30% +
MONICA 2015-08-01 ROBIN 50%
MONICA 2015-08-01 TED 50%
CHANDLER 2015-10-01 ROBIN 50% +
CHANDLER 2015-10-01 LILY 50% +
CHANDLER 2015-07-10 BARNEY 50% +
CHANDLER 2015-07-10 MARSHALL 50% +
CHANDLER 2015-06-01 ROBIN 50%
CHANDLER 2015-06-01 LILY 50%
CHANDLER 2015-04-10 BARNEY 50%
CHANDLER 2015-04-10 MARSHALL 50%
ROSS 2015-10-01 MARSHALL 100% +
ROSS 2015-09-15 BARNEY 100% +
PHOEBE 2015-10-01 MARSHALL 20% +
PHOEBE 2015-10-01 BARNEY 20% +
PHOEBE 2015-10-01 LILY 20% +
PHOEBE 2015-10-01 ROBIN 20% +
PHOEBE 2015-10-01 TED 20% +
PHOEBE 2015-09-01 MARSHALL 100% +
PHOEBE 2015-08-01 BARNEY 100%
PHOEBE 2015-07-01 LILY 100%
PHOEBE 2015-06-01 ROBIN 100%
PHOEBE 2015-05-01 TED 100%
How can I construct a single SQL query that would return only the two latest sets of allocations per person at once (all of the records marked with a "+" above)? This is so that I could process the data into a display that says, for example, that:
"Monica changed allocation FROM 50% for Barney, 20% for Marshall, and 30% for Lily TO 100% for Barney."
"Chandler changed allocation FROM 50% for Barney, 50% for Marshall TO 50% for Robin, 50% for Lily."
"Ross changed allocation FROM 100% for Barney TO 100% for Marshall."
"Phoebe changed allocation FROM 100% for Marshall TO 20% for Marshall, 20% for Lily, 20% for Barney, 20% for Robin, 20% for Ted."
Here is one method using where
:
select t.*
from t
where t.effective_date in (select t2.effective_date
from t t2
where t2.person = t.person
group by t2.effective_date
order by t2.effective_date desc
fetch first 2 rows only
);
You can also do this with dense_rank()
:
select t.*
from (select t.*,
dense_rank() over (partition by person order by effective_date desc) as seqnum
from t
) t
where seqnum <= 2;