case statement trying to aggregate data in case statement using aggregate functions - sql

SELECT c.DESCRIPTION,
fobt.OBJECT_ID,
fobt.FA_OBJECT_TRANS_VALUE_TYPE,
fobt.OBJECT_TRANSACTION_ID,
fobt.OBJECT_TRANSACTION_DATE,
fobt.AMOUNT,
fobt.FA_YEAR,
fobt.FA_PERIOD,
SUM(fobt.AMOUNT) OVER(
PARTITION BY fobt.
ORDER BY fobt., fobt.OBJECT_TRANSACTION_DATE
) AS RUNTOTAL,
DENSE_RANK() OVER(
PARTITION BY fobt.OBJECT_ID
ORDER BY fobt.FA_YEAR DESC
) RK1,
DENSE_RANK()
OVER(
PARTITION BY fobt.OBJECT_ID, c.DESCRIPTION
ORDER BY
fobt.FA_YEAR,
fobt.FA_PERIOD,
c.DESCRIPTION DESC
)
rnk2
FROM FA_OBJECT_TRANSACTION fobt
INNER JOIN (SELECT DISTINCT COMPANY,
OBJECT_ID,
OBJECT_TRANSACTION_ID,
CODE_C
FROM FA_) FT
ON fobt. = FT.
AND fobt. = FT.OBJECT_TRANSACTION_ID
INNER JOIN CODE_C c
ON FT.CODE_C = c.CODE_C
AND FT.COMPANY = c.COMPANY
WHERE fobt.OBJECT_ID = 12345
AND fobt.FA_ IN ('Depreciation')
ORDER BY 2, 4
I'm trying to fix the DENSE_RANK for OBJECT_ID, DESCRIPTION, YEAR AND PERIOD. There is only one object id = 1234 but there are 2 locations (Rotterdam and South Windsor). Equipment A was logged in location South Windsor from 5/2015 to 3/2016 and then transferred to Rotterdam on 4/2016. I will like to have a DENSE_RANK by PARTITION by OBJECT_ID and LOCATION but the order by is not working properly. I would like to take the latest date(year and month by location of that piece of equipment. For example, Equipment A on 3/2016 at location South Windsor would be ranked 1 because it is the latest date at that location and then transferred to location Rotterdam on 4/2016 and that would also get tagged ranked 1 because it changed location. Below are the current results and second example is the desired result.

If I understood everything correctly You need to add only DESC after fa_year in last dense_rank:
... dense_rank() over(partition by object_id, description
order by fa_year desc, fa_period desc) as rk2
Alternatively use over (... order by transaction_id desc).
Test:
with t (DESCRIPTION, OBJECT_ID, transaction_id, AMOUNT, FA_YEAR, FA_PERIOD)
as (select 'South Windsor, CT', 22208, 278620, 257, 2015, 5 from dual union all
select 'South Windsor, CT', 22208, 287864, 257, 2015, 6 from dual union all
select 'South Windsor, CT', 22208, 305508, 257, 2015, 7 from dual union all
select 'South Windsor, CT', 22208, 322323, 257, 2015, 8 from dual union all
select 'South Windsor, CT', 22208, 330902, 257, 2015, 9 from dual union all
select 'South Windsor, CT', 22208, 340183, 257, 2015, 10 from dual union all
select 'South Windsor, CT', 22208, 350218, 257, 2015, 11 from dual union all
select 'South Windsor, CT', 22208, 359261, 257, 2015, 12 from dual union all
select 'South Windsor, CT', 22208, 374836, 257, 2016, 1 from dual union all
select 'South Windsor, CT', 22208, 382864, 257, 2016, 2 from dual union all
select 'South Windsor, CT', 22208, 388910, 257, 2016, 3 from dual union all
select 'Rotterdam, NY', 22208, 388915, 257, 2016, 4 from dual)
select description, transaction_id, fa_year, fa_period,
sum(amount) over (partition by object_id order by fa_year, fa_period) total,
dense_rank() over(partition by object_id order by fa_year desc) rk1,
dense_rank() over(partition by object_id, description
order by fa_year desc, fa_period desc) rk2
from t
order by object_id, transaction_id
Output:
DESCRIPTION TRANSACTION_ID FA_YEAR FA_PERIOD TOTAL RK1 RK2
----------------- -------------- ---------- ---------- ---------- ------ ------
South Windsor, CT 278620 2015 5 257 2 11
South Windsor, CT 287864 2015 6 514 2 10
South Windsor, CT 305508 2015 7 771 2 9
South Windsor, CT 322323 2015 8 1028 2 8
South Windsor, CT 330902 2015 9 1285 2 7
South Windsor, CT 340183 2015 10 1542 2 6
South Windsor, CT 350218 2015 11 1799 2 5
South Windsor, CT 359261 2015 12 2056 2 4
South Windsor, CT 374836 2016 1 2313 1 3
South Windsor, CT 382864 2016 2 2570 1 2
South Windsor, CT 388910 2016 3 2827 1 1
Rotterdam, NY 388915 2016 4 3084 1 1

Related

List the branch that monthly pays the most in salaries

I have this table, the expected output should be B003 since it's pays 54,000
STAFF
SALARY
BRAN
SL21
30000
B005
SG37
12000
B003
SG14
18000
B003
SA9
9000
B007
SG5
24000
B003
SL41
9000
B005
So far I only have this subquery, which isn't working how I expected.
SELECT BRANCHNO
FROM STAFF
WHERE (SALARY) IN (SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO);
This works but I want a subquery that returns the branchno
SELECT MAX(SUM(SALARY))
FROM STAFF
GROUP BY BRANCHNO;
select BRANCHNO max(sum_sal)
from (SELECT BRANCHNO, SUM(SALARY) sum_sal
FROM STAFF
GROUP BY BRANCHNO) q1
group by BRANCHNO ;
The column used to group the rows can be displayed. So, add BRANCHNO to your select clause.
One option is to use rank analytic function which ranks branches by sum of their salaries in descending order; you'd then return the one(s) that rank as the highest (rnk = 1).
Sample data:
SQL> with staff (staff, salary, bran) as
2 (select 'SL21', 30000, 'B005' from dual union all
3 select 'SG37', 12000, 'B003' from dual union all
4 select 'SG14', 18000, 'B003' from dual union all
5 select 'SA9' , 9000, 'B007' from dual union all
6 select 'SG5' , 24000, 'B003' from dual union all
7 select 'SL41', 9000, 'B005' from dual
8 )
Query:
9 select bran
10 from (select bran, rank() over (order by sum(salary) desc) rnk
11 from staff
12 group by bran
13 )
14 where rnk = 1;
BRAN
----
B003
SQL>

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

Oracle SQL - How to compare the number of rows between 2 sets in the same table?

in the table security_privileges I want to compare the number of rows for the last version_number with the number of rows for version_number BEFORE last_update = 01.03.2020.
I highlighted the two sets of rows that the result should identify. In the first set, there are 8 rows, in the second set there are 6 rows.
The output of what I expect from this example is right at the bottom.
Below I tried this sequel, but it didn't really work:
SELECT
cur.user_id,
cur.version_number,
cur.last_update,
(cur.last_update- prv.last_update)
FROM
security_privileges prv
INNER JOIN security_privileges cur
ON cur.version_number = prv.version_number +2
[![output][3]][3]
You can use window functions and conditional aggregation. The inner query calculates the maximum version number overall and before the specified date:
select user_id, max_vn, max_vn_pre,
sum(case when version_number = max_vn then 1 else 0 end) as max_vn_cnt,
sum(case when version_number = max_vn_pre then 1 else 0 end) as max_vn_pre_cnt
from (select sp.*,
max(version_number) over (partition by user_id) as max_vn,
max(case when last_update < date '2020-03-01' then version_number end) over (partition by user_id) as max_vn_pre
from security_privileges sp
) sp
group by user_id, max_vn, max_vn_pre;
This assumes that you want the results per user_id. If not, just remove the partition by in the two windowing clauses.
I would use a bit different approach, but still with window analytic functions:
at first, I'd filtered only rows you want, using dense_rank with partition by user_id, case when last_update>=date'2020-03-01' then 1 else 2 end, so this dense_rank will return 1 for all required rows and we can easily filter then using drnk=1:
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=b6925860bfa16d2f222d428f508c1b50
select *
from
(
SELECT
prv.*
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1;
Results:
USER_ID SECURITY_PRIVILEGE_ID VERSION_NUMBER LAST_USER_UPDATE_ID LAST_UPDATE DRNK
---------- --------------------- -------------- ------------------- ------------------- ----------
9867 20011 16 9954 2020-08-31 00:00:00 1
9867 20059 16 9955 2020-08-31 00:00:00 1
9867 20003 16 9956 2020-08-31 00:00:00 1
9867 20069 16 9957 2020-08-31 00:00:00 1
9867 20004 16 9958 2020-08-31 00:00:00 1
9867 20046 16 9959 2020-08-31 00:00:00 1
9867 20003 14 9832 2017-06-28 00:00:00 1
9867 20059 14 9833 2017-06-28 00:00:00 1
9867 20046 14 9834 2017-06-28 00:00:00 1
9867 20004 14 9835 2017-06-28 00:00:00 1
9867 20045 14 9836 2017-06-28 00:00:00 1
9867 20002 14 9837 2017-06-28 00:00:00 1
9867 20011 14 9838 2017-06-28 00:00:00 1
9867 20069 14 9839 2017-06-28 00:00:00 1
14 rows selected.
Second step even easier: we just need to aggregate those rows:
either using conditional aggregation: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=6467247bfde1dd978da4ce51067e3d70
select
user_id,
min(version_number) version_number1,
max(version_number) version_number2,
count(decode(grp,1,0)) cnt1,
count(decode(grp,2,0)) cnt2,
listagg(decode(grp,1,security_privilege_id), ',')
within group(order by security_privilege_id) sec_priv_ids1,
listagg(decode(grp,2,security_privilege_id), ',')
within group(order by security_privilege_id) sec_priv_ids2
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id;
Results:
USER_ID VERSION_NUMBER1 VERSION_NUMBER2 CNT1 CNT2 SEC_PRIV_IDS1 SEC_PRIV_IDS2
---------- --------------- --------------- ---------- ---------- ---------------------------------------- --------------------------------------------------
9867 14 16 6 8 20003,20004,20011,20046,20059,20069 20002,20003,20004,20011,20045,20046,20059,20069
or standard aggregation as your last screenshot shows: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=1eb736011e32754dc444c120946e8bea
select
user_id,
grp,
version_number,
last_update,
count(*) cnt,
listagg(security_privilege_id, ',')
within group(order by security_privilege_id) sec_priv_ids
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id,grp,version_number,last_update
order by 1,2;
Results:
USER_ID GRP VERSION_NUMBER LAST_UPDATE CNT SEC_PRIV_IDS
---------- ---------- -------------- ------------------- ---------- --------------------------------------------------
9867 1 16 2020-08-31 00:00:00 6 20003,20004,20011,20046,20059,20069
9867 2 14 2017-06-28 00:00:00 8 20002,20003,20004,20011,20045,20046,20059,20069
Full test case with sample data in CTE:
with security_privileges(user_id, security_privilege_id, version_number, last_user_update_id, last_update) as (
select 9867, 20011, 16, 9954, date'2020-08-31' from dual union all
select 9867, 20059, 16, 9955, date'2020-08-31' from dual union all
select 9867, 20003, 16, 9956, date'2020-08-31' from dual union all
select 9867, 20069, 16, 9957, date'2020-08-31' from dual union all
select 9867, 20004, 16, 9958, date'2020-08-31' from dual union all
select 9867, 20046, 16, 9959, date'2020-08-31' from dual union all
select 9867, 20011, 15, 9960, date'2020-08-13' from dual union all
select 9867, 20059, 15, 9961, date'2020-08-13' from dual union all
select 9867, 20004, 15, 9962, date'2020-08-13' from dual union all
select 9867, 20003, 15, 9963, date'2020-08-13' from dual union all
select 9867, 20046, 15, 9964, date'2020-08-13' from dual union all
select 9867, 20003, 14, 9832, date'2017-06-28' from dual union all
select 9867, 20059, 14, 9833, date'2017-06-28' from dual union all
select 9867, 20046, 14, 9834, date'2017-06-28' from dual union all
select 9867, 20004, 14, 9835, date'2017-06-28' from dual union all
select 9867, 20045, 14, 9836, date'2017-06-28' from dual union all
select 9867, 20002, 14, 9837, date'2017-06-28' from dual union all
select 9867, 20011, 14, 9838, date'2017-06-28' from dual union all
select 9867, 20069, 14, 9839, date'2017-06-28' from dual union all
select 9867, 20059, 13, 9840, date'2017-06-21' from dual union all
select 9867, 20011, 13, 9841, date'2017-06-21' from dual union all
select 9867, 20045, 13, 9842, date'2017-06-21' from dual union all
select 9867, 20003, 13, 9843, date'2017-06-21' from dual union all
select 9867, 20046, 13, 9844, date'2017-06-21' from dual union all
select 9867, 20002, 13, 9845, date'2017-06-21' from dual union all
select 9867, 20069, 13, 9846, date'2017-06-21' from dual union all
select 9867, 20069, 12, 9127, date'2017-06-02' from dual union all
select 9867, 20046, 12, 9128, date'2017-06-02' from dual union all
select 9867, 20003, 12, 9127, date'2017-06-02' from dual union all
select 9867, 20059, 12, 9128, date'2017-06-02' from dual union all
select 9867, 20011, 12, 9128, date'2017-06-02' from dual
)
select
user_id,
grp,
version_number,
last_update,
count(*) cnt,
listagg(security_privilege_id, ',')
within group(order by security_privilege_id) sec_priv_ids
from
(
SELECT
prv.*
,case when last_update>=date'2020-03-01' then 1 else 2 end grp
,dense_rank()
over(
partition by
user_id,
case when last_update>=date'2020-03-01' then 1 else 2 end
order by version_number desc
) as drnk
FROM
security_privileges prv
) v
where v.drnk = 1
group by user_id,grp,version_number,last_update
order by 1,2;

Grouping SQL results by Year and count

I have a table with the below structure:
I would like to retrieve the results using sql in the below format
I am new to SQL and can't figure out how to go about it. Is this possible without using procedures? How do I go achieve this? (the actual data size is huge and I have given only a snapshot here)
Part of it is pivoting. Totals by row and column (and really, even the pivoting) should be done in your reporting application, not in SQL. If you insist on doing it in SQL, there are fancier ways, but something like the silly query below will suffice.
with test_data (city, yr, ct) as (
select 'Tokyo' , 2016, 2 from dual union all
select 'Mumbai', 2013, 3 from dual union all
select 'Mumbai', 2014, 5 from dual union all
select 'Dubai' , 2011, 5 from dual union all
select 'Dubai' , 2015, 15 from dual union all
select 'Dubai' , 2016, 8 from dual union all
select 'London', 2011, 16 from dual union all
select 'London', 2012, 22 from dual union all
select 'London', 2013, 4 from dual union all
select 'London', 2014, 24 from dual union all
select 'London', 2015, 13 from dual union all
select 'London', 2016, 5 from dual
),
test_with_totals as (
select city, yr, ct from test_data union all
select city, 9999, sum(ct) from test_data group by city union all
select 'Grand Total', yr , sum(ct) from test_data group by yr union all
select 'Grand Total', 9999, sum(ct) from test_data
)
select * from test_with_totals
pivot ( sum (ct) for yr in (2011, 2012, 2013, 2014, 2015, 2016, 9999 as "Total"))
order by "Total";
Result:
CITY 2011 2012 2013 2014 2015 2016 Total
----------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
Tokyo 2 2
Mumbai 3 5 8
Dubai 5 15 8 28
London 16 22 4 24 13 5 84
Grand Total 21 22 7 29 28 15 122

Oracle : min max values within a repeating group [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have a table as below.
DATE_WORKED COUNTRY
1-Nov-13 United Kingdom
4-Nov-13 United Kingdom
5-Nov-13 India
6-Nov-13 India
7-Nov-13 India
8-Nov-13 United Kingdom
11-Nov-13 United Kingdom
12-Nov-13 India
13-Nov-13 India
14-Nov-13 India
15-Nov-13 United Kingdom
18-Nov-13 United Kingdom
19-Nov-13 India
20-Nov-13 India
21-Nov-13 India
22-Nov-13 United Kingdom
25-Nov-13 United Kingdom
26-Nov-13 India
27-Nov-13 India
28-Nov-13 India
29-Nov-13 United Kingdom
I am looking to find the start_date and end date for each stay in a country.
COUNTRY START_DATE END_Date
United Kingdom 1-Nov-13 4-Nov-13
India 5-Nov-13 7-Nov-13
United Kingdom 8-Nov-13 11-Nov-13
India 12-Nov-13 14-Nov-13
United Kingdom 15-Nov-13 18-Nov-13
India 19-Nov-13 21-Nov-13
United Kingdom 22-Nov-13 25-Nov-13
India 26-Nov-13 28-Nov-13
United Kingdom 29-Nov-13
Please help me with an SQL query to achieve this.
Thanks in advance.
Using Tabibitosan:
SQL> create table mytable (date_worked,country)
2 as
3 select to_date('1-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
4 select to_date('4-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
5 select to_date('5-Nov-13','dd-Mon-yy'), 'India' from dual union all
6 select to_date('6-Nov-13','dd-Mon-yy'), 'India' from dual union all
7 select to_date('7-Nov-13','dd-Mon-yy'), 'India' from dual union all
8 select to_date('8-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
9 select to_date('11-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
10 select to_date('12-Nov-13','dd-Mon-yy'), 'India' from dual union all
11 select to_date('13-Nov-13','dd-Mon-yy'), 'India' from dual union all
12 select to_date('14-Nov-13','dd-Mon-yy'), 'India' from dual union all
13 select to_date('15-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
14 select to_date('18-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
15 select to_date('19-Nov-13','dd-Mon-yy'), 'India' from dual union all
16 select to_date('20-Nov-13','dd-Mon-yy'), 'India' from dual union all
17 select to_date('21-Nov-13','dd-Mon-yy'), 'India' from dual union all
18 select to_date('22-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
19 select to_date('25-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual union all
20 select to_date('26-Nov-13','dd-Mon-yy'), 'India' from dual union all
21 select to_date('27-Nov-13','dd-Mon-yy'), 'India' from dual union all
22 select to_date('28-Nov-13','dd-Mon-yy'), 'India' from dual union all
23 select to_date('29-Nov-13','dd-Mon-yy'), 'United Kingdom' from dual
24 /
Table created.
SQL> with tabibitosan as
2 ( select row_number() over (order by date_worked)
3 - row_number() over (partition by country order by date_worked) grp
4 , date_worked
5 , country
6 from mytable
7 )
8 select country
9 , min(date_worked) start_date
10 , max(date_worked) end_date
11 from tabibitosan
12 group by country
13 , grp
14 order by start_date
15 /
COUNTRY START_DATE END_DATE
-------------- ------------------- -------------------
United Kingdom 01-11-2013 00:00:00 04-11-2013 00:00:00
India 05-11-2013 00:00:00 07-11-2013 00:00:00
United Kingdom 08-11-2013 00:00:00 11-11-2013 00:00:00
India 12-11-2013 00:00:00 14-11-2013 00:00:00
United Kingdom 15-11-2013 00:00:00 18-11-2013 00:00:00
India 19-11-2013 00:00:00 21-11-2013 00:00:00
United Kingdom 22-11-2013 00:00:00 25-11-2013 00:00:00
India 26-11-2013 00:00:00 28-11-2013 00:00:00
United Kingdom 29-11-2013 00:00:00 29-11-2013 00:00:00
9 rows selected.
Somewhat more complicated than #RobVanWijk's answer:
with v_data as (
select to_date('2013-11-01', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-04', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-05', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-06', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-07', 'YYYY-MM-DD') as date_worked, 'India' as country from dual union all
select to_date('2013-11-08', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-11', 'YYYY-MM-DD') as date_worked, 'UK' as country from dual union all
select to_date('2013-11-12', 'YYYY-MM-DD') as date_worked, 'India' as country from dual
)
select country, start_day, end_day from (
select
v3.*,
row_number() over (partition by start_day, end_day order by date_worked) as rn
from (
select
v2.*,
max(case when is_first_day = 1 then date_worked else null end) over (Partition by null order by date_worked) as start_day,
min(case when is_last_day = 1 then date_worked else null end) over (Partition by null order by date_worked desc) as end_day
from (
select
v1.*,
(case when country <> nvl(country_next_day, 'n/a') then 1 else 0 end) is_last_day,
(case when country <> nvl(country_prev_day, 'n/a') then 1 else 0 end) is_first_day
from (
select
date_worked,
country,
lead(country) over (order by date_worked) as country_next_day,
lag(country) over (order by date_worked) as country_prev_day
from v_data
) v1
) v2
order by date_worked
) v3
) v4 where rn=1
Explanation:
for each workday, get the successor and the predecessor using the lag() and lead() analytic functions (v1)
for each workday, decide whether it is the start or end of a group by comparing its country to the previous and next countries (v2)
for each group, compute the start and end day (v3)
for each workday, compute its ordering inside its group (v4)
return only workdays with ordering 1
Try this query:
select country,min(date_worked) as start_date,max(date_worked) as end_date
from (select country,date_worked,
Row_Number() over(order by date_worked)
-Row_Number() over(partition by country order by date_worked) as disTance
from YourTable)
group by disTance,country order by min(date_worked);