Compare data of current week against same week of previous years - sql

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

Related

Find customer ids who ordered more in 2019 than they did in 2018

This one was asked in an interview.
Below is the structure of the table.
Shipments- Shipment_id,Price, Order_id, Ship_date, Delivery_Location, Price, Ship_method , ShipETA,
Customer-Customer_id, order_id, customer_address, prime_eligible
Order - Order_id , Order_Qty, Order_date , Order_location, Item_id , Shipment_id
Item- Item _id , Item_description, Item_Location
Question: List of customer ids who ordered more in 2019 than they did in 2018.
SELECT customer_id
FROM Customer join Order using (order_id)
WHERE YEAR(Order_date) IN (2019)
GROUP BY customer_id
HAVING
SUM(CASE WHEN YEAR(Order_date) = 2019 THEN Order_Qty ELSE 0 END)
> SUM(CASE WHEN YEAR(Order_date) = 2018 THEN Order_Qty ELSE 0 END)
Unfortunately, I don't have sample data can anyone help with the approach to solve this one.
Data model you posted looks somewhat "strange"; I wouldn't keep ORDER_ID in CUSTOMER table, it just doesn't belong there. I'd add CUSTOMER_ID into SHIPMENT instead.
Anyway, here's one option:
sample data in lines #1 - 21
temp CTE calculates summaries (ordered quantities) per customers and years (just or 2018 and 2019)
final query just checks who ordered more items in 2019 than in 2018
SQL> with
2 customer (customer_id, order_id) as
3 (select 'A', 1 from dual union all
4 select 'A', 3 from dual union all
5 select 'B', 2 from dual union all
6 select 'B', 4 from dual union all
7 select 'B', 5 from dual union all
8 --
9 select 'A', 6 from dual
10 ),
11 orders (order_id, order_qty, order_date) as
12 -- A's summaries: 2018: 100 / 2019: 400
13 -- B's summaries: 2018: 400 / 2019: 300 --> should be returned
14 (select 1, 100, date '2018-05-03' from dual union all -- A
15 select 2, 200, date '2018-07-23' from dual union all -- B
16 select 3, 400, date '2019-04-02' from dual union all -- A
17 select 4, 300, date '2019-08-14' from dual union all -- B
18 select 5, 200, date '2018-11-14' from dual union all -- B
19 --
20 select 6, 900, date '2020-01-01' from dual -- A
21 ),
22 -- summaires per customers and years
23 temp as
24 (select c.customer_id,
25 extract(year from o.order_date) as year,
26 sum(o.order_qty) sum_qty
27 from customer c join orders o on o.order_id = c.order_id
28 where extract(year from o.order_date) in (2018, 2019)
29 group by c.customer_id,
30 extract(year from o.order_date)
31 )
32 select t.customer_id
33 from temp t
34 group by t.customer_id
35 having sum(case when t.year = 2019 then t.sum_qty end) <
36 sum(case when t.year = 2018 then t.sum_qty end);
CUSTOMER_ID
-----------
B
SQL>

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

SQL: Create multiple rows for a record based on months between two dates

My table has records as below for different Id's and different start and end dates
ID, Startdate, Enddate
1, 2017-02-14, 2018-11-05
I want to write an SQL without using date dimension table that gives below output: Basically one record for each month between start and end date.
1, 2017, 02
1, 2017, 03
1, 2017, 04
1, 2017, 05
1, 2017, 06
1, 2017, 07
1, 2017, 08
1, 2017, 09
1, 2017, 10
1, 2017, 11
1, 2017, 12
1, 2018, 01
1, 2018, 02
1, 2018, 03
1, 2018, 04
1, 2018, 05
1, 2018, 06
1, 2018, 07
1, 2018, 09
1, 2018, 10
1, 2018, 11
Please use below query example:
set #start_date = '2017-02-14';
set #end_date = LAST_DAY('2018-11-05');
WITH RECURSIVE date_range AS
(
select MONTH(#start_date) as month_, YEAR(#start_date) as year_, DATE_ADD(#start_date, INTERVAL 1 MONTH) as next_month_date
UNION
SELECT MONTH(dr.next_month_date) as month_, YEAR(dr.next_month_date) as year_, DATE_ADD(dr.next_month_date, INTERVAL 1 MONTH) as next_month_date
FROM date_range dr
where next_month_date <= #end_date
)
select month_, year_ from date_range
order by next_month_date desc
This is what I did and it worked like a charm:
-- sample data
WITH table_data
AS (
SELECT 1 AS id
,cast('2017-08-14' AS DATE) AS start_dt
,cast('2018-12-16' AS DATE) AS end_dt
UNION ALL
SELECT 2 AS id
,cast('2017-09-14' AS DATE) AS start_dt
,cast('2019-01-16' AS DATE) AS end_dt
)
-- find minimum date from the data
,starting_date (start_date)
AS (
SELECT min(start_dt)
FROM TABLE_DATA
)
--get all months between min and max dates
,all_dates
AS (
SELECT last_day(add_months(date_trunc('month', start_date), idx * 1)) month_date
FROM starting_date
CROSS JOIN _v_vector_idx
WHERE month_date <= add_months(start_date, abs(months_between((
SELECT min(start_dt) FROM TABLE_DATA), (SELECT max(end_dt) FROM TABLE_DATA))) + 1)
ORDER BY month_date
)
SELECT id
,extract(year FROM month_date)
,extract(month FROM month_date)
,td.start_dt
,td.end_dt
FROM table_data td
INNER JOIN all_dates ad
ON ad.month_date > td.start_dt
AND ad.month_date <= last_day(td.end_dt)
ORDER BY 1
,2
You have to generate date and from that have to pick year and month
select distinct year(date),month( date) from
(select * from (
select
date_add('2017-02-14 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date
from
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n1,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n2,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n3,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n4,
(select 0 as num
union all select 1
union all select 2
union all select 3
union all select 4
union all select 5
union all select 6
union all select 7
union all select 8
union all select 9) n5
) a
where date >'2017-02-14 00:00:00.000' and date < '2018-11-05'
) as t

Sql query for aggregation

Let consider the below table structure
Product Year Month Price
A 2011 01 23
A 2011 02 34
.......
.....
A 2011 12 54
B 2011 01 13
B 2011 02 12
.......
.....
B 2011 12 20
From this table i need to aggregate the value for every 3 months ie..,
Product Year Month Price
A 2011 1-3 45
A 2011 4-6 23
A 2011 7-9 45
A 2011 10-12 16
A 2012 1-3 12
.......
.......
Can anybody tell me how to do this calculation using sql query...
Thanks in Advance !!!
Try this. Use Case statement to group the month Quarter wise then find the sum of price
SELECT product,
[year],
CASE
WHEN ( [month] ) IN( '01', '02', '03' ) THEN '1-3'
WHEN ( [month] ) IN( '04', '05', '06' ) THEN '4-6'
WHEN ( [month] ) IN( '07', '08', '09' ) THEN '7-9'
WHEN ( [month] ) IN( '10', '11', '12' ) THEN '10-12'
END [Month],
Sum(Price)
FROM tablename
GROUP BY product,
[year],
CASE
WHEN ( [month] ) IN( '01', '02', '03' ) THEN '1-3'
WHEN ( [month] ) IN( '04', '05', '06' ) THEN '4-6'
WHEN ( [month] ) IN( '07', '08', '09' ) THEN '7-9'
WHEN ( [month] ) IN( '10', '11', '12' ) THEN '10-12'
END
Note : By looking at your month data it looks like its a varchar column but you can change it to TINYINT
Try using CASE in GROUP BY clause:
...
GROUR BY [Year], CASE WHEN Month in (1,2,3) THEN '1-3'
WHEN Month in (4,5,6) THEN '4-6'
...
END
Considering Month is stored as varchar(), you can write as:
Select Product,
Year,
[MonthRange] as [Month],
Sum(Price)
From (
select Product,
Year,
case when Month in ('01','02','03') then '[1-3]'
when Month in ('04','05','06') then '[4-6]'
when Month in ('07','08','09') then '[7-9]'
when Month in ('10','11','12') then '[10-12]'
end as [MonthRange],
Price
from #test) as T
Group by Product,Year,[MonthRange]
DEMO
select Product, [year], case when [month] between 1 and 3 then '01-03' else case when [month] between 4 and 6 then '04-06' else case when [month] between 7 and 9 then '07-09' else '10-12' end end end, sum([price])
from Table1
group by Product, [year], case when [month] between 1 and 3 then '01-03' else case when [month] between 4 and 6 then '04-06' else case when [month] between 7 and 9 then '07-09' else '10-12' end end end
Above sql query will give you the required results, if you don not use leading zeros which I used in month labels then you will not get result sorted by month.
Use this query.
CREATE table #test
(
product VARCHAR(10),
year int,
month int,
price int
)
INSERT INTO #test
SELECT 'A', 2011, 01, 23 UNION
SELECT 'A', 2011, 02, 65 UNION
SELECT 'A', 2011, 03, 45 UNION
SELECT 'B', 2011, 04, 34 UNION
SELECT 'B', 2011, 05, 67 UNION
SELECT 'B', 2011, 06, 34 UNION
SELECT 'B', 2011, 07, 87 UNION
SELECT 'B', 2011, 08, 2 UNION
SELECT 'B', 2011, 09, 345 UNION
SELECT 'B', 2011, 10, 9 UNION
SELECT 'B', 2011, 11, 293 UNION
SELECT 'B', 2011, 12, 23
SELECT product, [year], [MONTH], SUM(price)
FROM
(
SELECT product, [year], CASE WHEN [MONTH] IN (01,02,03) THEN '1-3'
WHEN [MONTH] IN (04,05,06) THEN '4-6'
WHEN [MONTH] IN (07,08,09) THEN '7-9'
WHEN [MONTH] IN (10, 11, 12) THEN '10-12'
END AS [Month],
price
FROm #Test
)AS A
group BY product, [year], [Month]

SQL to get an daily average from month total

I have a table that lists month totals (targets)
person total month
----------- --------------------- -----------
1001 114.00 201005
1001 120.00 201006
1001 120.00 201007
1001 120.00 201008
.
1002 114.00 201005
1002 222.00 201006
1002 333.00 201007
1002 111.00 201008
.
.
but month is an integer(!)
I also have another table that has a list of working days (calendar)
tran_date day_type
----------------------- ---------------------------------
1999-05-01 00:00:00.000 WEEKEND
1999-05-02 00:00:00.000 WEEKEND
1999-05-03 00:00:00.000 WORKING_DAY
1999-05-04 00:00:00.000 WORKING_DAY
1999-06-01 00:00:00.000 .....
.
.
.
What I want to do is get a list of dates with the average for that day based on the number of days in the month where day_type is 'WORKING_DAY' / the month's total.
so if I had say 20 working days in 201005 then I'd get an average of 114/20 on each working day, while the other days would be 0.
somthing like
person tran_date day_avg
------- ----------------------- ---------------------------------
1001 2010-05-01 00:00:00.000 0
1001 2010-05-02 00:00:00.000 0
1001 2010-05-03 00:00:00.000 114/2 (as there are two working days)
1001 2010-05-04 00:00:00.000 114/2 (as there are two working days)
.
.
.
It has to be done as a CTE as this is a limitation of the target system (I can only do one statement)
I can start off with (Dates to
WITH
Dates AS
(
SELECT CAST('19990501' as datetime) TRAN_DATE
UNION ALL
SELECT TRAN_DATE + 1
FROM Dates
WHERE TRAN_DATE + 1 <= CAST('20120430' as datetime)
),
Targets as
(
select CAST(cast(month as nvarchar) + '01' as dateTime) mon_start,
DATEADD(MONTH, 1, CAST(cast(month as nvarchar) + '01' as dateTime)) mon_end,
total
from targets
)
select ????
Sample data (may vary):
select * into #totals from (
select '1001' as person, 114.00 as total, 199905 as month union
select '1001', 120.00, 199906 union
select '1001', 120.00, 199907 union
select '1001', 120.00, 199908
) t
select * into #calendar from (
select cast('19990501' as datetime) as tran_date, 'WEEKEND' as day_type union
select '19990502', 'WEEKEND' union
select '19990503', 'WORKING_DAY' union
select '19990504', 'WORKING_DAY' union
select '19990505', 'WORKING_DAY' union
select '19990601', 'WEEKEND' union
select '19990602', 'WORKING_DAY' union
select '19990603', 'WORKING_DAY' union
select '19990604', 'WORKING_DAY' union
select '19990605', 'WORKING_DAY' union
select '19990606', 'WORKING_DAY' union
select '19990701', 'WORKING_DAY' union
select '19990702', 'WEEKEND' union
select '19990703', 'WEEKEND' union
select '19990704', 'WORKING_DAY' union
select '19990801', 'WORKING_DAY' union
select '19990802', 'WORKING_DAY' union
select '19990803', 'WEEKEND' union
select '19990804', 'WEEKEND' union
select '19990805', 'WORKING_DAY' union
select '19990901', 'WORKING_DAY'
) t
Select statement, it returns 0 if the day is 'weekend' or not exists in calendar table. Please keep in mind that MAXRECURSION is a value between 0 and 32,767.
;with dates as (
select cast('19990501' as datetime) as tran_date
union all
select dateadd(dd, 1, tran_date)
from dates where dateadd(dd, 1, tran_date) <= cast('20010101' as datetime)
)
select t.person , d.tran_date, (case when wd.tran_date is not null then t.total / w_days else 0 end) as day_avg
from dates d
left join #totals t on
datepart(yy, d.tran_date) * 100 + datepart(mm, d.tran_date) = t.month
left join (
select datepart(yy, tran_date) * 100 + datepart(mm, tran_date) as month, count(*) as w_days
from #calendar
where day_type = 'WORKING_DAY'
group by datepart(yy, tran_date) * 100 + datepart(mm, tran_date)
) c on t.month = c.month
left join #calendar wd on d.tran_date = wd.tran_date and wd.day_type = 'WORKING_DAY'
where t.person is not null
option(maxrecursion 20000)
You could calculate the number of working days per month in a subquery. Only the subquery would have to use group by. For example:
select t.person
, wd.tran_date
, t.total / m.WorkingDays as day_avg
from #Targets t
join #WorkingDays wd
on t.month = convert(varchar(6), wd.tran_date, 112)
left join
(
select convert(varchar(6), tran_date, 112) as Month
, sum(case when day_type = 'WORKING_DAY' then 1 end) as WorkingDays
from #WorkingDays
group by
convert(varchar(6), tran_date, 112)
) as m
on m.Month = t.month
Working example at SE Data.
For the "magic number" 112 in convert, see the MSDN page.
If I understood your question correctly, the following query should do it:
SELECT
*,
ISNULL(
(
SELECT total
FROM targets
WHERE
MONTH(tran_date) = month - ROUND(month, -2)
AND c1.day_type = 'WORKING_DAY'
) /
(
SELECT COUNT(*)
FROM calendar c2
WHERE
MONTH(c1.tran_date) = MONTH(c2.tran_date)
AND c2.day_type = 'WORKING_DAY'
),
0
) day_avg
FROM
calendar c1
In plain English:
For each row in calendar,
get the total of the corresponding month if this row is a working day (otherwise get NULL),
get the number of working days in the same month
and divide them.
Finally, convert the NULL (of non-working days) into 0.