Select most recent test score on per month basis - sql

I am using Oracle SQL, and I'm trying to perform a historical test score analysis (to visualize test score improvements on per month basis for individuals). Firstly, I have a table that is a list of Users and the respective Months they are active; it looks something like this:
TABLE1
________________________
UserName | ActiveDate
________________________
John Doe, 01-MAY-18
John Doe, 01-APR-18
John Doe, 01-MAR-18
Jane Doe, 01-APR-18
Jane Doe, 01-MAR-18
Jim Doe, 01-MAY-18
On top of that, I have another table that lists Test Scores, which are timestamped (you can retake the test as many times as you want). It looks something like this:
TABLE2
________________________________________
UserName | TestScore | EffectiveDate
________________________________________
John Doe, 87, 07-FEB-18
John Doe, 85, 14-FEB-18
John Doe, 90, 18-FEB-18
John Doe, 92, 02-MAR-18
John Doe, 91, 12-MAR-18
Jane Doe, 70, 01-FEB-18
Jane Doe, 72, 02-FEB-18
Jane Doe, 78, 18-FEB-18
Jane Doe, 77, 06-MAR-18
Jane Doe, 81, 18-MAR-18
Jim Doe, 50, 03-MAR-18
Jim Doe, 48, 23-MAR-18
Jim Doe, 58, 08-APR-18
For every row in the first table (all the UserName | ActiveDate pairings are disctinct), I would like to select the most recent TestScore from Table2 where the EffectiveDate is prior to the ActiveDate
So I'm hoping to get something like this
UserName | ActiveDate | Most recent TestScore prior to ActiveDate
______________________________________
John Doe, 01-MAY-18, 91
John Doe, 01-APR-18, 91
John Doe, 01-MAR-18, 90
Jane Doe, 01-APR-18, 81
Jane Doe, 01-MAR-18, 78
Jim Doe, 01-MAY-18, 58
I've tried to make this work by JOINING Table1 to Table2 on UserName, where EffectiveDate < ActiveDate, but I can't seem to figure out the SQL statement I need to SELECT * from Table2 where EffectiveDate < ActiveDate, but I'm struggling to figure that out on a "per row" basis...
Thanks for any and all advice in advance. This is my first posting to StackOverflow, so I hope I've posed this question correctly!
Edit: Thanks all for the help, I think I have everything I need to proceed with my project now. I'll be sure to make some improvements to my posting next time I ask a question here on SO.

Two solutions that only require a single join:
Oracle Setup:
CREATE TABLE TABLE1 ( UserName, ActiveDate ) AS
SELECT 'John Doe', DATE '2018-05-01' FROM DUAL UNION ALL
SELECT 'John Doe', DATE '2018-04-01' FROM DUAL UNION ALL
SELECT 'John Doe', DATE '2018-03-01' FROM DUAL UNION ALL
SELECT 'Jane Doe', DATE '2018-04-01' FROM DUAL UNION ALL
SELECT 'Jane Doe', DATE '2018-03-01' FROM DUAL UNION ALL
SELECT 'Jim Doe', DATE '2018-05-01' FROM DUAL;
CREATE TABLE TABLE2 ( UserName, TestScore, EffectiveDate ) AS
SELECT 'John Doe', 87, DATE '2018-02-07' FROM DUAL UNION ALL
SELECT 'John Doe', 85, DATE '2018-02-14' FROM DUAL UNION ALL
SELECT 'John Doe', 90, DATE '2018-02-18' FROM DUAL UNION ALL
SELECT 'John Doe', 92, DATE '2018-03-02' FROM DUAL UNION ALL
SELECT 'John Doe', 91, DATE '2018-03-12' FROM DUAL UNION ALL
SELECT 'Jane Doe', 70, DATE '2018-02-01' FROM DUAL UNION ALL
SELECT 'Jane Doe', 72, DATE '2018-02-02' FROM DUAL UNION ALL
SELECT 'Jane Doe', 78, DATE '2018-02-18' FROM DUAL UNION ALL
SELECT 'Jane Doe', 77, DATE '2018-03-06' FROM DUAL UNION ALL
SELECT 'Jane Doe', 81, DATE '2018-03-18' FROM DUAL UNION ALL
SELECT 'Jim Doe', 50, DATE '2018-03-03' FROM DUAL UNION ALL
SELECT 'Jim Doe', 48, DATE '2018-03-23' FROM DUAL UNION ALL
SELECT 'Jim Doe', 58, DATE '2018-04-08' FROM DUAL;
Query 1:
SELECT *
FROM (
SELECT t2.*,
t1.ActiveDate,
ROW_NUMBER() OVER ( PARTITION BY t2.UserName, t1.ActiveDate ORDER BY EffectiveDate DESC ) AS rn
FROM table2 t2
INNER JOIN
table1 t1
ON ( t1.UserName = t2.UserName
AND t2.EffectiveDate < t1.ActiveDate )
) t2
WHERE rn = 1;
Output:
USERNAME TESTSCORE EFFECTIVEDATE ACTIVEDATE RN
---------- ---------- -------------- ---------- ---
Jane Doe 78 18-FEB-18 01-MAR-18 1
Jane Doe 81 18-MAR-18 01-APR-18 1
Jim Doe 58 08-APR-18 01-MAY-18 1
John Doe 90 18-FEB-18 01-MAR-18 1
John Doe 91 12-MAR-18 01-APR-18 1
John Doe 91 12-MAR-18 01-MAY-18 1
Query 2:
SELECT t1.UserName,
t1.ActiveDate,
MAX( TestScore ) KEEP ( DENSE_RANK LAST ORDER BY EffectiveDate ) AS MostRecentTestScore
FROM table2 t2
INNER JOIN
table1 t1
ON ( t1.UserName = t2.UserName
AND t2.EffectiveDate < t1.ActiveDate )
GROUP BY t1.UserName, t1.ActiveDate;
Output:
USERNAME ACTIVEDATE MOSTRECENTTESTSCORE
---------- ---------- -------------------
Jim Doe 01-MAY-18 58
Jane Doe 01-MAR-18 78
Jane Doe 01-APR-18 81
John Doe 01-MAR-18 90
John Doe 01-APR-18 91
John Doe 01-MAY-18 91

If you just want the test score, a correlated subquery might be the simplest approach:
select t1.*,
(select max(t2.score) keep (dense_rank first order by t2.effectivedate desc)
from table2 t2
where t2.effectivedate < t1.activedate
) as most_recent_score
from table1 t1;

Here's one option (you need lines 24 onwards; previous lines are just testing CTEs):
SQL> with table1 (username, activedate) as
2 (select 'jod', date '2018-05-01' from dual union all
3 select 'jod', date '2018-04-01' from dual union all
4 select 'jod', date '2018-03-01' from dual union all
5 select 'jad', date '2018-04-01' from dual union all
6 select 'jad', date '2018-03-01' from dual union all
7 select 'jid', date '2018-05-01' from dual
8 ),
9 table2 (username, testscore, effectivedate) as
10 (select 'jod', 87, date '2018-02-07' from dual union all
11 select 'jod', 85, date '2018-02-14' from dual union all
12 select 'jod', 90, date '2018-02-18' from dual union all
13 select 'jod', 92, date '2018-03-02' from dual union all
14 select 'jod', 91, date '2018-03-12' from dual union all
15 select 'jad', 70, date '2018-02-01' from dual union all
16 select 'jad', 72, date '2018-02-02' from dual union all
17 select 'jad', 78, date '2018-02-18' from dual union all
18 select 'jad', 77, date '2018-03-06' from dual union all
19 select 'jad', 81, date '2018-03-18' from dual union all
20 select 'jid', 50, date '2018-03-03' from dual union all
21 select 'jid', 48, date '2018-03-23' from dual union all
22 select 'jid', 58, date '2018-04-08' from dual
23 )
24 select t1.username, t1.activedate, t2.testscore
25 from table1 t1 join table2 t2 on t1.username = t2.username
26 where t2.effectivedate = (select max(t2a.effectivedate)
27 from table2 t2a
28 where t2a.username = t2.username
29 and t2a.effectivedate < t1.activedate
30 )
31 order by t1.username, t1.activedate desc;
USE ACTIVEDAT TESTSCORE
--- --------- ----------
jad 01-apr-18 81
jad 01-mar-18 78
jid 01-may-18 58
jod 01-may-18 91
jod 01-apr-18 91
jod 01-mar-18 90
6 rows selected.
SQL>

Related

Group by rows which are in sequence

Consider I have a table like this
PASSENGER CITY DATE
43 NEW YORK 1-Jan-21
44 CHICAGO 4-Jan-21
43 NEW YORK 2-Jan-21
43 NEW YORK 3-Jan-21
44 ROME 5-Jan-21
43 LONDON 4-Jan-21
44 CHICAGO 6-Jan-21
44 CHICAGO 7-Jan-21
How would I group Passenger and City column in sequence to get a result like below?
PASSENGER CITY COUNT
43 NEW YORK 3
44 CHICAGO 1
44 ROME 1
43 LONDON 1
44 CHICAGO 2
One way to deal with such a gaps-and-islands problem is to calculate a ranking for the gaps.
Then group also on that ranking.
SELECT PASSENGER, CITY
, COUNT(*) AS "Count"
-- , MIN("DATE") AS StartDate
-- , MAX("DATE") AS EndDate
FROM (
SELECT q1.*
, SUM(gap) OVER (PARTITION BY PASSENGER ORDER BY "DATE") as Rnk
FROM (
SELECT PASSENGER, CITY, "DATE"
, CASE
WHEN 1 = TRUNC("DATE")
- TRUNC(LAG("DATE")
OVER (PARTITION BY PASSENGER, CITY ORDER BY "DATE"))
THEN 0 ELSE 1 END as gap
FROM table_name t
) q1
) q2
GROUP BY PASSENGER, CITY, Rnk
ORDER BY MIN("DATE"), PASSENGER
PASSENGER
CITY
Count
43
NEW YORK
3
43
LONDON
1
44
CHICAGO
1
44
ROME
1
44
CHICAGO
2
db<>fiddle here
From Oracle 12, you can use MATCH_RECOGNIZE:
SELECT *
FROM table_name
MATCH_RECOGNIZE (
PARTITION BY passenger
ORDER BY "DATE"
MEASURES
FIRST(city) AS city,
COUNT(*) AS count
PATTERN (same_city+)
DEFINE
same_city AS FIRST(city) = city
);
Which, for the sample data:
CREATE TABLE table_name (PASSENGER, CITY, "DATE") AS
SELECT 43, 'NEW YORK', DATE '2021-01-01' FROM DUAL UNION ALL
SELECT 44, 'CHICAGO', DATE '2021-01-04' FROM DUAL UNION ALL
SELECT 43, 'NEW YORK', DATE '2021-01-02' FROM DUAL UNION ALL
SELECT 43, 'NEW YORK', DATE '2021-01-03' FROM DUAL UNION ALL
SELECT 44, 'ROME', DATE '2021-01-05' FROM DUAL UNION ALL
SELECT 43, 'LONDON', DATE '2021-01-04' FROM DUAL UNION ALL
SELECT 44, 'CHICAGO', DATE '2021-01-06' FROM DUAL UNION ALL
SELECT 44, 'CHICAGO', DATE '2021-01-07' FROM DUAL
Outputs:
PASSENGER
CITY
COUNT
43
NEW YORK
3
43
LONDON
1
44
CHICAGO
1
44
ROME
1
44
CHICAGO
2
If you have ordered the input result set (note: tables should be considered to be unordered) and want to maintain the order then:
SELECT *
FROM (SELECT t.*, ROWNUM AS rn FROM table_name t)
MATCH_RECOGNIZE (
PARTITION BY passenger
ORDER BY RN
MEASURES
FIRST(rn) AS rn,
FIRST("DATE") AS "DATE",
FIRST(city) AS city,
COUNT(*) AS count
PATTERN (same_city+)
DEFINE
same_city AS FIRST(city) = city
)
ORDER BY rn
Outputs:
PASSENGER
RN
DATE
CITY
COUNT
43
1
01-JAN-21
NEW YORK
3
44
2
04-JAN-21
CHICAGO
1
44
5
05-JAN-21
ROME
1
43
6
04-JAN-21
LONDON
1
44
7
06-JAN-21
CHICAGO
2
db<>fiddle here

Groupin Clients when client is new in year ORACLE

I have list of invoices. I wonna group data into sales_departments but only this clients who have first invoice in 2018 Year.
Below my "data". Sory for mystake but i can't paste data better.
Client_Number sales_depart Netto Number_Invoice Invoice_Date
1022562 0140 113 51545121188 04.11.18
1022562 0140 139 5586258568 04.01.18
1022564 0140 171 5586713889 03.22.18
1022565 0140 211 5587169210 03.22.17
1022566 0140 259 5587624531 03.22.16
1022567 0140 319 5588079852 03.23.15
1022568 0140 392 5588535173 03.23.14
1022569 0140 483 5588990494 03.23.13
1022570 0140 594 5589445815 03.23.12
1022571 0140 730 5589901136 03.24.11
1008144 0530 898 5590356457 01.31.18
1008145 0530 104 5590811778 02.20.18
1008146 0530 358 5591267099 02.20.17
1008147 0530 671 5591722420 02.21.16
1008148 0530 055 5592177741 02.21.15
1008149 0530 528 5592633062 02.21.14
1008150 0530 109 5593088383 02.21.13
1016058 0130 825 5593543704 01.18.18
1051643 0290 704 5593999025 01.30.18
1051643 0290 175 5595199025 01.30.17
1049433 0180 786 5594454346 02.20.18
1010219 0180 117 5594909667 02.28.18
1033233 0180 754 5595364988 02.28.18
1004914 0160 767 5595820309 02.14.18
1011699 0140 244 5596275630 02.20.18
1007323 0160 290 5596730951 04.19.18
1004914 0160 036 5597186272 02.07.18
1005837 0530 645 5597641593 04.19.18
The data I would like to receive
Sales Dept Count_Clintr
0130 1
0160 2
0180 3
You can do it with a single table scan using the HAVING clause:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Client_Number, sales_depart, Netto, Number_Invoice, Invoice_Date ) AS
SELECT 1022562, '0140', 113, 51545121188, DATE '2018-04-11' FROM DUAL UNION ALL
SELECT 1022562, '0140', 139, 5586258568, DATE '2018-04-01' FROM DUAL UNION ALL
SELECT 1022564, '0140', 171, 5586713889, DATE '2018-03-22' FROM DUAL UNION ALL
SELECT 1022565, '0140', 211, 5587169210, DATE '2017-03-22' FROM DUAL UNION ALL
SELECT 1022566, '0140', 259, 5587624531, DATE '2016-03-22' FROM DUAL UNION ALL
SELECT 1022567, '0140', 319, 5588079852, DATE '2015-03-23' FROM DUAL UNION ALL
SELECT 1022568, '0140', 392, 5588535173, DATE '2014-03-23' FROM DUAL UNION ALL
SELECT 1022569, '0140', 483, 5588990494, DATE '2013-03-23' FROM DUAL UNION ALL
SELECT 1022570, '0140', 594, 5589445815, DATE '2012-03-23' FROM DUAL UNION ALL
SELECT 1022571, '0140', 730, 5589901136, DATE '2011-03-24' FROM DUAL UNION ALL
SELECT 1008144, '0530', 898, 5590356457, DATE '2018-01-31' FROM DUAL UNION ALL
SELECT 1008145, '0530', 104, 5590811778, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1008146, '0530', 358, 5591267099, DATE '2017-02-20' FROM DUAL UNION ALL
SELECT 1008147, '0530', 671, 5591722420, DATE '2016-02-21' FROM DUAL UNION ALL
SELECT 1008148, '0530', 055, 5592177741, DATE '2015-02-21' FROM DUAL UNION ALL
SELECT 1008149, '0530', 528, 5592633062, DATE '2014-02-21' FROM DUAL UNION ALL
SELECT 1008150, '0530', 109, 5593088383, DATE '2013-02-21' FROM DUAL UNION ALL
SELECT 1016058, '0130', 825, 5593543704, DATE '2018-01-18' FROM DUAL UNION ALL
SELECT 1051643, '0290', 704, 5593999025, DATE '2018-01-30' FROM DUAL UNION ALL
SELECT 1051643, '0290', 175, 5595199025, DATE '2017-01-30' FROM DUAL UNION ALL
SELECT 1049433, '0180', 786, 5594454346, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1010219, '0180', 117, 5594909667, DATE '2018-02-28' FROM DUAL UNION ALL
SELECT 1033233, '0180', 754, 5595364988, DATE '2018-02-28' FROM DUAL UNION ALL
SELECT 1004914, '0160', 767, 5595820309, DATE '2018-02-14' FROM DUAL UNION ALL
SELECT 1011699, '0140', 244, 5596275630, DATE '2018-02-20' FROM DUAL UNION ALL
SELECT 1007323, '0160', 290, 5596730951, DATE '2018-04-19' FROM DUAL UNION ALL
SELECT 1004914, '0160', 036, 5597186272, DATE '2018-02-07' FROM DUAL UNION ALL
SELECT 1005837, '0530', 645, 5597641593, DATE '2018-04-19' FROM DUAL;
Query 1:
SELECT sales_depart,
COUNT( client_number )
FROM (
SELECT sales_depart,
client_number
FROM table_name
GROUP BY
sales_depart,
client_number
HAVING MIN( Invoice_date ) >= DATE '2018-01-01'
)
GROUP BY sales_depart
Results:
| SALES_DEPART | COUNT(CLIENT_NUMBER) |
|--------------|----------------------|
| 0140 | 3 |
| 0530 | 3 |
| 0180 | 3 |
| 0130 | 1 |
| 0160 | 2 |
One method uses not exists for filtering:
select sales_department, sum(netto)
from t
where not exists (select 1
from t t2
where t2.client = t.client and
t2.invoice_date < date '2018-01-01'
)
group by sales_department;
For performance, you want an index on (client, invoice_date). Also not the use of the date keyword to define the constant using ISO/ANSI standard formats.
Here is the solution to your problem:
SELECT sales_depart, COUNT(Distinct client_Number) AS Distinct_Clients
FROM Table1
GROUP BY sales_depart
HAVING MIN(TO_DATE(invoice_date, 'MM.DD.YY')) >= TO_DATE('01-01-2018', 'DD-MM-YYYY')
ORDER BY sales_depart
OUTPUT:
SALES_DEPART DISTINCT_CLIENTS
0130 1
0160 2
0180 3
Note: Since, Client_Number = 1004914 repeats 2 times in Sales_depart = 0160 so it is counted 1 time only.
Link to the demo:
http://sqlfiddle.com/#!4/28c71/1
SELECT sd.sales_depart,COUNT(sd.Client_Number) AS Client_Number
FROM sales_departments sd
INNER JOIN
(
SELECT sales_depart,MIN(Invoice_Date) AS Invoice_Date
FROM sales_departments
WHERE Invoice_date >= DATE '2018-01-01'
GROUP BY sales_depart
) T
ON T.sales_depart=sd.sales_depart
GROUP BY sd.sales_depart

Find the missing record

Three tables are connected to each other in the following manner:
Employee (ID, name) to Salary (ID, Employee_id, Vendor_id, total_amount, date_paid) by employee ID
Salary (ID, Employee_id, Vendor_id) to Vendor (Id, name) by vendor ID
Each employee ID has at least 2 vendors.
However one vendor is same for all employees – “ABC”
I need a list of employees that Vendor ABC was not paid.
For example:
Employee Vendor Month Total_amount
123 ABC Jan 150
123 DEF Jan 200
456 ABC Jan 150
456 XYZ Jan 250
123 DEF Feb 200
456 ABC Feb 150
456 XYZ Feb 250
My result should be Employee_ID 123 for Feb, as Vendor ABC was not paid that month.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Employee, Vendor, Month, Total_amount ) AS
SELECT 123, 'ABC', 'Jan', 150 FROM DUAL UNION ALL
SELECT 123, 'DEF', 'Jan', 200 FROM DUAL UNION ALL
SELECT 456, 'ABC', 'Jan', 150 FROM DUAL UNION ALL
SELECT 456, 'XYZ', 'Jan', 250 FROM DUAL UNION ALL
SELECT 123, 'DEF', 'Feb', 200 FROM DUAL UNION ALL
SELECT 456, 'ABC', 'Feb', 150 FROM DUAL UNION ALL
SELECT 456, 'XYZ', 'Feb', 250 FROM DUAL;
Query 1:
WITH EVA( Employee, Vendor, Total_Amount ) AS (
SELECT DISTINCT
Employee,
Vendor,
Total_Amount
FROM table_name
),
Months ( Month ) AS (
SELECT DISTINCT MONTH FROM table_name
)
SELECT Employee, Vendor, Month, Total_Amount
FROM EVA CROSS JOIN Months
MINUS
SELECT Employee, Vendor, Month, Total_Amount
FROM table_name
Results:
| EMPLOYEE | VENDOR | MONTH | TOTAL_AMOUNT |
|----------|--------|-------|--------------|
| 123 | ABC | Feb | 150 |

SQL select case group by

I have a table 'LIST_USERS'.
Table Description -
USER_ID NUMBER(8)
LOGIN_ID VARCHAR2(8)
CREATE_DATE TIMESTAMP(6)
LOGIN_DATE TIMESTAMP(6)
Table data -
USER_ID LOGIN_ID CREATE_DATE LOGIN_DATE
---------------------------------------------------
101 test1 04/24/2016 null
102 test1 04/24/2016 04/29/2016
103 test2 04/25/2016 null
104 test2 04/26/2016 null
105 test3 04/27/2016 04/28/2016
106 test3 04/27/2016 04/29/2016
107 test4 04/28/2016 04/29/2016
987 test5 04/29/2016 null
109 test5 04/29/2016 null
108 test5 04/29/2016 04/29/2016
Condition - I need to fetch USER_ID, and LOGIN_ID from 'LIST_USERS' table based of max LOGIN_DATE. If LOGIN_DATE is null, I need to get the record based on max CREATE_DATE.
I need to get the below result -
USER_ID LOGIN_ID
---------------------
102 test1
104 test2
106 test3
107 test4
108 test5
I am using the below query. But it will give me only LOGIN_ID, and 'Login_Or_Create_Date' but I need USER_ID, and LOGIN_ID. Is there way I can get USER_ID as well as in the result shown above?
select LOGIN_ID,
(case when max(LOGIN_DATE) is null then max(CREATE_DATE)
else max(LOGIN_DATE) end) as Login_Or_Create_Date
from LIST_USERS;
Try this:
SELECT USER_ID, LOGIN_ID
FROM (
SELECT USER_ID, LOGIN_ID,
ROW_NUMBER() OVER (PARTITION BY LOGIN_ID
ORDER BY COALESCE(LOGIN_DATE, CREATE_DATE) DESC) AS rn
FROM LIST_USERS) t
WHERE t.rn = 1
Sounds like a job for keep dense_rank:
select min(user_id) keep (dense_rank last order by coalesce(login_date, create_date))
as user_id,
login_id
from list_users
group by login_id
order by user_id;
The last keeps the record with the latest login/create date; the coalesce() takes the login date first and falls back to the create date if that is null (or you could use nvl() instead of course). You could also do first and order by desc - the result is the same (if there are no nulls anyway, and it looks like there shouldn't be), but last feels more intuitive when you want the latest date I think.
Demo using your data in a CTE:
with list_users(user_id, login_id, create_date, login_date) as (
select 101, 'test1', date '2016-04-24', null from dual
union all select 102, 'test1', date '2016-04-24', date '2016-04-29' from dual
union all select 103, 'test2', date '2016-04-25', null from dual
union all select 104, 'test2', date '2016-04-26', null from dual
union all select 105, 'test3', date '2016-04-27', date '2016-04-28' from dual
union all select 106, 'test3', date '2016-04-27', date '2016-04-29' from dual
union all select 107, 'test4', date '2016-04-28', date '2016-04-29' from dual
)
select min(user_id) keep (dense_rank last order by coalesce(login_date, create_date))
as user_id,
login_id
from list_users
group by login_id
order by user_id;
USER_ID LOGIN
---------- -----
102 test1
104 test2
106 test3
107 test4
And with your modified data:
with list_users(user_id, login_id, create_date, login_date) as (
select 101, 'test1', date '2016-04-24', null from dual
union all select 102, 'test1', date '2016-04-24', date '2016-04-29' from dual
union all select 103, 'test2', date '2016-04-25', null from dual
union all select 104, 'test2', date '2016-04-26', null from dual
union all select 105, 'test3', date '2016-04-27', date '2016-04-28' from dual
union all select 106, 'test3', date '2016-04-27', date '2016-04-29' from dual
union all select 107, 'test4', date '2016-04-28', date '2016-04-29' from dual
union all select 987, 'test5', date '2016-04-29', null from dual
union all select 109, 'test5', date '2016-04-29', null from dual
union all select 108, 'test5', date '2016-04-29', date '2016-04-29' from dual
)
select min(user_id) keep (dense_rank last order by coalesce(login_date, create_date))
as user_id,
login_id
from list_users
group by login_id
order by user_id;
USER_ID LOGIN
---------- -----
102 test1
104 test2
106 test3
107 test4
108 test5

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