I am trying to create a Calendar Table with following columns and requirements. I have this calendar table made previously and I am not able to update it to my current requirements.
Date
Week No
Week Name
Week Start Date
Week End Date
Month
Month Start Date
Month End Date
List item
Quarter
Quarter Start Date
Quarter End Date
Year
Requirements
--Week Starts from Sunday and ends at Saturday
--Week Name should be in format 31Aug--to--06Sep
--Month should be ending date of Week's Month
--Calendar should start from 2015-08-31
--Financial Year should be in format of FY16-17
--Year should start from 1-Jun Till 31 May
--Quarter should be of 3 months of above mentioned dates
--Week No should be incremental number should not get reset after year completion.
The Query
DECLARE #StartDate date = '20150831'
DECLARE #CutoffDate date = '20300101'
;WITH seq(n) AS
(
SELECT 0 UNION ALL SELECT n + 1 FROM seq
WHERE n < DATEDIFF(DAY, #StartDate, #CutoffDate)
),
d(d) AS
(
SELECT DATEADD(DAY, n, #StartDate) FROM seq
),
src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(
SELECT
TheDate = CONVERT(date, d),
TheDay = DATEPART(DAY, d),
TheDayName = DATENAME(WEEKDAY, d),
TheWeek = DATEPART(WEEK, d),
TheDayOfWeek = DATEPART(WEEKDAY, d),
TheMonth = DATEPART(MONTH, d),
TheMonthName = DATENAME(MONTH, d),
TheQuarter = Concat('Q',DATEPART(Quarter, d)),
Financial_Year = DATEPART(YEAR, d),
Financial_Quarter=Datepart(QUARTER,d),
TheYear = DATEPART(YEAR, d),
TheFirstOfMonth = DATEFROMPARTS(YEAR(d), MONTH(d), 1),
TheFirstOfFYear = DATEFROMPARTS(YEAR(d), 4, 1),
TheFirstOfYear = DATEFROMPARTS(YEAR(d), 1, 1),
TheLastOfYear = DATEFROMPARTS(YEAR(d), 12, 31),
TheDayOfYear = DATEPART(DAYOFYEAR, d)
FROM d
),
Dimension AS
(
SELECT
TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
-- TheDayOfWeekInMonth = CONVERT(tinyint, ROW_NUMBER() OVER
-- (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)),
TheDayOfYear,
TheWeek,
TheFirstOfWeek = DATEADD(DAY, 1 - TheDayOfWeek, TheDate),
TheLastOfWeek = DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)),
TheMonth,
TheMonthName,
TheFirstOfMonth,
TheLastOfMonth = MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth),
TheFirstOfNextMonth = DATEADD(MONTH, 1, TheFirstOfMonth),
TheLastOfNextMonth = DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)),
TheQuarter,
TheFirstOfQuarter = MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheLastOfQuarter = MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter),
TheYear,
TheFirstOfYear = DATEFROMPARTS(TheYear, 1, 1),
TheFirstOfFYear = DATEFROMPARTS(TheYear, 4, 1),
TheLastOfYear,
MMYYYY = CONVERT(char(2), CONVERT(char(8), TheDate, 101))
+ CONVERT(char(4), TheYear),
Financial_Quarter = Datepart(Quarter,DATEADD(MONTH, -3, TheFirstOfMonth)), /*Starting Financial Quarter from April*/
Financial_Year =CASE
WHEN Financial_Quarter = 1 THEN DATEPART(Year,Dateadd(Year,-1,TheFirstofYear)) ELSE THEYEAR END
FROM src
)
SELECT * FROM Dimension
ORDER BY TheDate
OPTION (MAXRECURSION 0);
How to convert Months depending on the weeks for example a month starts on Sunday (week also starts from Sunday) date be 01-MM-YYYY and the month should always end on Saturday giving 4 weeks normally in a month. The month cannot start or end with dates of previous week or month it should always have only the whole weeks, starting from Sunday and ending on Saturday.
As I mention in the comments, seems you just need a windowed COUNT. This is a guess, based on a lack of expected results, but this should get you on the right path. I also use the same set based method I used for your colleague's question:
DECLARE #StartDate date = '20150831'
DECLARE #CutoffDate date = '20300101';
/*
; is a terminator, not a "beginingator". It goes at the end of ALL your statements,
not at the start of statements that require the PREVIOUS statement to be properly terminated.
*/
WITH N AS
(SELECT N
FROM (VALUES (NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL)) N(N)),
Tally AS
(SELECT 0 AS I
UNION ALL
SELECT TOP (DATEDIFF(DAY, #StartDate, #CutoffDate))
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1,N N2,N N3, N N4), --Up to 1000 rows. Add more cross joins for more rows
D AS
(SELECT DATEADD(DAY, T.I, #StartDate) AS d
FROM Tally T),
Src AS /*SOURCE TABLE WITH OBJECT DEFINITION*/
(SELECT CONVERT(date, d) AS TheDate,
DATEPART(DAY, d) AS TheDay,
DATENAME(WEEKDAY, d) AS TheDayName,
DATEPART(WEEK, d) AS TheWeek,
DATEPART(WEEKDAY, d) AS TheDayOfWeek,
DATEPART(MONTH, d) AS TheMonth,
DATENAME(MONTH, d) AS TheMonthName,
CONCAT('Q', DATEPART(QUARTER, d)) AS TheQuarter,
DATEPART(YEAR, d) AS Financial_Year,
DATEPART(QUARTER, d) AS Financial_Quarter,
DATEPART(YEAR, d) AS TheYear,
DATEFROMPARTS(YEAR(d), MONTH(d), 1) AS TheFirstOfMonth,
DATEFROMPARTS(YEAR(d), 4, 1) AS TheFirstOfFYear,
DATEFROMPARTS(YEAR(d), 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(YEAR(d), 12, 31) AS TheLastOfYear,
DATEPART(DAYOFYEAR, d) AS TheDayOfYear
FROM d),
Dimension AS
(SELECT TheDate,
TheDay,
TheDayName,
TheDayOfWeek,
CONVERT(tinyint, ROW_NUMBER() OVER (PARTITION BY TheFirstOfMonth, TheDayOfWeek ORDER BY TheDate)) AS TheDayOfWeekInMonth,
TheDayOfYear,
TheWeek,
DATEADD(DAY, 1 - TheDayOfWeek, TheDate) AS TheFirstOfWeek,
DATEADD(DAY, 6, DATEADD(DAY, 1 - TheDayOfWeek, TheDate)) AS TheLastOfWeek,
CONVERT(tinyint, DENSE_RANK() OVER (PARTITION BY TheYear, TheMonth ORDER BY TheWeek)) AS TheWeekOfMonth,
TheMonth,
TheMonthName,
TheFirstOfMonth,
MAX(TheDate) OVER (PARTITION BY TheYear, TheMonth) AS TheLastOfMonth,
DATEADD(MONTH, 1, TheFirstOfMonth) AS TheFirstOfNextMonth,
DATEADD(DAY, -1, DATEADD(MONTH, 2, TheFirstOfMonth)) AS TheLastOfNextMonth,
TheQuarter,
MIN(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheFirstOfQuarter,
MAX(TheDate) OVER (PARTITION BY TheYear, TheQuarter) AS TheLastOfQuarter,
TheYear,
DATEFROMPARTS(TheYear, 1, 1) AS TheFirstOfYear,
DATEFROMPARTS(TheYear, 4, 1) AS TheFirstOfFYear,
TheLastOfYear,
CONVERT(char(2), CONVERT(char(8), TheDate, 101)) + CONVERT(char(4), TheYear) AS MMYYYY,
DATEPART(QUARTER, DATEADD(MONTH, -3, TheFirstOfMonth)) AS Financial_Quarter, /*Starting Financial Quarter from April*/
CASE
WHEN Financial_Quarter = 1 THEN DATEPART(YEAR, DATEADD(YEAR, -1, TheFirstOfYear))
ELSE TheYear
END AS Financial_Year,
COUNT(CASE WHEN DATENAME(WEEKDAY,TheDate) = 'Sunday' THEN 1 END) OVER (PARTITION BY YEAR(TheDate), MONTH(TheMonth) ORDER BY TheDate) AS TheWeekNo
FROM src)
SELECT *
FROM Dimension
ORDER BY TheDate;
Here is a single SELECT query that uses the WITH RECURSIVE statement. It does not require loops procedures etc.
A RECURSIVE SQL common table expression (CTE) is a query that continuously references a previous result until it returns an empty result. It's achieved using a CTE, which in SQL is known as a “WITH” statement.
In the query below, the second date_range with statement has a UNION ALL. The first query in that statement is the first record of the table, and the subsequent records are all from the UNION ALL second statement where it continuously references itself until it reaches the end_date, as expresses as "WHERE next_date <= end_date"
WITH RECURSIVE min_max_dates AS (
SELECT
MIN(start_at) start_date,
MAX(end_at) end_date
FROM table_or_hard_code ay
),
date_range AS (
select
start_date date,
DATE_TRUNC('month', start_date) month_date,
MONTH(start_date) as month_num,
YEAR(start_date) as month_year,
TO_CHAR((start_date),'MMMM') as month_display,
MONTHNAME (start_date) as month_short_display,
CONCAT(TO_CHAR((start_date),'MMMM'), ', ', YEAR(start_date)) as month_year_display,
DATEADD(DAY, 1, start_date) as next_date,
start_date,
end_date
FROM min_max_dates
UNION ALL
SELECT
dr.next_date date,
DATE_TRUNC('month', dr.next_date) month_date,
MONTH(dr.next_date) as month_num,
YEAR(dr.next_date) as month_year,
TO_CHAR((dr.next_date),'MMMM') as month_display,
MONTHNAME (dr.next_date) as month_short_display,
CONCAT(TO_CHAR((dr.next_date),'MMMM'), ', ', YEAR(dr.next_date)) as month_year_display,
DATEADD(DAY, 1, dr.next_date) as next_date,
start_date,
end_date
FROM date_range dr
WHERE next_date <= end_date
)
SELECT date, month_date, month_year, month_num, month_display, month_short_display, month_year_display
FROM date_range
Related
I have a column 'working days' which give info is it working day or no (1 for woking day and 0 for weekend). And the task is to find first 3 working days in each month.
I try to use this code:
SELECT working_day, *
FROM table
WHERE tdate BETWEEN DATEADD(mm, DATEDIFF(mm, 0, GETDATE()), 0) AND DATEADD(dd, -1, DATEADD(mm, DATEDIFF(mm, -1, GETDATE()), 0))
AND working_day = 1
AND tdate = CAST(GETDATE() AS DATE);
Try using the row_number function as the following:
WITH CTE AS
(
SELECT *,
ROW_NUMBER() OVER (PARTITION BY YEAR(tdate), MONTH(tdate) ORDER BY tdate) RN
FROM tbl_name
WHERE working_day = 1
)
SELECT tdate, working_day
FROM CTE
WHERE RN <= 3
Demo
I have following code in which it presently gives month wise Total Sales for current year, I need to get total sales from last month of previous year to current month of this year.
My query is as follows:
;WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE DATEPART(MONTH,MONTH_NAME) < 12),octe AS(
SELECT (DATENAME (MONTH, DATEADD ( MONTH, DATEPART(MONTH, OI.CreatedDate), -1) )) AS MONTH_NAME,
SUM (OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM Order_Item OI
GROUP BY MONTH(OI.CreatedDate))
SELECT DATENAME(MONTH,m.MONTH_NAME) + '' + DATENAME(YEAR,m.MONTH_NAME) as
MONTH_NAME, o.TOTAL_SALES FROM mcte m LEFT JOIN octe o ON o.MONTH_NAME = DATENAME(MONTH,m.MONTH_NAME)
and I am getting records
MONTH_NAME TOTAL_SALES
July2019 54023.45
August2019 NULL
December2019 NULL
September2019 NULL
October2019 NULL
November2019 NULL
Here I am only getting data for previous year only, not getting data for current year.Can anyone please guide me on this.
Thank you
You are only generating months up to 12. Try replacing the first CTE with:
WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE month_name < GETDATE()
),
Note the difference is the WHERE clause.
The entire query should look like this:
WITH months AS (
SELECT DATEFROMPARTS(YEAR(getdate()) - 1, MONTH(getdate()), 1) as month
UNION ALL
SELECT DATEADD(MONTH, 1, month)
FROM months
WHERE EOMONTH(month) < GETDATE()
)
SELECT m.month, SUM(OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM months m LEFT JOIN
Order_Item OI oi
ON oi.CreatedDate >= m.month AND
oi.CreatedDate < DATEAADD(month, 1, m.month)
GROUP BY m.month
Try doing this:
DECLARE #CurDate DATE = GET_DATE()
DECLARE #OneYearPrior DATE = DATEADD(YEAR, -1, #CurDate)
WITH relevant_months(start_date, month_of_sale, year_of_sale) AS (
SELECT
#CurDate AS start_date,
MONTH(#CurDate) as month_of_sale,
YEAR(#CurDate) as year_of_sale
UNION ALL
SELECT DATEADD(MONTH, -1, start_date) AS start_date,
MONTH(DATEADD(MONTH, -1, start_date)) as month_of_sale,
YEAR(DATEADD(MONTH, -1, start_date)) AS year_of_sale
FROM relevant_months
WHERE DATEADD(MONTH, -1, start_date) >= #OneYearPrior
),
relevant_data AS (
SELECT OI.CreatedDate,
OI.ItemQty,
OI.TotalPrice,
MONTH(OI.CreatedDate), AS month_of_sale,
YEAR(OI.CreatedDate) AS year_of_sale
FROM Order_Item OI
WHERE OI.CreatedDate >= DATEADD(YEAR, -1, GETDATE())
)
SELECT rm.month_of_sale as month, rm.year_of_sale as year,
SUM(rd.ItemQty*rd.TotalPrice) as total_sales
FROM relevant_months rm
LEFT JOIN relevant_data rd
ON rm.month_of_sale = rd.month_of_sale
AND rm.year_of_sale = rd.year_of_sale
GROUP BY rm.month_of_sale, rm.year_of_sale
ORDER BY rm.year_of_sale asc, rm.month_of_sale asc
I can get the top 10 percent for the previous month using
select top 10 percent ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt from Tbl
where DATEPART(m, Entered_Date) = DATEPART(m, DATEADD(m, -1, getdate())) AND DATEPART(yyyy, Entered_Date) = DATEPART(yyyy, DATEADD(m, -1, getdate())) and
CreatedBy ='User1')
order by amt DESC
I am having to do this many times for each user using a union, how can I do this on one query? When I add the other users CreatedBy in('User1','User2') it doesn't work. I had a look at row over partition but cant figure it out. I'm using SSMS 2017.
Updated with below
Select * From(
select ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt
NTILE (10) OVER ( PARTITION BY CREATEDBY ORDER BY Amt desc) AS PercentageNo
from Tbl
where DATEPART(m, Entered_Date) = DATEPART(m, DATEADD(m, -1, getdate())) AND DATEPART(yyyy, Entered_Date) = DATEPART(yyyy, DATEADD(m, -1, getdate()))
/*Entered_Date between DATEADD(m, -2, getdate()) and DATEADD(m, -1, getdate()) */ )as SubQuery
where PercentageNo=1 order By Amt
you can use GROUP BY for select percentage of each user as this:
select top 10 percent ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt
FROM Tbl
where DATEPART(m, Entered_Date) = DATEPART(m, DATEADD(m, -1, getdate()))
AND DATEPART(yyyy, Entered_Date) = DATEPART(yyyy, DATEADD(m, -1, getdate()))
AND CreatedBy IN ('User1','User2')
GROUP BY ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt
order by amt DESC
you can use row_number() for this reason
If you want the top 10% for each user, I would recommend using logic such as this:
select ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt
from (select t.*,
row_number() over (partition by user order by amt desc) as seqnum,
count(*) over (partition by user) as cnt
from Tbl t
where Entered_Date >= dateadd(month, -1, dateadd(day, 1 - day(getdate()), convert(date, getdate()))) and
Entered_Date < dateadd(day, 1 - day(getdate()), convert(date, getdate()))
) t
where seqnum <= cnt / 10;
order by user, amt desc;
Note the change to the date arithmetic. The functions are all applied only on getdate(). This allows indexes to be used to winnow down the data.
The key for solving your problem is using window functions with the partition by user construct to do separate counts and enumerations for each user.
This I now woking as expected thanks to Gordon Linoff Re Dates and Caius Jard for pointing me in to Ntile
Select * From(
select ID, Ref, Entered_Date, [Type], CREATEDBY, Office, Created_Date, Amt
NTILE (10) OVER ( PARTITION BY CREATEDBY ORDER BY Amt desc) AS PercentageNo
from Tbl
where Entered_Date >= dateadd(month, -1, dateadd(day, 1 - day(getdate()), convert(date, getdate()))) and Entered_Date < dateadd(day, 1 - day(getdate()), convert(date, getdate())) as SubQuery where PercentageNo=1 order By Amt
I have a query like this:
select date, count(*)
from inflow
where date >= dateadd(year, -2, getdate())
group by date
order by date
I need to exclude Saturday and Sunday dates, and instead add their counts into the following Monday. What would be the best way to do this? Do I need to exclude Saturday, Sunday, and Mondays, then add them on with a join to a different query? The query above is a simplified version, this is a relatively big query, so I need to keep efficiency in mind.
Well, this is a somewhat brute-force approach:
select date,
(case when datename(weekday, date) = 'Monday')
then cnt + cnt1 + cnt2
else cnt
end) as cnt
from (select date, count(*) as cnt,
lag(count(*), 1, 0) over (order by date) as prev_cnt,
lag(count(*), 2, 0) over (order by date) as prev_cnt2
from inflow
where date >= dateadd(year, -2, getdate())
group by date
) d
where datename(weekday, date) not in ('Saturday', 'Sunday')
order by date;
Note: This is assuming English-language settings so the datename() logic works.
An alternative method without subqueries;
select v.dte, count(*) as cnt
from inflow i cross apply
(values (case when datename(weekday, i.date) = 'Saturday'
then dateadd(day, 2, i.date)
when datename(weekday, i.date) = 'Sunday'
then dateadd(day, 1, 9.date)
else i.date
end)
) v.dte
where i.date >= dateadd(year, -2, getdate())
group by v.dte
order by date;
You state for performance, however without knowing the full picture it's quite hard to understand how to optimise the query.
While I've been working on this, I noticed Gordon Linoff's answer, however I'll continue to write my version up as well, we both following the same path, but get to the answer a little different.
WITH DateData (date, datetoapply)
AS
(
SELECT
[date],
CASE DATEPART(w, [date])
WHEN 5 THEN DATEADD(d, 2, [date])
WHEN 6 THEN DATEADD(d, 1, [date])
ELSE date
END as 'datetoapply'
FROM inflow
WHERE [date] >= dateadd(year, -2, getdate())
)
SELECT datetoapply, count(*)
FROM DateData
GROUP BY datetoapply
ORDER BY datetoapply
While I could not get Gordon's query working as expected, I can confirm that "DATEPART(w, [date])" performs much better than "DATENAME(weekday, [date])", which if replaced in the query above increases the server processing time from 87ms to 181ms based on a table populated with 10k rows in Azure.
I have two tables "Fiscal_calendar" and "Sales". The 2 tables do not link together. I want to be able to write a query that calculates the total sales made a in a week according to the fiscal calendar. Is this possible? Our fiscal calendar starts from December 1st and every month end falls on a friday.
Any help would be appreciated.
Fiscal_calendar
Period Period1_StartDate Period1_EndDate Period2_StartDate Period2_EndDate...........
2018 01/12/2017 29/12/2017 30/01/2018 26/01/2018
Sales
Sales_order_no Amount Date Customer
111 20453 03/12/2017 abc
112 23154 04/12/2017 bbb
113 20201 10/12/2017 ddd
114 39012 11/12/2017 ccc
115 11111 18/12/2017 eee
116 22222 25/12/2017 uuu
So there are 4 weeks between Period 1 startdate and enddate. And the first 2 sales fall under week 1. So total sales for Week 1 would be 43607
OUTPUT
WEEK Total_Sales
W1 43607
W2 59213
W3 11111
W4 22222
Two uses of CROSS APPLY to fix (normalise) the fiscal calendar table in to something useful, then a simple GROUP BY.
The WHERE clause picks the row from the fiscal calendar, that the period or week (or whatever) from the derived views.
The LEFT JOIN is in case of no sales in that week.
The ON clause looks at the fiscal calendar AND the week derived view, just in case the derived view describes a week that's not really in that year.
SELECT
p.year_id,
p.period_id,
w.week_id,
SUM(s.amount) AS total_amount
FROM
fiscal_calendar c
CROSS APPLY
(
SELECT period, 1, period1_startDate, period1_endDate
UNION ALL SELECT period, 2, period2_startDate, period2_endDate
..
UNION ALL SELECT period, 13, period13_startDate, period13_endDate
)
AS p(year_id, period_id, startDate, endDate)
CROSS APPLY
(
SELECT 1, DATEADD(d, 0, startDate), DATEADD(d, 6, startDate)
UNION ALL SELECT 2, DATEADD(d, 7, startDate), DATEADD(d, 13, startDate)
UNION ALL SELECT 3, DATEADD(d, 14, startDate), DATEADD(d, 20, startDate)
UNION ALL SELECT 4, DATEADD(d, 21, startDate), DATEADD(d, 27, startDate)
)
AS w(week_id, startDate, endDate)
LEFT JOIN
sales s
ON s.date BETWEEN c.period1_startdate AND c.period13_enddate
AND s.date BETWEEN w.startDate AND w.endDate
WHERE
p.year_id = 2018
AND p.period_id = 1
GROUP BY
p.year_id,
p.period_id,
w.week_id
UNPIVOT is a useful TSQL operator to use when normalizing data. Basically it takes a row and makes new rows - one for each column specified in a list of columns.
In the case below I unpivot on the PeriodX_StartDate columns. I originally did two unpivots, the other on PeriodX_EndDate. The end date was unnecessary, but in case you're wondering how I joined the two unpivots: I used a cross apply to generate a key for each week and had a predicate that tested if they were equal. Without the predicate you get a Cartesian product.
To generate the weeks I used a CROSS APPLY that is very similar to the one in #MatBailie's answer, except I avoided the unions by generating a set of arguments for each week that are passed into functions that generate the dates.
WITH normalizedCal AS (
SELECT [Period], MonthStart, WeekKey, StartDate, CutoffDate
FROM fiscal_calendar cal
UNPIVOT (
MonthStart FOR StartKey IN (Period1_StartDate, Period2_StartDate, ..., PeriodN_StartDate)
) AS startInfo
CROSS APPLY (
SELECT 'WK' + CAST((StartIndex / 7) + 1 AS char(1)) [WeekKey]
, DATEADD(day, startIndex, MonthStart) [StartDate]
, DATEADD(day, EndIndex, MonthStart) [CutoffDate]
FROM (
VALUES ( 0, 7 ), ( 7, 14 ), ( 14, 21 ), ( 21, 28 )
) rangeValues ( StartIndex, EndIndex )
) weekInfo
)
SELECT [Period], MonthStart, WeekKey, COALESCE(SUM(AMOUNT), 0) [Total_Sales]
FROM normalizedCal nc
LEFT JOIN sales ON sales.Date >= nc.StartDate AND sales.Date < nc.CutoffDate
GROUP BY [Period], MonthStart, WeekKey
ORDER BY [Period], MonthStart, WeekKey
Working Example with sample data:
WITH normalizedCal AS (
SELECT [Period], MonthStart, WeekKey, StartDate, CutoffDate
FROM (
VALUES (2018, '2017-12-01', '2017-12-29', '2017-12-30', '2018-01-26')
) cal ([Period], Period1_StartDate, Period1_EndDate, Period2_StartDate, Period2_EndDate)
UNPIVOT (
MonthStart FOR StartKey IN (Period1_StartDate, Period2_StartDate)
) AS startInfo
CROSS APPLY (
SELECT 'WK' + CAST((StartIndex / 7) + 1 AS char(1)) [WeekKey]
, DATEADD(day, startIndex, MonthStart) [StartDate]
, DATEADD(day, EndIndex, MonthStart) [CutoffDate]
FROM (
VALUES ( 0, 7 ), ( 7, 14 ), ( 14, 21 ), ( 21, 28 )
) rangeValues ( StartIndex, EndIndex )
) weekInfo
),
sales AS (
SELECT [Sales_order_no],[Amount], CAST([Date] as DATE) [Date],[Customer]
FROM (VALUES
(111, 20453, '2017-12-03', N'abc'),
(112, 23154, '2017-12-04', N'bbb'),
(113, 20201, '2017-12-10', N'ddd'),
(114, 39012, '2017-12-11', N'ccc'),
(115, 11111, '2017-12-18', N'eee'),
(116, 22222, '2017-12-25', N'uuu')
) [salessrc] ( [Sales_order_no],[Amount],[Date],[Customer])
)
SELECT [Period], MonthStart, WeekKey, COALESCE(SUM(AMOUNT), 0) [Total_Sales]
FROM normalizedCal nc
LEFT JOIN sales ON sales.Date >= nc.StartDate AND sales.Date < nc.CutoffDate
GROUP BY [Period], MonthStart, WeekKey
ORDER BY [Period], MonthStart, WeekKey
according to your provided example, You could do something like this :
SELECT *
FROM (
SELECT
[YEAR] = YEAR([Date]),
[MONTH] = MONTH([Date]),
[WEEK] = DATEPART(week, [Date]) - DATEPART(week, DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0)),
SUM(Amount) OVER(PARTITION BY YEAR([Date]), DATEPART(week, [Date]) - DATEPART(week, DATEADD(MONTH, DATEDIFF(MONTH, 0, [Date]), 0))+ 1) AS TotalSales
FROM Sales
)D
GROUP BY
[YEAR],
[MONTH],
[WEEK],
TotalSales
this will give you the same results that you've provided in your example, however, since we don't know how your Fiscal_calendar is configured you will need to see is it a mandatory to link it with it or not.