Generate between two dates in different rows SQL - sql

I am working on generating lines depending on the days (a row by day) between two dates. I would have in the first row the first date and in the second row the second date but it all depends on the ID and the money. I consider it is better to show you with an example:
The origin table:
My target:
Could be possible with a loop?
Thank you very much.

You can use a recursive sub-query factoring clause:
WITH dt_range ( id, dt, next_dt, money ) AS (
SELECT id,
dt,
LEAD(dt) OVER (PARTITION BY id ORDER BY dt),
money
FROM table_name
UNION ALL
SELECT id,
dt + INTERVAL '1' DAY,
next_dt,
money
FROM dt_range
WHERE dt + INTERVAL '1' DAY < next_dt
)
SEARCH DEPTH FIRST BY dt SET dt_order
SELECT id, dt, money FROM dt_range;
Which, for your sample data:
CREATE TABLE table_name ( id, dt, money ) AS
SELECT 500, DATE '2017-02-23', 3500 FROM DUAL UNION ALL
SELECT 500, DATE '2017-02-26', 35000 FROM DUAL UNION ALL
SELECT 500, DATE '2017-02-28', 50000 FROM DUAL UNION ALL
SELECT 200, DATE '2020-05-01', 8888 FROM DUAL UNION ALL
SELECT 200, DATE '2020-05-05', 999 FROM DUAL UNION ALL
SELECT 200, DATE '2020-05-09', 1000 FROM DUAL;
Outputs:
ID
DT
MONEY
500
23-FEB-17
3500
500
24-FEB-17
3500
500
25-FEB-17
3500
500
26-FEB-17
35000
500
27-FEB-17
35000
500
28-FEB-17
50000
200
01-MAY-20
8888
200
02-MAY-20
8888
200
03-MAY-20
8888
200
04-MAY-20
8888
200
05-MAY-20
999
200
06-MAY-20
999
200
07-MAY-20
999
200
08-MAY-20
999
200
09-MAY-20
1000
db<>fiddle here
If you are using Oracle 11g then it has bugs iterating over dates; this can be easily fixed by iterating over a number and then adding it to a date (rather than iterating directly on the date):
WITH dt_range ( id, dt, offset, next_dt, money ) AS (
SELECT id,
dt,
0,
LEAD(dt) OVER (PARTITION BY id ORDER BY dt),
money
FROM table_name
UNION ALL
SELECT id,
dt,
offset + 1,
next_dt,
money
FROM dt_range
WHERE dt + offset + 1 < next_dt
)
SEARCH DEPTH FIRST BY dt SET dt_order
SELECT id, dt + offset AS dt, money FROM dt_range;
db<>fiddle here

for example:
with
simple_data( id,dates,money) as
(
select 500, date '2017-02-23', 3500 from dual union all
select 500, date '2017-02-26', 35000 from dual union all
select 500, date '2017-02-28', 50000 from dual union all
select 200, date '2020-05-01', 8888 from dual union all
select 200, date '2020-05-05', 999 from dual union all
select 200, date '2020-05-09', 1000 from dual
)
,step1 as
(
select
sd.id,
sd.money,
sd.dates,
lead (dates,1,dates) over(partition by id order by dates)-1 lead_dts
from simple_data sd
)
select
st1.id,
st1.dates + to_number(t.column_value)-1 as dates,
st1.money
from step1 st1,table(cast(multiset(select level from dual connect by level<= st1.lead_dts-st1.dates+1) as ora_mining_varchar2_nt)) t
order by id desc,dates;
----OR------
with
simple_data( id,dates,money) as
(
select 500, date '2017-02-23', 3500 from dual union all
select 500, date '2017-02-26', 35000 from dual union all
select 500, date '2017-02-28', 50000 from dual union all
select 200, date '2020-05-01', 8888 from dual union all
select 200, date '2020-05-05', 999 from dual union all
select 200, date '2020-05-09', 1000 from dual
)
,step1 as
(
select
sd.id,
sd.money,
sd.dates,
lag (dates,1,dates) over(partition by id order by dates desc) lag_dts
from simple_data sd
)
select
st1.id,
st1.lag_dts - to_number(t.column_value) as dates,
st1.money
from step1 st1,table(cast(multiset(select st1.lag_dts-st1.dates - level +1 from dual connect by level<= st1.lag_dts-st1.dates) as ora_mining_varchar2_nt)) t
order by id desc,dates;
12c and later:
with
simple_data( id,dates,money) as
(
select 500, date '2017-02-23', 3500 from dual union all
select 500, date '2017-02-26', 35000 from dual union all
select 500, date '2017-02-28', 50000 from dual union all
select 200, date '2020-05-01', 8888 from dual union all
select 200, date '2020-05-05', 999 from dual union all
select 200, date '2020-05-09', 1000 from dual
)
,step1 as
(
select
sd.id,
sd.money,
sd.dates,
lead (dates,1,dates) over(partition by id order by dates)-1 lead_dts
from simple_data sd
)
select
st1.id,
st1.dates + t.lvl-1 as dates,
st1.money
from step1 st1,lateral(select level lvl from dual connect by level<= st1.lead_dts-st1.dates+1) t
order by id desc,dates;
---OR-----
with
simple_data( id,dates,money) as
(
select 500, date '2017-02-23', 3500 from dual union all
select 500, date '2017-02-26', 35000 from dual union all
select 500, date '2017-02-28', 50000 from dual union all
select 200, date '2020-05-01', 8888 from dual union all
select 200, date '2020-05-05', 999 from dual union all
select 200, date '2020-05-09', 1000 from dual
)
,step1 as
(
select
sd.id,
sd.money,
sd.dates,
lag (dates,1,dates) over(partition by id order by dates desc) lag_dts
from simple_data sd
)
select
st1.id,
st1.lag_dts - t.lvl as dates,
st1.money
from step1 st1,lateral(select st1.lag_dts-st1.dates - level +1 lvl from dual connect by level<= st1.lag_dts-st1.dates ) t
order by id desc,dates;

Related

Oracle list only records that changed

I have the following code that produces a table as shown in the image:
with test (code, datum) as
(select 600, date '2018-02-01' from dual union all
select 600, date '2018-02-02' from dual union all
select 0, date '2018-02-03' from dual union all
select 0, date '2018-02-04' from dual union all
select 0, date '2018-02-05' from dual union all
select 600, date '2018-02-06' from dual union all
select 600, date '2018-02-07' from dual union all
select 0, date '2018-02-08' from dual union all
select 0, date '2018-02-09' from dual
)
select * from test;
I have tried the following, but does not return what I need.
select * from (
select test.*, min(datum) over (partition by code order by code) as min_date,
max(datum) over (partition by code order by code) as max_date
from test) where min_date = datum;
What I would like to achieve is list only the records where a change occurs on the 'code' column (before and after record where the change occurs).
So the result set should look like this:
02/FEB/18 00:00:00 600
03/FEB/18 00:00:00 0
05/FEB/18 00:00:00 0
06/FEB/18 00:00:00 600
07/FEB/18 00:00:00 600
08/FEB/18 00:00:00 0
I referenced this question, but it does not address the same issue I have.
question
Any help appreciated, thank you.
UPDATE:
This is closer to what I would like to achieve. I can list all rows where columns code and change are not the same. However, I need to list the record after where these values are different as well.
with test (code, datum) as
(select 600, date '2018-02-01' from dual union all
select 600, date '2018-02-02' from dual union all
select 0, date '2018-02-03' from dual union all
select 0, date '2018-02-04' from dual union all
select 0, date '2018-02-05' from dual union all
select 600, date '2018-02-06' from dual union all
select 600, date '2018-02-07' from dual union all
select 0, date '2018-02-08' from dual union all
select 0, date '2018-02-09' from dual
)
,y1 as (
select test.datum, test.code, lead(code) over (order by datum) as change
from test
)
select * from y1;
The final result set should contain the highlighted rows only.
UPDATE 2:
I think I may have got it right, still need to verify but this seems to work:
with test (code, datum) as
(select 600, date '2018-02-01' from dual union all
select 600, date '2018-02-02' from dual union all
select 0, date '2018-02-03' from dual union all
select 0, date '2018-02-04' from dual union all
select 0, date '2018-02-05' from dual union all
select 600, date '2018-02-06' from dual union all
select 600, date '2018-02-07' from dual union all
select 0, date '2018-02-08' from dual union all
select 0, date '2018-02-09' from dual
)
,y1 as (
select test.datum, test.code, lag(nvl(code,code)) over (order by datum) as after, lead(nvl(code,code)) over (order by datum) as before
from test
)
select * from y1 where code != before or code != after;
Not sure if this would help i couldnot see any relevance to sort out the expected output in your question.
with test (code, datum) as
(select 600, date '2018-02-01' from dual union all
select 600, date '2018-02-02' from dual union all
select 0, date '2018-02-03' from dual union all
select 0, date '2018-02-04' from dual union all
select 0, date '2018-02-05' from dual union all
select 600, date '2018-02-06' from dual union all
select 600, date '2018-02-07' from dual union all
select 0, date '2018-02-08' from dual union all
select 0, date '2018-02-09' from dual
)
,y1 as (
select test.datum, test.code, lead(code) over (order by datum) as change
from test
UNION
select test.datum, test.code, lag(code) over (order by datum) as change
from test
)
select * from y1
where change = 600;
The following script produced the expected result set:
with test (code, datum) as
(select 600, date '2018-02-01' from dual union all
select 600, date '2018-02-02' from dual union all
select 0, date '2018-02-03' from dual union all
select 0, date '2018-02-04' from dual union all
select 0, date '2018-02-05' from dual union all
select 600, date '2018-02-06' from dual union all
select 600, date '2018-02-07' from dual union all
select 0, date '2018-02-08' from dual union all
select 0, date '2018-02-09' from dual
)
,y1 as (
select test.datum, test.code, lag(nvl(code,code)) over (order by datum) as after, lead(nvl(code,code)) over (order by datum) as before
from test
)
select * from y1 where code != before or code != after;

sqHow to get sum values weekly based between two dates?

My table values like ...
Date Amt Cash Money Name
15-Jun 100 10 20 GUL
16-Jun 200 20 40 ABC
20-Jun 300 30 60 GUL
25-Jun 400 40 80 BCA
28-Jun 500 50 10 GUL
3-Jul 600 60 120 ABC
19-Jun 700 70 140 BCA
26-Jun 800 80 160 ABC
7-Jul 900 90 180 GUL
9-Jul 1000 100 200 ABC
I need to return weekly based sum of values between two date in oracle .My expected output.
Date Amt Cash Mony
13 to 19 June 1000 100 200
20 to 26 June 1500 150 300
27 to3 July 1100 110 130
4 to 10 July 1900 190 380
you can achieve this by a case statement:
e.g.
-- test data
with data(dat,
val1,
val2) as
(select sysdate - 7, 12, 13
from dual
union all
select sysdate - 6, 32, 1
from dual
union all
select sysdate - 5, 52, 53
from dual
union all
select sysdate - 4, 2, 16
from dual
union all
select sysdate - 3, 72, 154
from dual)
select -- build up your groups
case
when d.dat < to_date('28.09.2016', 'DD.MM.YYYY') then
'<28.09.'
when d.dat > to_date('30.09.2016', 'DD.MM.YYYY') then
'>30.09.'
else
'28.-30.'
end as grp,
sum(val1),
sum(val2)
from data d
group by case
when d.dat < to_date('28.09.2016', 'DD.MM.YYYY') then
'<28.09.'
when d.dat > to_date('30.09.2016', 'DD.MM.YYYY') then
'>30.09.'
else
'28.-30.'
end;
-- output
grp sum(val1) sum(val2)
28.-30. 84 54
<28.09. 12 13
>30.09. 74 170
To group by calendar week use
-- test data
with data(dat,
val1,
val2) as
(select sysdate - 9, 12, 13
from dual
union all
select sysdate - 6, 32, 1
from dual
union all
select sysdate - 5, 52, 53
from dual
union all
select sysdate - 4, 2, 16
from dual
union all
select sysdate + 3, 72, 154
from dual)
select TRUNC(dat, 'iw') ||'-'|| TRUNC(dat+7, 'iw'),
sum(val1),
sum(val2)
from data
group by TRUNC(dat, 'iw') ||'-'|| TRUNC(dat+7, 'iw');
The query below has the input dates (from and to) in the first factored subquery. Those can be made into bind variables, or whatever mechanism you want to use to pass these inputs to the query. Then I have the test data in the second factored subquery; you don't need that in your final solution. I create all the needed weeks in the "weeks" factored subquery and I use a left outer join, so that weeks with no transactions will show 0 sums. Note that in the main query, where I do a join, the "date" column from the base table is not enclosed within any kind of function; this allows the use of an index on that column, which you should have if the table is very large, or if performance may be a concern for any other reason. Note that the output is different from yours (missing the last row) because I input a to-date before the last row in the table. That is intentional, I wanted to make sure the query works correctly. Also: I didn't use "date" or "week" as column names; that is a very poor practice. Reserved Oracle keywords should not be used as column names. I used "dt" and "wk" instead.
with
user_inputs ( from_dt, to_dt ) as (
select to_date('4-Jun-2016', 'dd-Mon-yyyy'), to_date('3-Jul-2016', 'dd-Mon-yyyy') from dual
),
test_data ( dt, amt, cash, money, name ) as (
select to_date('15-Jun-2016', 'dd-Mon-yyyy'), 100, 10, 20, 'GUL' from dual union all
select to_date('16-Jun-2016', 'dd-Mon-yyyy'), 200, 20, 40, 'ABC' from dual union all
select to_date('20-Jun-2016', 'dd-Mon-yyyy'), 300, 30, 60, 'GUL' from dual union all
select to_date('25-Jun-2016', 'dd-Mon-yyyy'), 400, 40, 80, 'BCA' from dual union all
select to_date('28-Jun-2016', 'dd-Mon-yyyy'), 500, 50, 10, 'GUL' from dual union all
select to_date( '3-Jul-2016', 'dd-Mon-yyyy'), 600, 60, 120, 'ABC' from dual union all
select to_date('19-Jun-2016', 'dd-Mon-yyyy'), 700, 70, 140, 'BCA' from dual union all
select to_date('26-Jun-2016', 'dd-Mon-yyyy'), 800, 80, 160, 'ABC' from dual union all
select to_date( '7-Jul-2016', 'dd-Mon-yyyy'), 900, 90, 180, 'GUL' from dual union all
select to_date( '9-Jul-2016', 'dd-Mon-yyyy'), 1000, 100, 200, 'ABC' from dual
),
weeks ( start_dt ) as (
select trunc(from_dt, 'iw') + 7 * (level - 1)
from user_inputs
connect by level <= 1 + (to_dt - trunc(from_dt, 'iw')) / 7
)
select to_char(w.start_dt, 'dd-Mon-yyyy') || ' - ' ||
to_char(w.start_dt + 6, 'dd-Mon-yyyy') as wk,
nvl(sum(t.amt), 0) as tot_amt, nvl(sum(t.cash), 0) as tot_cash,
nvl(sum(t.money), 0) as tot_money
from weeks w left outer join test_data t
on t.dt >= w.start_dt and t.dt < w.start_dt + 7
group by start_dt
order by start_dt
;
Output:
WK TOT_AMT TOT_CASH TOT_MONEY
-------------------------------------------- ---------- ---------- ----------
30-May-2016 - 05-Jun-2016 0 0 0
06-Jun-2016 - 12-Jun-2016 0 0 0
13-Jun-2016 - 19-Jun-2016 1000 100 200
20-Jun-2016 - 26-Jun-2016 1500 150 300
27-Jun-2016 - 03-Jul-2016 1100 110 130
You can try like below, I chose 13-Jun-2016 as a starting date. You can chose it as per your requirement upto any range of dates.
with t as
(select dt,
min(dt) over (partition by week)||' to '|| max(dt) over (partition by week) week
from (
select to_date('13-Jun-2016','dd-Mon-yyyy')+(level-1) dt,
ceil(level/7) week
from dual
connect by level<=52))
select week,
sum(amt),
sum(cash),
sum(money)
from (
select your_table.*,
t.week
from your_table,t
where trunc(to_date(your_table.dt,'dd-Mon-yyyy'))=trunc(t.dt))
group by week;

How to identify positive minimum or negative maximum in a column for a key?

I have the following columns - Person_ID Days. For one person id, multiple days are possible. Something like this:
Person_Id Days
1000 100
1000 200
1000 -50
1000 -10
1001 100
1001 200
1001 50
1001 10
1002 -50
1002 -10
I need to address the following scenarios:
If all values for days column are positive, I need minimum of the days for a person_id. If the days column has both positive and negative, I need minimum of positive. If all negatives, I need maximum of negative.
The output like:
Person_id Days
1000 100
1001 10
1002 -10
I tried using case statement, but I am unable to use a same column in the condition as well as grouping.
Try this (Postgres 9.4+):
select person_id, coalesce(min(days) filter (where days > 0), max(days))
from a_table
group by 1
order by 1;
Oracle Setup:
CREATE TABLE table_name ( Person_Id, Days ) AS
SELECT 1000, 100 FROM DUAL UNION ALL
SELECT 1000, 200 FROM DUAL UNION ALL
SELECT 1000, -50 FROM DUAL UNION ALL
SELECT 1000, -10 FROM DUAL UNION ALL
SELECT 1001, 100 FROM DUAL UNION ALL
SELECT 1001, 200 FROM DUAL UNION ALL
SELECT 1001, 50 FROM DUAL UNION ALL
SELECT 1001, 10 FROM DUAL UNION ALL
SELECT 1002, -50 FROM DUAL UNION ALL
SELECT 1002, -10 FROM DUAL;
Query:
SELECT person_id, days
FROM (
SELECT t.*,
ROW_NUMBER() OVER ( PARTITION BY person_id
ORDER BY SIGN( ABS( days ) ),
SIGN( DAYS ) DESC,
ABS( DAYS )
) AS rn
FROM table_name t
)
WHERE rn = 1;
Output:
PERSON_ID DAYS
---------- ----------
1000 100
1001 10
1002 -10
Oracle solution:
with
input_data ( person_id, days) as (
select 1000, 100 from dual union all
select 1000, 200 from dual union all
select 1000, -50 from dual union all
select 1000, -10 from dual union all
select 1001, 100 from dual union all
select 1001, 200 from dual union all
select 1001, 50 from dual union all
select 1001, 10 from dual union all
select 1002, -50 from dual union all
select 1002, -10 from dual
)
select person_id,
NVL(min(case when days > 0 then days end), max(days)) as days
from input_data
group by person_id;
PERSON_ID DAYS
---------- ----------
1000 100
1001 10
1002 -10
For each person_id, if there is at least one days value that is strictly positive, then the min will be taken over positive days only and will be returned by NVL(). Otherwise the min() will return null, and NVL() will return max() over all days (all of which are, in this case, negative or 0).
select Person_id, min(abs(days)) * days/abs(days) from table_name
group by Person_id
-- + handle zero_divide .. SORRY.. the above works only in MySQL .
Something like this will work anywhere which is equivalent of above query:
select t.Person_id , min(t.days) from table_name t,
(select Person_id, min(abs(days)) as days from table_name group by Person_id) v
where t.Person_id = v.Person_id
and abs(t days) = v.days
group by Person_id;
OR
select id, min(Days) from (
select Person_id, min(abs(Days)) as Days from temp group by Person_id
union
select Person_id, max(Days) as Days from temp group by Person_id
) temp
group by Person_id;
You can do this by using GroupBy clause in sql server. Take a look into below query:-
CREATE TABLE #test(Person_Id INT, [Days] INT)
DECLARE #LargestNumberFromTable INT;
INSERT INTO #test
SELECT 1000 , 100 UNION
SELECT 1000 , 200 UNION
SELECT 1000 , -50 UNION
SELECT 1000 , -10 UNION
SELECT 1001 , 100 UNION
SELECT 1001 , 200 UNION
SELECT 1001 , 50 UNION
SELECT 1001 , 10 UNION
SELECT 1002 , -50 UNION
SELECT 1002 , -10
SELECT #LargestNumberFromTable = ISNULL(MAX([Days]), 0)
FROM #test
SELECT Person_Id
,CASE WHEN SUM(IIF([Days] > 0,[Days] , 0)) = 0 THEN MAX([Days]) -- All Negative
WHEN SUM([Days]) = SUM(IIF([Days] > 0, [Days], 0)) THEN MIN ([Days]) -- ALL Positive
WHEN SUM([Days]) <> SUM(IIF([Days] > 0, [Days], 0)) THEN MIN(IIF([Days] > 0, [Days], #LargestNumberFromTable)) --Mix (Negative And positive)
END AS [Days]
FROM #test
GROUP BY Person_Id
DROP TABLE #test

oracle query to retrieve the date of the highest value which are grouped by month and year

Data looks similar to this..
Here I need to retrieve the highest amount by grouping them by month and year but I couldn't retrieve the exact date of the highest amount....
The code I tried for the highest amount is ...
SELECT ACCT_ID AS ACCOUNT_ID,
COUNT(TRANS_AMOUNT) AS NUMBER_OF_TRANSACTION,
Sum(Trans_Amount) As Transaction_Amount,
To_Char( Trans_Date, 'MON-YYYY') As Month_Date,
Max(Trans_Amount) As Highest
From Trans_Amt
group by ACCT_ID, To_Char( Trans_Date, 'MON-YYYY');
And this worked but I could not retrieve the date here. If I try for the date I get "not a group by variable" error ...
Group by account and month(I used trunc at month level).
Select max(amount) for amount and use keep dense_rank first to get the date of maximum amount:
SELECT
ACCT_ID AS ACCOUNT_ID,
trunc(Trans_Date, 'MM') As Month_Date,
max(trans_date) keep (dense_rank first order by trans_amount desc) as trans_date
Max(Trans_Amount) As Highest
FROM Trans_Amt
GROUP BY
ACCT_ID, trunc(Trans_Date, 'MM');
with t as (
select 1000 ACCT_ID, to_date('11-JAN-2000', 'dd-MM-yyyy') TRANS_DATE, 201 TRANS_AMOUNT from dual union all
select 1000, to_date('22-JAN-2000', 'dd-MM-yyyy'), 209 from dual union all
select 1000, to_date('31-JAN-2000', 'dd-MM-yyyy'), 4504 from dual union all
select 1000, to_date('10-FEB-2000', 'dd-MM-yyyy'), 487 from dual union all
select 1001, to_date('10-FEB-2000', 'dd-MM-yyyy'), 4287 from dual union all
select 1001, to_date('17-FEB-2000', 'dd-MM-yyyy'), 4501 from dual union all
select 1001, to_date('22-FEB-2000', 'dd-MM-yyyy'), 1209 from dual union all
select 1000, to_date('22-FEB-2000', 'dd-MM-yyyy'), 4550 from dual union all
select 1001, to_date('23-FEB-2000', 'dd-MM-yyyy'), 120 from dual union all
select 1001, to_date('26-FEB-2000', 'dd-MM-yyyy'), 245 from dual union all
select 1000, to_date('28-FEB-2000', 'dd-MM-yyyy'), 4500 from dual union all
select 1000, to_date('08-MAR-2000', 'dd-MM-yyyy'), 256 from dual union all
select 1001, to_date('08-MAR-2000', 'dd-MM-yyyy'), 2561 from dual union all
select 1000, to_date('24-MAR-2000', 'dd-MM-yyyy'), 987 from dual union all
select 1001, to_date('24-MAR-2000', 'dd-MM-yyyy'), 75000 from dual union all
select 1000, to_date('31-MAR-2000', 'dd-MM-yyyy'), 1100 from dual union all
select 1001, to_date('31-MAR-2000', 'dd-MM-yyyy'), 11000 from dual union all
select 1001, to_date('04-APR-2000', 'dd-MM-yyyy'), 4287 from dual union all
select 1000, to_date('04-APR-2000', 'dd-MM-yyyy'), 487 from dual union all
select 1001, to_date('12-APR-2000', 'dd-MM-yyyy'), 1209 from dual union all
select 1001, to_date('14-APR-2000', 'dd-MM-yyyy'), 1092 from dual union all
select 1001, to_date('20-APR-2000', 'dd-MM-yyyy'), 1245 from dual union all
select 1000, to_date('20-APR-2000', 'dd-MM-yyyy'), 7500 from dual union all
select 1000, to_date('22-APR-2000', 'dd-MM-yyyy'), 1205 from dual union all
select 1000, to_date('26-APR-2000', 'dd-MM-yyyy'), 245 from dual
)
select
*
from
t t
where
not exists(
select
*
from
t n
where
trunc(t.TRANS_DATE, 'mm') = trunc(n.TRANS_DATE, 'mm')
and n.TRANS_AMOUNT > t.TRANS_AMOUNT
and t.ACCT_ID = n.ACCT_ID
)
order by
t.TRANS_DATE, t.ACCT_ID
Output

SQL query to get the first date, depending on the current group (~control break)

I'm trying to create a sql query which returns the first date of the current group.
Let's assume (just as an example) that on the first of every month a row is saved in a table (EmployeeInfo) with the employee ID and the current department.
EmployeeID, Department, Date (Format: DD.MM.YYYY)
100, IT, 01.07.2014
100, IT, 01.08.2014
100, IT, 01.09.2014
100, HR, 01.10.2014
100, HR, 01.11.2014
100, CC, 01.12.2014
100, IT, 01.01.2015
100, IT, 01.02.2015
100, IT, 01.03.2015
100, IT, 01.04.2015
The query should return the date since an employee is working in the current department.
The current department of the employee with ID 100 is IT, therefore the value should be 01.01.2015 (not 01.07.2014).
Any ideas how this could be implemented?
UPDATE New answer based on OP's comments.
You could use ANALYTIC function ROW_NUMBER and LAG.Something like, start of group method:
SQL> WITH DATA AS(
2 SELECT 100 EmployeeID, 'IT' Department, to_date('01.07.2014','DD.MM.YYYY') dt FROM dual UNION ALL
3 SELECT 100, 'IT', to_date('01.08.2014','DD.MM.YYYY') dt from dual union all
4 select 100, 'IT', to_date('01.09.2014','DD.MM.YYYY') dt from dual union all
5 SELECT 100, 'HR', to_date('01.10.2014','DD.MM.YYYY') dt from dual union all
6 select 100, 'HR', to_date('01.11.2014','DD.MM.YYYY') dt from dual union all
7 SELECT 100, 'CC', to_date('01.12.2014','DD.MM.YYYY') dt from dual union all
8 select 100, 'IT', to_date('01.01.2015','DD.MM.YYYY') dt from dual union all
9 select 100, 'IT', to_date('01.02.2015','DD.MM.YYYY') dt from dual
10 )
11 SELECT EmployeeID,
12 Department,
13 DT
14 FROM
15 (SELECT *
16 FROM
17 (SELECT t.*,
18 CASE
19 WHEN Department = lag(Department) over (PARTITION BY EmployeeID ORDER BY dt)
20 THEN 0
21 ELSE 1
22 END gap
23 FROM DATA t
24 ) T
25 WHERE GAP = 1
26 ORDER BY DT DESC
27 )
28 WHERE ROWNUM = 1
29 /
EMPLOYEEID DE DT
---------- -- ---------
100 IT 01-JAN-15
SQL>
OLD answer
For example,
SQL> WITH DATA AS(
2 SELECT 100 EmployeeID, 'IT' Department, to_date('01.07.2014','DD.MM.YYYY') dt FROM dual UNION ALL
3 SELECT 100, 'IT', to_date('01.08.2014','DD.MM.YYYY') dt from dual union all
4 select 100, 'IT', to_date('01.09.2014','DD.MM.YYYY') dt from dual union all
5 SELECT 100, 'HR', to_date('01.10.2014','DD.MM.YYYY') dt from dual union all
6 select 100, 'HR', to_date('01.11.2014','DD.MM.YYYY') dt from dual union all
7 SELECT 100, 'CC', to_date('01.12.2014','DD.MM.YYYY') dt from dual union all
8 select 100, 'IT', to_date('01.01.2015','DD.MM.YYYY') dt from dual union all
9 select 100, 'IT', to_date('01.02.2015','DD.MM.YYYY') dt from dual
10 )
11 SELECT*
12 FROM
13 (SELECT t.*,
14 row_number() OVER(PARTITION BY department ORDER BY dt DESC) rn
15 FROM DATA t
16 )
17 WHERE rn = 1
18 /
EMPLOYEEID DE DT RN
---------- -- --------- ----------
100 CC 01-DEC-14 1
100 HR 01-NOV-14 1
100 IT 01-FEB-15 1
SQL>
Use below query and let me know if want this
WITH table_ (EmployeeID, Department, Dat) AS (
SELECT 100, 'IT', TO_DATE('01.07.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'IT', TO_DATE('01.08.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'IT', TO_DATE('01.09.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'HR', TO_DATE('01.10.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'HR', TO_DATE('01.11.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'CC', TO_DATE('01.12.2014', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'IT', TO_DATE('01.01.2015', 'DD.MM.YYYY') FROM DUAL UNION ALL
SELECT 100, 'IT', TO_DATE('01.02.2015', 'DD.MM.YYYY') FROM DUAL),
----------
--End of Data preparation
----------
table1 AS (SELECT EmployeeID, Department, Dat,
row_number() OVER (PARTITION BY EmployeeID ORDER BY dat DESC) -
row_number() OVER (PARTITION BY EmployeeID, department ORDER BY dat DESC) rk
FROM TABLE_),
table2 AS (SELECT EmployeeID, Department, Dat, RANK() OVER (PARTITION BY employeeid ORDER BY dat) rnk
from table1
WHERE rk = 0 )
SELECT EmployeeID, Department, Dat
FROM table2
WHERE rnk = 1;
output:
EMPLOYEEID DE DAT
---------- -- ---------
100 IT 01-JAN-15