Creating ranged mapping records from yearly entries - sql

I have a table that maps an ID to an Associated ID (AssocID) over time and the database is built having one record per year. I would like to roll up the table have one record for each period of association.
Current Example:
ID AssocID Start End
1 a 2000 2001
1 a 2001 2002
1 b 2002 2003
1 b 2003 2004
1 a 2004 2005
...
1 a 2017 2018
2 c 2000 2001
2 c 2001 2002
2 d 2002 2003
...
2 d 2017 2018
and I am trying to make it look more like this:
ID AssocID Start End
1 a 2000 2002
1 b 2002 2004
1 a 2004 2018
2 c 2000 2002
2 d 2002 2018
My main problem is that ID '1' goes back to AssocID 'a' after time and using DISTINCT (ID, AssocID) and MIN (Start) misses the second time ID '1' maps to AssocID 'a'
Any help appreciated :)

You can use this.
-- Sample Data
DECLARE #MyTable TABLE (ID INT, AssocID VARCHAR(10), Start INT, [End] INT)
INSERT INTO #MyTable VALUES
(1, 'a', 2000, 2001),
(1, 'a', 2001, 2002),
(1, 'b', 2002, 2003),
(1, 'b', 2003, 2004),
(1, 'a', 2004, 2005),
(1, 'a', 2017, 2018),
(2, 'c', 2000, 2001),
(2, 'c', 2001, 2002),
(2, 'd', 2002, 2003),
(2, 'd', 2017, 2018)
-- Query
SELECT ID, AssocID, MIN(Start) [Start], MAX([End]) [End] FROM
( SELECT *,
GRP = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Start) - ROW_NUMBER() OVER(PARTITION BY ID, AssocID ORDER BY Start)
FROM #MyTable ) T
GROUP BY ID, AssocID, GRP
ORDER BY ID, [Start]
Result:
ID AssocID Start End
----------- ---------- ----------- -----------
1 a 2000 2002
1 b 2002 2004
1 a 2004 2018
2 c 2000 2002
2 d 2002 2018

This is an example of a gaps and islands problem. You need to first identify the start of each group grp_start and then group by each grp to find the min / max
declare #T table (ID int, AssocID varchar(3), Start int, [End] int)
insert into #T (ID, AssocID, Start, [End]) values
(1, 'a', 2000, 2001),(1, 'a', 2001, 2002),(1, 'b', 2002, 2003),(1, 'b', 2003, 2004),(1, 'a', 2004, 2005),(1, 'a', 2005, 2006),(1, 'a', 2006, 2007),(1, 'a', 2007, 2008),(1, 'a', 2008, 2009),(1, 'a', 2009, 2010),(1, 'a', 2010, 2011),(1, 'a', 2011, 2012),(1, 'a', 2012, 2013),(1, 'a', 2013, 2014),(1, 'a', 2014, 2015),(1, 'a', 2015, 2016),(1, 'a', 2016, 2017),(1, 'a', 2017, 2018),(2, 'c', 2000, 2001),(2, 'c', 2001, 2002),(2, 'd', 2002, 2003),(2, 'd', 2017, 2018)
select
ID,
AssocID,
min(Start),
max([End])
from
(
select *,
sum([grp_start]) over (partition by ID, AssocID order by [End]) as grp
from
(
select *,
case
when
lag([End]) over (partition by ID, AssocID order by [End]) <> [Start]
then 1 else 0
end as [grp_start]
from #T
) as T
)as T
group by ID, AssocID, grp
order by ID, min(Start), max([End])

Related

Count daily fidelity

I have the below table and I would like to count, day by day, the number of distinct people who logged in everyday. For example, for day 1, everyone logged in, so it's 4. For day 4, there's just one person ID who logged in everyday since day 1, so the count would be 1.
DAY
PERSON_ID
1
01
1
02
1
03
1
04
2
01
2
02
2
03
3
01
4
02
4
01
Expected output.
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
1
4
01, 02, 03, 04
2
3
01, 02, 03
3
1
01
4
1
01
EDIT: the query should also work on the below data.
with t ( DAY, PERSON_ID ) AS(
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL)
Expected output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
EXPLANATION
10
3
01, 02, 04
Three unique people in day 10
12
2
01, 02
Day 11 does not have values, so it's not included. From those in day 10, only 2 appear in day 12
13
1
01
From those in day 10 and 12, only 01 appears in day 13
14
1
01
From those in day 10, 12 and 13, only 01 appears in day 14
You can use listagg() with group by clause. If day is always start from the 1 and increases by 1 then you can use below query. He with the help of exits I have selected only those person_id which are available in all the previous days.
create table yourtable(DAY int, PERSON_ID varchar(10));
insert into yourtable values(1, '01');
insert into yourtable values(1, '02');
insert into yourtable values(1, '03');
insert into yourtable values(1, '04');
insert into yourtable values(2, '01');
insert into yourtable values(2, '02');
insert into yourtable values(2, '03');
insert into yourtable values(3, '01');
insert into yourtable values(4, '02');
insert into yourtable values(4, '01');
Query:
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(day)=a.day)
group by day;
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
1
4
01,02,03,04
2
3
01,02,03
3
1
01
4
1
01
db<fiddle here
Instead of day sequence if you had increasing dates in day column:
create table yourtable(DAY date, PERSON_ID varchar(10));
insert into yourtable values(date '2021-01-01', '01');
insert into yourtable values(date '2021-01-01', '02');
insert into yourtable values(date '2021-01-01', '03');
insert into yourtable values(date '2021-01-01', '04');
insert into yourtable values(date '2021-01-02', '01');
insert into yourtable values(date '2021-01-02', '02');
insert into yourtable values(date '2021-01-02', '03');
insert into yourtable values(date '2021-01-03', '01');
insert into yourtable values(date '2021-01-04', '02');
insert into yourtable values(date '2021-01-04', '01');
Query:
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(day)=( max(day)- min(day))+1)
group by day;
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
01-JAN-21
4
01,02,03,04
02-JAN-21
3
01,02,03
03-JAN-21
1
01
04-JAN-21
1
01
db<fiddle here
Revised answer
create table yourtable(DAY int, PERSON_ID varchar(10));
insert into yourtable(day,person_id)
with cte ( DAY, PERSON_ID ) AS(
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL)
select * from cte ;
Query#1 (for Oracle 19c and later)
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG(distinct person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(DISTINCT day)=(select COUNT( distinct DAY) from yourtable where day<=a.day))
group by day;
Query#1 (for Oracle 18c and earlier)
select day, count(person_id) as PEOPLE_LOGGED_EVERYDAY, LISTAGG( person_id,',') WITHIN GROUP(ORDER BY person_id) AS PEOPLE
from
(
select distinct day, person_id
from yourtable a
where exists (select 1 from yourtable b where b.day<=a.day and a.person_id=b.person_id
group by person_id having count(DISTINCT day)=(select COUNT( distinct DAY) from yourtable where day<=a.day))
)t group by day
Output:
DAY
PEOPLE_LOGGED_EVERYDAY
PEOPLE
10
3
01,02,04
12
2
01,02
13
1
01
14
1
01
db<fiddle here
In Standard SQL, I would approach this by doing the following:
Enumerate the days for each person.
Determine the earliest day for each person.
Filter where the earliest day is "1" and the enumeration equals the days.
Then aggregate:
select day, count(*),
listagg(person_id, ',') within group (order by person_id)
from (select t.*,
row_number() over (partition by person_id order by day) as seqnum,
min(day) over (partition by person_id) as min_day
from t
) t
where seqnum = day and min_day = 1
group by day
order by day;
Note only is this simpler than using match recognize, but I would guess that the performance would be much better too.
You can use either:
SELECT DAY,
COUNT(DISTINCT person_id) AS num_people
FROM (
SELECT t.*,
DENSE_RANK() OVER (ORDER BY day)
- DENSE_RANK() OVER (PARTITION BY person_id ORDER BY day) AS day_grp
FROM table_name t
)
WHERE day_grp = 0
GROUP BY day
ORDER BY day
or MATCH_RECOGNIZE to find the successive days:
SELECT day,
COUNT(
DISTINCT
CASE cls WHEN 'CONSECUTIVE_DAYS' THEN person_id END
) AS num_people
FROM (
SELECT t.*,
DENSE_RANK() OVER (ORDER BY day) AS day_rank
FROM table_name t
)
MATCH_RECOGNIZE(
PARTITION BY person_id
ORDER BY day
MEASURES
classifier() AS cls
ALL ROWS PER MATCH
PATTERN ( ^ consecutive_days* )
DEFINE
consecutive_days AS COALESCE( PREV(day_rank) + 1, 1 ) = day_rank
)
GROUP BY day
ORDER BY day
Which, for the sample data:
CREATE TABLE table_name ( DAY, PERSON_ID ) AS
SELECT 1, '01' FROM DUAL UNION ALL
SELECT 1, '02' FROM DUAL UNION ALL
SELECT 1, '03' FROM DUAL UNION ALL
SELECT 1, '04' FROM DUAL UNION ALL
SELECT 2, '01' FROM DUAL UNION ALL
SELECT 2, '02' FROM DUAL UNION ALL
SELECT 2, '03' FROM DUAL UNION ALL
SELECT 3, '01' FROM DUAL UNION ALL
SELECT 3, '02' FROM DUAL UNION ALL
SELECT 4, '01' FROM DUAL;
Outputs:
DAY
NUM_PEOPLE
1
4
2
3
3
2
4
1
and for the sample data:
CREATE TABLE table_name ( DAY, PERSON_ID ) AS
SELECT 10, '01' FROM DUAL UNION ALL
SELECT 10, '02' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 10, '04' FROM DUAL UNION ALL
SELECT 12, '01' FROM DUAL UNION ALL
SELECT 12, '02' FROM DUAL UNION ALL
SELECT 12, '03' FROM DUAL UNION ALL
SELECT 13, '04' FROM DUAL UNION ALL
SELECT 13, '01' FROM DUAL UNION ALL
SELECT 14, '02' FROM DUAL UNION ALL
SELECT 14, '01' FROM DUAL
Outputs:
DAY
NUM_PEOPLE
10
3
12
2
13
1
14
1
db<>fiddle here

Compare data of current week against same week of previous years

I have this table that contains sales by stores & date.
-------------------------------------------
P_DATE - P_STORE - P_SALES
-------------------------------------------
2019-02-05 - S1 - 5000
2019-02-05 - S2 - 9850
2018-06-17 - S1 - 6980
2018-05-17 - S2 - 6590
..
..
..
-------------------------------------------
I want to compare Sum of sales for each store of last 10 weeks of this year with same week of previous years.
I want a result like this :
---------------------------------------------------
Week - Store - Sales-2019 - Sales2018
---------------------------------------------------
20 - S1 - 2580 - 2430
20 - S2 - 2580 - 2430
.
.
10 - S1 - 5905 - 5214
10 - S2 - 4789 - 6530
---------------------------------------------------
I'v tried this :
Select
[Week] = DATEPART(WEEK, E_Date),
[Store] = E_store
[Sales 2019] = Case when Year(P_date) = '2019' Then Sum (P_Sales)
[Sales 2018] = Case when Year(P_date) = '2018' Then Sum (P_Sales)
From
PIECE
Group by
DATEPART(WEEK, E_Date),
E_store
I need your help please.
This script will consider 10 weeks including current week-
WITH wk_list (COMMON,DayMinus)
AS
(
SELECT 1,0 UNION ALL
SELECT 1,1 UNION ALL
SELECT 1,2 UNION ALL
SELECT 1,3 UNION ALL
SELECT 1,4 UNION ALL
SELECT 1,5 UNION ALL
SELECT 1,6 UNION ALL
SELECT 1,7 UNION ALL
SELECT 1,8 UNION ALL
SELECT 1,9
)
SELECT
DATEPART(ISO_WEEK, P_DATE) WK,
P_STORE,
SUM(CASE WHEN YEAR(P_DATE) = 2019 THEN P_SALES ELSE 0 END) SALES_2019,
SUM(CASE WHEN YEAR(P_DATE) = 2018 THEN P_SALES ELSE 0 END) SALES_2018
FROM your_table
WHERE YEAR(P_DATE) IN (2019,2018)
AND DATEPART(ISO_WEEK, P_DATE) IN
(
SELECT A.WKNUM-wk_list.DayMinus AS [WEEK NUMBER]
FROM wk_list
INNER JOIN (
SELECT 1 AS COMMON,DATENAME(ISO_WEEK,GETDATE()) WKNUM
) A ON wk_list.COMMON = A.COMMON
)
GROUP BY DATEPART(ISO_WEEK, P_DATE),P_STORE
But if you want to exclude current week, just replace the following part in above script
, wk_list (COMMON,DayMinus)
AS
(
SELECT 1,1 UNION ALL
SELECT 1,2 UNION ALL
SELECT 1,3 UNION ALL
SELECT 1,4 UNION ALL
SELECT 1,5 UNION ALL
SELECT 1,6 UNION ALL
SELECT 1,7 UNION ALL
SELECT 1,8 UNION ALL
SELECT 1,9 UNION ALL
SELECT 1,10
)
Is this what you're looking for?
DECLARE #t TABLE (TransactionID INT, Week INT, Year INT, Amount MONEY)
INSERT INTO #t
(TransactionID, Week, Year, Amount)
VALUES
(1, 20, 2018, 50),
(2, 20, 2019, 20),
(3, 19, 2018, 35),
(4, 19, 2019, 40),
(5, 20, 2018, 70),
(6, 20, 2019, 80)
SELECT TOP 10 Week, [2018], [2019] FROM (SELECT Week, Year, SUM(Amount) As Amount FROM #t GROUP BY Week, Year) t
PIVOT
(
SUM(Amount)
FOR Year IN ([2018], [2019])
) sq
ORDER BY Week DESC

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 |

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

Finding records over continuous date range

I need to write a query which returns loans that have been taken in a semester, the semesters are defined in this way:
Jan - April - Spring
May - August - Summer
September - Dec - Fall
Now my loans can start at any time and can span for any duration of time:
say a loan starts in Dec 2013 to Oct 2014, then the loan has spanned the four semesters:
Fall 2013
Spring 2014
Summer 2014
Fall 2014
And so when counting the number of loans for each semester for each year, the count of this loan will be present in all the four semesters
Fall 2013
Spring 2014
Summer 2014
Fall 2014
The schema of the loan table is
LOAN(Loan_ID, St_id#, Comp_id#, Start_Date, Date_Returned)
where start_date and date_returned are the corresponding start and end dates of a loan.
What I have so far does not take into account loans that have overlapped into semesters.
SELECT extract(YEAR FROM start_date) AS year,
CASE WHEN extract(MONTH FROM start_date) <= 4 THEN 'spring'
WHEN extract(MONTH FROM start_date) > 4 AND extract(MONTH FROM start_date) <=8 THEN 'summer'
ELSE 'fall' END AS semester,
Count(comp_id) AS num_of_loans
FROM loan
GROUP BY (extract(YEAR FROM start_date),
CASE WHEN extract(MONTH FROM start_date) <= 4 THEN 'spring'
WHEN extract(MONTH FROM start_date) > 4 AND extract(MONTH FROM start_date) <=8 THEN 'summer'
ELSE 'fall' END)
ORDER BY YEAR, Decode(semester, 'spring', 1, 'summer', 2, 'fall', 3);
Sample Input:
INSERT INTO loan VALUES('L101', '101', 'H101', TO_DATE('2014-10-19','YYYY-MM-DD'), TO_DATE('2014-10-30','YYYY-MM-DD'));
INSERT INTO loan VALUES('L102', '102', 'H101', TO_DATE('2014-10-31','YYYY-MM-DD'), TO_DATE('2014-11-03','YYYY-MM-DD'));
INSERT INTO loan VALUES('L103', '102', 'H102', TO_DATE('2014-10-24','YYYY-MM-DD'), TO_DATE('2014-10-30','YYYY-MM-DD'));
INSERT INTO loan VALUES('L104', '101', 'H102', TO_DATE('2014-10-31','YYYY-MM-DD'), TO_DATE('2014-11-03','YYYY-MM-DD'));
INSERT INTO loan VALUES('L105', '102', 'H102', TO_DATE('2014-11-04','YYYY-MM-DD'), TO_DATE('2014-11-10','YYYY-MM-DD'));
INSERT INTO loan VALUES('L106', '103', 'N101', TO_DATE('2014-10-15','YYYY-MM-DD'), TO_DATE('2014-10-20','YYYY-MM-DD'));
INSERT INTO loan VALUES('L107', '201', 'N101', TO_DATE('2013-09-01','YYYY-MM-DD'), TO_DATE('2013-09-19','YYYY-MM-DD'));
INSERT INTO loan VALUES('L108', '201', 'N102', TO_DATE('2013-11-15','YYYY-MM-DD'), TO_DATE('2013-11-19','YYYY-MM-DD'));
INSERT INTO loan VALUES('L109', '202', 'N102', TO_DATE('2013-10-10','YYYY-MM-DD'), TO_DATE('2013-10-19','YYYY-MM-DD'));
INSERT INTO loan VALUES('L110', '202', 'N102', TO_DATE('2013-08-23','YYYY-MM-DD'), TO_DATE('2013-09-02','YYYY-MM-DD'));
INSERT INTO loan VALUES('L111', '202', 'N104', TO_DATE('2014-11-12','YYYY-MM-DD'), TO_DATE('2014-11-15','YYYY-MM-DD'));
INSERT INTO loan VALUES('L112', '203', 'N104', TO_DATE('2014-08-27','YYYY-MM-DD'), TO_DATE('2014-08-31','YYYY-MM-DD'));
INSERT INTO loan VALUES('L113', '301', 'N104', TO_DATE('2014-09-13','YYYY-MM-DD'), TO_DATE('2014-09-23','YYYY-MM-DD'));
INSERT INTO loan VALUES('L114', '301', 'N104', TO_DATE('2014-10-23','YYYY-MM-DD'), TO_DATE('2014-10-24','YYYY-MM-DD'));
INSERT INTO loan VALUES('L115', '301', 'N107', TO_DATE('2014-10-11','YYYY-MM-DD'), TO_DATE('2014-10-14','YYYY-MM-DD'));
INSERT INTO loan VALUES('L116', '302', 'N107', TO_DATE('2014-09-10','YYYY-MM-DD'), TO_DATE('2014-09-15','YYYY-MM-DD'));
INSERT INTO loan VALUES('L117', '101', 'H101', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L118', '101', 'H103', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L119', '101', 'H104', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L120', '101', 'H103', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L121', '101', 'H104', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L122', '101', 'H105', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L123', '101', 'H106', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L124', '101', 'H106', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
INSERT INTO loan VALUES('L125', '101', 'H105', TO_DATE('2014-11-19','YYYY-MM-DD'), null);
Sample Output:
SEMESTER YEARS NUM_LOANS
Spring 2013 0
Summer 2013 3
Fall 2013 5
Spring 2014 1
Summer 2014 2
Fall 2014 20
what I get:
YEAR SEMESTER NUM_OF_LOANS
2013 summer 2
2013 fall 3
2014 summer 1
2014 fall 19
Thanks!
Query ::
WITH FNL AS(
SELECT EXTRACT(YEAR FROM A.START_DATE) AS YEAR,B.SEMESTER AS semester,0 as num_of_loans
FROM LOAN A,(SELECT 1 SEQ,'spring' SEMESTER,0 NUM_OF_LOANS FROM DUAL
UNION ALL
SELECT 2,'summer' SEMESTER,0 num_of_loans from dual
UNION ALL
SELECT 3,'fall' SEMESTER ,0 num_of_loans FROM DUAL) b
GROUP BY B.SEMESTER,EXTRACT(YEAR FROM START_DATE)
union all
SELECT extract(YEAR FROM start_date) AS year,
CASE WHEN extract(MONTH FROM start_date) <= 4 THEN 'spring'
WHEN extract(MONTH FROM start_date) > 4 AND extract(MONTH FROM start_date) <=8 THEN 'summer'
ELSE 'fall' END AS semester,
Count(comp_id) AS num_of_loans
FROM loan
GROUP BY (extract(YEAR FROM start_date),
CASE WHEN extract(MONTH FROM start_date) <= 4 THEN 'spring'
WHEN extract(MONTH FROM start_date) > 4 AND extract(MONTH FROM start_date) <=8 THEN 'summer'
ELSE 'fall' END)
)
select year,semester,sum(num_of_loans) from fnl group by year,semester
ORDER BY YEAR, Decode(semester, 'spring', 1, 'summer', 2, 'fall', 3);
Output :
Year semester num_of_loans
2013 spring 0
2013 summer 1
2013 fall 3
2014 spring 0
2014 summer 1
2014 fall 20
i hope this helpful for you.Good Luck.. :)
I don't currently have an operating SQL Server available at the moment, but I believe it should look something like this:
select #min = min(start_date),
#max = max(end_date)
FROM LOAN
SELECT #minYear = YEAR(#min),
#minSem = CEILING(MONTH(#min)/4),
#maxYear = YEAR(#max),
#maxSem = CEILING(MONTH(#max)/4),
#semCount= (4-#minSem+#maxSem)+(#maxYear-#minYear-1)*3,
#i = 0
CREATE TABLE #TMP (Semester INT, Year INT, LoanCount INT)
WHILE #i < #semCount BEGIN
SELECT #curSem = (#minSem + #i)%3, -- 3 semesters per year
#curYear = #minYear + FLOOR((#minSem +#i)/3)
INSERT INTO #TMP
SELECT #curSem,
#curYear,
(SELECT COUNT * FROM LOAN
WHERE YEAR(end_date) >= #curYear
AND CEILING(MONTH(end_date)/4)>+#curSem
YEAR(start_date) <= #curYear
AND CEILING(MONTH(start_date)/4)<=#curSem) as loanCount
SELECT #i = #i + 1
END
SELECT * FROM #TMP
Hope this helps. Good Luck
You have to join semestrs with loans, here's a brief example:
with loan as (
-- This is your sample data
select TO_DATE('2014-10-19','YYYY-MM-DD') startdate, TO_DATE('2014-10-30','YYYY-MM-DD') datereturned from dual union all
select TO_DATE('2014-10-31','YYYY-MM-DD'), TO_DATE('2014-11-03','YYYY-MM-DD') from dual union all
select TO_DATE('2014-10-24','YYYY-MM-DD'), TO_DATE('2014-10-30','YYYY-MM-DD') from dual union all
select TO_DATE('2014-10-31','YYYY-MM-DD'), TO_DATE('2014-11-03','YYYY-MM-DD') from dual union all
select TO_DATE('2014-11-04','YYYY-MM-DD'), TO_DATE('2014-11-10','YYYY-MM-DD') from dual union all
select TO_DATE('2014-10-15','YYYY-MM-DD'), TO_DATE('2014-10-20','YYYY-MM-DD') from dual union all
select TO_DATE('2013-09-01','YYYY-MM-DD'), TO_DATE('2013-09-19','YYYY-MM-DD') from dual union all
select TO_DATE('2013-11-15','YYYY-MM-DD'), TO_DATE('2013-11-19','YYYY-MM-DD') from dual union all
select TO_DATE('2013-10-10','YYYY-MM-DD'), TO_DATE('2013-10-19','YYYY-MM-DD') from dual union all
select TO_DATE('2013-08-23','YYYY-MM-DD'), TO_DATE('2013-09-02','YYYY-MM-DD') from dual union all
select TO_DATE('2014-11-12','YYYY-MM-DD'), TO_DATE('2014-11-15','YYYY-MM-DD') from dual union all
select TO_DATE('2014-08-27','YYYY-MM-DD'), TO_DATE('2014-08-31','YYYY-MM-DD') from dual union all
select TO_DATE('2014-09-13','YYYY-MM-DD'), TO_DATE('2014-09-23','YYYY-MM-DD') from dual union all
select TO_DATE('2014-10-23','YYYY-MM-DD'), TO_DATE('2014-10-24','YYYY-MM-DD') from dual union all
select TO_DATE('2014-10-11','YYYY-MM-DD'), TO_DATE('2014-10-14','YYYY-MM-DD') from dual union all
select TO_DATE('2014-09-10','YYYY-MM-DD'), TO_DATE('2014-09-15','YYYY-MM-DD') from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null from dual union all
select TO_DATE('2014-11-19','YYYY-MM-DD'), null FROM dual
),
timescale as ( -- Timescale boundaries to build a list of semesters
select to_date ('01.01.2013', 'dd.mm.yyyy') d1, -- Start date
to_date ('01.12.2015', 'dd.mm.yyyy') d2 -- End date
from dual
),
months as ( -- List of months withing the timescale , semestr is specified for each month
select y || '*' ||
case
when m < 5 then 'Spring'
when m > 8 then 'Fall'
else 'Summer'
end s,
d d1,
add_months(d,1)-1 d2
from (
select add_months (d1, level-1) d,
extract (year from add_months (d1, level-1)) y,
extract (month from add_months (d1, level-1)) m
from timescale
connect by add_months (d1, level-2) < d2
)
),
semestr as( -- List of semesters with their boundaries, built by grouping the list of months
select s, min(d1) d1, max(d2) d2
from months
group by s
)
-- the query itself - quite easy
select s,
(select count(1) from loan where startdate <= d2 and nvl(datereturned, d1) >= d1 ) x
from semestr
order by d1