Snowflake will allow you to set the first day of the week with a parameter.
https://docs.snowflake.com/en/sql-reference/parameters.html#label-week-start
This will allow you to set the first day of the week at Saturday.
Doing so will result in the WEEK()
function counting weeks in a year using saturday as a delimiter between weeks.
Now we just need to find which actual month has the most days for any given week and assign that week to the proper month.
I have an example script below that serves as an example on how to make a custom date dimension table. You can generate the table once and join against it to retrieve your custom date attributes.
/***************************************************************************
A WEEK_START session variable of 0 is the default Snowflake behavior
and has weeks start on Monday and end of Sunday (ISO standard).
https://docs.snowflake.com/en/sql-reference/parameters.html#label-week-start
-- 6 = Saturday is day 1 of of the week
*********************************************************************************************/
alter session set week_start = 6;
/*********************************************************************************************
The parameters below define the temporal boundaries of the calendar table. The values must be
DATE type and can be hardcoded, the result of a query, or a combination of both.
For example, you could set date_start and date_end based on the MIN and MAX date of the table
with the finest date granularity in your data.
*********************************************************************************************/
SET date_start = TO_DATE('2022-12-18');
SET date_end = current_date(); --TIP: for the current date use current_date();
--This sets the num_days parameter to the number of days between start and end
--this value is used for the generator
set num_days = (select datediff(day, $date_start, $date_end+1));
--CTE to hold generated date range
create or replace transient table calendar as
with gen_cte as (
select
dateadd(day,'-' || row_number() over (order by null),
dateadd(day, '+1', $date_end)
) as date_key
from table (generator(rowcount => ($num_days)))
order by 1)
-- calendar table expressions
, step_1 as (
select
date_key,
, dayofmonth(date_key) as day_of_month
, week(date_key) as week_num --*see comments
--, dayofweekiso(date_key) as day_of_week_iso,
, dayofweek(date_key) as day_of_week
, dayname(date_key) as day_name
, month(date_key) as month_num
--, weekiso(date_key) as week_iso_num, --*see comments
, year(date_key) as year_
, year_ || '-' ||week_num::string as year_week_key
, count(date_key) over (partition by year_week_key, month_num) as days_of_week_in_month
--ceil(dayofmonth(date_key) / 7) as day_instance_in_month --used to identify 'floating' events such as "fourth thursday of november"
FROM gen_cte)
-- calculate the max number of days in each month for any week in year
, step_2 as (
select
year_week_key
, month_num
, max(step_1.days_of_week_in_month) as max_days_of_week_in_month
from step_1
group by year_week_key, month_num)
-- for any week with 2 actual month values, assign the month with the most number of days
, step_3 as (
select
year_week_key
, month_num
, row_number() over (partition by year_week_key order by max_days_of_week_in_month desc ) as month_rank
from step_2
qualify month_rank = 1
)
select
s1.date_key
, s1.day_of_month
, s1.week_num
, s1.day_of_week
, s1.day_name
, s3.month_num as assigned_month_num
, s1.month_num as actual_month_num
, s1.year_
from step_1 s1
left join step_3 s3
on s1.year_week_key = s3.year_week_key
;
-- select from your new date dimension table
select * from calendar;
