0

I would like to split up the year into 13 periods with 4 weeks in each

52 weeks a year / 4 = 13 even periods

I would like each period to start on a saturday and end on a friday.

It should look like the below image

enter image description here

Obviously I could do this manually, but the dates would change each year and I am looking for a way to automate this with SQL rather than manually do this for each upcoming year

Is there a way to produce this yearly split automatically?

David Hayward
  • 189
  • 1
  • 1
  • 14

3 Answers3

1

In this previous answer I show an approach to create a numbers/date table. Such a table is very handsome in many places.

With this approach you might try something like this:

CREATE TABLE dbo.RunningNumbers(Number INT NOT NULL,CalendarDate DATE NOT NULL, CalendarYear INT NOT NULL,CalendarMonth INT NOT NULL,CalendarDay INT NOT NULL, CalendarWeek INT NOT NULL, CalendarYearDay INT NOT NULL, CalendarWeekDay INT NOT NULL);

DECLARE @CountEntries INT = 100000;
DECLARE @StartNumber INT = 0;


WITH E1(N) AS(SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)), --10 ^ 1
    E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b), -- 10 ^ 2 = 100 rows
    E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b), -- 10 ^ 4 = 10,000 rows
    E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b), -- 10 ^ 8 = 10,000,000 rows
    CteTally AS
    (
        SELECT TOP(ISNULL(@CountEntries,1000000)) ROW_NUMBER() OVER(ORDER BY(SELECT NULL)) -1 + ISNULL(@StartNumber,0) As Nmbr
        FROM E8
    )
INSERT INTO dbo.RunningNumbers
SELECT CteTally.Nmbr,CalendarDate.d,CalendarExt.*
FROM CteTally
CROSS APPLY
(
    SELECT DATEADD(DAY,CteTally.Nmbr,{ts'1900-01-01 00:00:00'})
) AS CalendarDate(d)
CROSS APPLY
(
    SELECT YEAR(CalendarDate.d) AS CalendarYear
          ,MONTH(CalendarDate.d) AS CalendarMonth
          ,DAY(CalendarDate.d) AS CalendarDay
          ,DATEPART(WEEK,CalendarDate.d) AS CalendarWeek
          ,DATEPART(DAYOFYEAR,CalendarDate.d) AS CalendarYearDay
          ,DATEPART(WEEKDAY,CalendarDate.d) AS CalendarWeekDay
) AS CalendarExt;
GO

NTILE - SQL Server 2008+ will create (almost) even chunks.

This the actual query

SELECT *,NTILE(13) OVER(ORDER BY CalendarDate) AS Periode
FROM RunningNumbers
WHERE CalendarWeekDay=6
AND CalendarDate>={d'2017-01-01'} AND CalendarDate <= {d'2017-12-31'};
GO
--Carefull with existing data!
--DROP TABLE dbo.RunningNumbers;

Hint 1: Place indexes!

Hint 2: Read the link about NTILE, especially the Remark-section.

I think this will fit for this case. You might think about using Prdp's approach with ROW_NUMBER() in conncetion with INT division. But - big advantage! - NTILE would allow PARTITION BY CalendarYear.

Hint 3: You might add a column to the table

...where you set the period's number as a fix value. This will make future queries very easy and would allow manual correction on special cases (53rd week..)

Community
  • 1
  • 1
Shnugo
  • 66,100
  • 9
  • 53
  • 114
1

Here is one way using Calendar table

DECLARE @start    DATE = '2017-04-01',
        @end_date DATE = '2017-12-31'

SET DATEFIRST 7;

WITH Calendar 
     AS (SELECT 1                      AS id,
                @start                 AS start_date,
                Dateadd(dd, 6, @start) AS end_date
         UNION ALL
         SELECT id + 1,
                Dateadd(week, 1, start_date),
                Dateadd(week, 1, end_date)
         FROM   Calendar 
         WHERE  end_date < @end_date)
SELECT id,
       ( Row_number()OVER(ORDER BY id) - 1 ) / 4 + 1 AS Period,
       start_date,
       end_date
FROM   Calendar 
OPTION (maxrecursion 0) 

I have generated dates using Recursive CTE but it is better to create a physical calendar table use it in queries like this

Pரதீப்
  • 91,748
  • 19
  • 131
  • 172
0

Firstly, you will never get 52 even weeks in a year, there are overlap weeks in most calendar standards. You will occasionally get a week 53.

You can tell SQL to use Saturday as the first day of the week with datefirst, then running a datepart on today's date with getdate() will tell you the week of the year:

  SET datefirst 6 -- 6 is Saturday
  SELECT datepart(ww,getdate()) as  currentWeek

You could then divide this by 4 with a CEILING command to get the 4-week split:

  SET datefirst 6
  SELECT DATEPART(ww,getdate()) as  currentWeek,
  CEILING(DATEPART(ww,getdate())/4) as four_week_split
TJB
  • 787
  • 1
  • 8
  • 29