一、问题
按每年的1月1日算当年的第一个自然周
(遇到跨年也不管,如果1月1日是周三,那么到1月5号(周日)算是本年的第一个自然周, 如果按周一是一周的第一天)
计算是本年的第几周,那么 spark sql 如何写 ?
二、分析
难点 :
- Spark SQL 的 DAYOFWEEK 函数返回的每周第一天是周日。
- 边界值的处理,即第一周如何判定、第二周从哪天开始计算。
先给出 sql 关键逻辑
CASE
WHEN DAYOFYEAR(your_date_column) <= 7 AND DAYOFWEEK(your_date_column) <> 2 THEN 1
ELSE CEIL((DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) + 8) / 7.0)
END AS week_number
多找一些边界值测试一下。
DAYOFWEEK(your_date_column)分别返回
周日 周一 周二 周三 周四 周五 周六
1 2 3 4 5 6 7
当1月1日是周一时,DAYOFWEEK(your_date_column) 返回 2(Spark SQL 中周一是2)。
当1月1日是周日时,DAYOFWEEK(your_date_column) 返回 1。
但是 1月1日都是当年的第一天,即 1月1日在 DAYOFYEAR(your_date_column) 永远都返回 1。
当1月1日是周日时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 1 = 0 第二周第一天 1月2日 = 2 - 1 = 1
当1月1日是周一时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 2 = -1 第二周第一天 1月8日 = 8 - 1 = 7
当1月1日是周二时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 3 = -2 第二周第一天 1月7日 = 7 - 1 = 6
当1月1日是周三时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 4 = -3 第二周第一天 1月6日 = 6 - 1 = 5
当1月1日是周四时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 5 = -4 第二周第一天 1月5日 = 5 - 1 = 4
当1月1日是周五时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 6 = -5 第二周第一天 1月4日 = 4 - 1 = 3
当1月1日是周六时,DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) = 1 - 7 = -6 第二周第一天 1月3日 = 3 - 1 = 2
除了第一行,每一行都相差 8
8 可以拆解为 7 + 1,7 代表第二周, 1 代表第二周的第一天定义,偏移了 1。
举个例子,如果 your_date_column 是 2024-01-08(这是一个周二),DAYOFYEAR 为 8,DAYOFWEEK 为 3(周二),
那么计算将是 CEIL((8 - 3 + 8) / 7.0),即 CEIL(13 / 7.0),结果为 2,意味着这是第二周。
还有一种思考方式,去理解 7 + 1 中的 1 ,
DAYOFWEEK(your_date_column) 返回 1~7 , 如果除以 7 的话,为了确保不进位 1,那么需要把 1~7 向左偏移一位。 1~7 - 1 = 0~6
DAYOFYEAR(your_date_column) - ( DAYOFWEEK(your_date_column) - 1 ) / 7 都是在同一周
2023-01-01 年是周日,
那么 DAYOFWEEK(your_date_column) 返回的是 1,即本周第一天。
WEEKOFYEAR(your_date_column) 返回的是 52, 即 2022 年最后一周。
但实际上我们要求的结果应该是 2023 年的第一周。
2023-01-02 年是周一,
那么 DAYOFWEEK(your_date_column) 返回的是 2,即本周第二天。
WEEKOFYEAR(your_date_column) 返回的是 1, 即 2023 年第一周。
但实际上我们要求的结果应该是 2023 年的第二周。
三、验证
drop table your_table;
CREATE TABLE your_table (
id INT,
your_date_column DATE
);
CREATE OR REPLACE TEMPORARY VIEW temp_view AS
SELECT 1 as id, to_date('2023-01-01', 'yyyy-MM-dd') as your_date_column
UNION ALL
SELECT 2, to_date('2023-01-02', 'yyyy-MM-dd')
UNION ALL
SELECT 3, to_date('2023-02-15', 'yyyy-MM-dd')
UNION ALL
SELECT 4, to_date('2023-12-31', 'yyyy-MM-dd')
UNION ALL
SELECT 5, to_date('2024-01-01', 'yyyy-MM-dd')
UNION ALL
SELECT 6, to_date('2024-01-02', 'yyyy-MM-dd')
UNION ALL
SELECT 5, to_date('2024-01-07', 'yyyy-MM-dd')
UNION ALL
SELECT 6, to_date('2024-01-08', 'yyyy-MM-dd');
INSERT INTO your_table
SELECT * FROM temp_view;
SELECT
your_date_column,
YEAR(your_date_column) AS year,
date_format(your_date_column, 'EEEE') as WEEK,
WEEKOFYEAR(your_date_column) as WEEK_OF_YEAR,
DAYOFYEAR(your_date_column) as DAYOFYEAR,
DAYOFWEEK(your_date_column) as DAYOFWEEK,
CEIL((DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) + 8) / 7.0) as CEIL,
CASE
WHEN DAYOFYEAR(your_date_column) <= 7 AND DAYOFWEEK(your_date_column) <> 2 THEN 1
ELSE CEIL((DAYOFYEAR(your_date_column) - DAYOFWEEK(your_date_column) + 8) / 7.0)
END AS week_number
from your_table;
your_date_column year WEEK WEEK_OF_YEAR DAYOFYEAR DAYOFWEEK CEIL WEEK_NUMBER
2023-01-01 2023 Sunday 52 1 1 2 1
2023-01-02 2023 Monday 1 2 2 2 2
2023-02-15 2023 Wednesday 7 46 4 8 8
2023-12-31 2023 Sunday 52 365 1 54 54
2024-01-01 2024 Monday 1 1 2 1 1
2024-01-02 2024 Tuesday 1 2 3 1 1
2024-01-07 2024 Sunday 1 7 1 2 1
2024-01-08 2024 Monday 2 8 2 2 2
在这个查询中:
date_format 函数的第二个参数 'EEEE' 指定返回完整的星期名称(如 Monday, Tuesday 等)。
DAYOFYEAR(your_date_column) 计算出年中的天数。
DAYOFWEEK(your_date_column) 返回一周中的某天(以周日为一周的第一天)。
当 DAYOFYEAR 小于或等于7且 DAYOFWEEK 不等于2(不是周一)时,日期属于第一周。
否则,使用调整后的公式计算周数:减去 DAYOFWEEK 的结果,加上8,然后除以7,并向上取整。