【SQL周周练】一千条数据需要做一天,怎么用 SQL 处理电表数据(如何动态构造自然月)

部署运行你感兴趣的模型镜像

大家好,我是“蒋点数分”,多年以来一直从事数据分析工作。从今天开始,与大家持续分享关于数据分析的学习内容。

本文是第 6 篇,也是【SQL 周周练】系列的第 5 篇。该系列是挑选或自创具有一些难度的 SQL 题目,一周至少更新一篇。后续创作的内容,初步规划的方向包括:

后续内容规划#

1.利用 Streamlit 实现 Hive 元数据展示SQL 编辑器、 结合Docker 沙箱实现数据分析 Agent
2.时间序列异常识别、异动归因算法
3.留存率拟合、预测、建模
4.学习 AB 实验、复杂实验设计等
5.自动化机器学习、自动化特征工程
6.因果推断学习
7. ……

欢迎关注,一起学习。

第 5 期题目#

题目来源:改编题目,在某 Excel 论坛看到的;求助希望使用 VBA 处理(求助者自称手工处理,上千条数据就需要做一天)。我参照他的数据格式改编为 SQL 题目(我的数据完全由自己模拟生成,并没有使用求助者的数据)

一、题目介绍#

有一张记录了电表缴费数据的表,缴费的时间间隔是不固定的。领导希望将这张表重新拆解按照月份来汇总,分析每月的日均使用电量等等。求助者的原始数据还有站点编码、电表号、供电类型等等,我这里将问题简化,省略这些信息,就假设只有一个电表。

注:只有缴费区间的总电量,没有每日电量;利用平均值拆分到每天再根据不同区间汇总到自然月。针对最开始的日期和结束日期,如果不满整月,就按照实际存在的日期区间来计算(时间维度按日来处理,不考虑小时等更精细的级别 | 求助者给的数据也是精确到日期而不是小时)

列名数据类型注释
period_startstring缴费区间开始日期
period_endstring缴费区间结束日期
electricity_usageint这段区间使用的电量
daily_electricity_usageint这段区间的日平均电量
(本应该设置为浮点类型,但是我用整数生成的)
这字段本来也没必要存在,只不过省得在 SQL 再算一遍
periodint这段日期有几天(没啥用,我生成数据后二次校验用的)

部分样例数据(完整生成逻辑参见第三节)

period_startperiod_endelectricity_usagedaily_electricity_usageperiod
2023-12-012023-12-301470049030
2023-12-312024-01-06420607
2024-01-072024-01-072302301
2024-01-082024-02-041316047028
...............
2025-02-112025-02-24336024014
2025-02-252025-03-10602043014

二、题目思路#

想要答题的同学,可以先思考答案🤔。
.……

.……

.……

我来谈谈我的思路,这道题目要解决两个问题点:

1.根据整个电费缴纳涵盖的区间,生成自然月的“维度表”。暴力的方法,那就是取最大最小日期,然后构造一个递增序列求出每一天,再分组/去重汇总成月,但是太低效了。直接的方法是获取最大最小日期之间有多少个月,然后 add_months 加上去,除了最大最小日期的端点,取出每一个月的 1 号和当月最后一天

是不是一定要做这一步?实际工作中可以使用固定日期范围或者数仓里现成的维度表 dim_date 等等。但是动态日期维度表生成一方面更符合这个场景的逻辑;另一方面也是笔试的考点,只不过动态生成每一天日期更常见。

2.根据上一步获取的自然月区间,来与缴费区间关联,关联的条件是存在交集。这又涉及到两点,第一是如何判断有交集(包括哪怕只有端点一天相交),第二是如何取交集区间的数据来进行后续的计算

(目前【SQL 周周练】系列默认讨论 Hive,但很多思路可以移植到其他 SQL 方言;我了解到诸如 OraclePostgre 是有非常多函数的,可能相对于 Hive 会有更直接更好用的方法 | SparkSQL 一般都能兼容 Hive,后期我会逐步拓展到 MySQLDuckDBDoris/Starrocks

下面,我用 NumPyScipy 生成模拟的数据集:

三、用 Python 生成模拟数据#

只关心 SQL 代码的同学,可以跳转到第四节(我在工作中使用 Hive 较多,因此采用 Hive 的语法)

模拟代码如下:

  1. 构造日期间隔,并将顺序随机打乱,作为“缴费”日期区间:
import numpy as np
import pandas as pd
from datetime import datetime, timedelta

# 确定随机数种子
np.random.seed(2025)

# 构造日期间隔
date_interval_list = [1, 3, 7, 14, 15, 28, 30, 45, 90] * 2

# 打乱顺序,注意它是直接修改没有返回
np.random.shuffle(date_interval_list)  
print(date_interval_list)
  1. 随机抽样生成“缴费”区间的日耗电量,构造“缴费”区间的起始日期、结束日期。将前面生成的数据转为 pd.DataFrame,并输出为 csv 文件:
power_consumption = range(50, 510, 10)

# 随机抽取数值,作为缴费区间的平均日消耗电量
daily_electricity_usage = np.random.choice(
    power_consumption, size=len(date_interval_list), replace=False
)

df = pd.DataFrame(
    {
        "period_start": [
            datetime(2023, 12, 1) + timedelta(days=sum(date_interval_list[:i]) )
            for i in range(len(date_interval_list))
        ],
        "period_end": [
            datetime(2023, 12, 1) + timedelta(days=sum(date_interval_list[:i])-1)
            for i in range(1, len(date_interval_list) + 1)
        ],
        "electricity_usage": date_interval_list * daily_electricity_usage,
        "daily_electricity_usage": daily_electricity_usage,
        "period": date_interval_list,
    }
)
# 在 Jupyter 环境中显示 dataframe,其他环境执行可能报错
display(df)

out_csv_path = "dwd_electricity_usage_records.csv"
df.to_csv(out_csv_path, index=False, header=False, encoding="utf-8-sig")
  1. 如果表存在则删除,创建新的 Hive 表,并将数据 load 到表中:
from pyhive import hive

# 配置连接参数
host_ip = "127.0.0.1"
port = 10000
username = "Jiang"


with hive.Connection(host=host_ip, port=port) as conn:
    cursor = conn.cursor()

    hive_table_name = 'data_exercise.dwd_electricity_usage_records'

    drop_table_sql = f"""
    drop table if exists {hive_table_name}
    """

    print('删除表语句:\n', drop_table_sql)

    cursor.execute(drop_table_sql)

    create_table_sql = f"""
    create table if not exists `{hive_table_name}` (
        period_start string comment '费用期始',
        period_end string comment '费用期终',
        electricity_usage int comment '计费期间总用电量',
        daily_electricity_usage int comment '计费期间日均电量(用来核对结果)',
        period int comment '期间天数'
    )
    comment "不定期的电表计费数据 | author: 蒋点数分 | 文章编号:04d08f61"
    row format delimited fields terminated by ','
    stored as textfile
    """

    print("创建表语句:\n", create_table_sql)
    cursor.execute(create_table_sql)

    import os

    load_data_sql = f"""
    load data local inpath "{os.path.abspath(out_csv_path)}"
    overwrite into table {hive_table_name}
    """

    print("覆盖写入数据语句:\n", load_data_sql)
    cursor.execute(load_data_sql)

    cursor.close()

我通过使用 PyHive 包实现 Python 操作 Hive。我个人电脑部署了 HadoopHive,但是没有开启认证,企业里一般常用 Kerberos 来进行大数据集群的认证。

四、SQL 解答#

我使用 CTE 的语法,这样将步骤串行展示,逻辑比较清晰,下面分成几部分解释 SQL 语句:

1.这部分代码的逻辑是,先求出所有缴费区间最大最小的日期;然后利用求出来两个日期之间的有多少自然月(包含两个端点所在的自然月);有的同学会问,Hive 中是存在 months_between 的,为什么你不用,而要自己“年-年”、“月-月”的这么算?当然可以用,但是要额外处理一下,此代码块后面,我解释一下,如何额外处理:

with get_date_interval as (
    -- 获取总体的开始、结束日期:这里只是一个电表
    -- 多个电表注意后续的逻辑该带分组的分组
    select
      min(period_start) as all_start
    , max(period_end) as all_end
    from data_exercise.dwd_electricity_usage_records
)
, get_inner_months as (
    -- 获取两个日期之间有多少个月;“年-年”*12“+ 月-月”
    -- 请参看文章的解释;如果按照“数数”的规则,注意结果加 1
    -- 如果要用 months_between 函数,该怎么处理?
    -- 根据官方文档,如果 两个日期的 day 部分不一样或者不是当月的最后一天
    -- 则是按照 31 天的月份来计算小数部分 `fractional portion`
    -- 注意 months_between 返回的小数,要取整
    select
      all_start, all_end
    , (year(all_end) - year(all_start))*12 + month(all_end) - month(all_start) + 1 as month_cnt
    -- , int(months_between(last_day(all_start), last_day(all_start)))+1 as month_cnt
    from get_date_interval
)
……

关于 months_between 函数,我查看了 Hive 的官方文档,人家是这么说的 —— “If date1 and date2 are either the same days of the month or both last days of months, then the result is always an integer. Otherwise the UDF calculates the fractional portion of the result based on a 31-day month and considers the difference in time components date1 and date2.” 翻译后是 “如果 date1 和 date2 在当月的日期是相同的,或者都是各自月份的最后一天 ,那么结果始终是一个整数。否则,该函数会基于一个31天的月份来计算结果的小数部分,并考虑 date1 和 date2 的时间部分差异。”

我没有看源代码,但是这里面说的是 fractional portion 小数部分按照 31 天的月来处理,什么意思呢?我稍微实验了一下,小数部分应该就是 (day(...)-day(...))/31(负数就向前借位),它的结果是四舍五入保留 8 位数。我们想避开这个小数,就想让它返回整数月份怎么办呢(因为情况比较多,单独用向上取整、向下取整或者四舍五入取整都有问题,要综合判断,直接返回整数月份最好)

我这里的最大最小日期,大概率 day 部分是不一样的,也不太可能都是每个月份的最后一天。这样会涉及小数,那么咱手工处理一下 —— 比如将最大最小日期改成当月的最后一天,这个很简单,使用 last_day 函数即可;还可以改成当月的第一天,使用 trunc(...,'MM'),也满足使用条件。甚至咱追求个性,将日期的年月部分取出来,日部分都写个 5 号、13 号也可以,只要不超过 28 号,就不用考虑日期合法性问题。

图片里 m1m4 的写法都是可以的,只不过后面要转换为整数。

2.使用 repeatsplit 加上 posexplode 构造一个指定长度的递增序列,这也是 Hive 的常见技巧。explode 更常见,posexplode 就是多返回一个“位置”列。(我个人电脑部署的 Hive 版本是 3.1.3,我验证过没有 sequence 函数)。如果根据日期取某个自然月的 1 号和最后一天,上一小节我已经解释过;注意最早最晚日期根据我题目的定义,要按照实际日期来计算:

……
, get_every_month_start as (
    select
     (case
        -- 对于非开始日期的那个月,取当月的 1 号
        when idx > 0 then add_months(trunc(g.all_start, 'MM'), idx)
        -- 如果是开始日期,直接用开始日期,不管它是不是当月的 1 号
        when idx = 0 then g.all_start 
     end) as month_start
    , all_end
    from get_inner_months g
    lateral view posexplode(split(repeat(',', month_cnt-1), ',')) month_table as idx, m
)
, get_every_month_end as (
    select
      month_start
      -- 如果是结束日期的那个月,结束日期就取截止日期
      -- 否则取每个月的最后一天,即 last_day
    , if(date_format(month_start, 'yyyy-MM') = date_format(all_end, 'yyyy-MM'), all_end,
    last_day(month_start)) as month_end
    from get_every_month_start
)
……

3.这一步是将前面构造的自然月维度表 get_every_month_end 与电费数据集,进行关联。关联的条件是判断有交集,为什么这两个条件可以保证有交集,我在代码下面用图片结合文字论述。另外就是当两者有交集的情况下,取交集的逻辑是 —— 起点选择最晚 greatest 的那个,终点选择最早 least 的那个。

Hive 非常早期的版本是不支持不等值连接的。这个时候要小心处理 on 后面的条件,如果是 inner join 一般将 on 后面的条件移到 where 中,而 outer join 如果 on 不匹配,只是代表关联的表没有匹配的数据,可以理解为给你一个 null;比如拿 left outer join 举例,左表的数据是不会被 on 条件过滤掉的,如果 on 能导致一条数据匹配多条,左表的部分数据甚至全部数据还会翻倍上涨。这个时候,不能将 on 的条件直接移到 where 中,而是需要保持 on 里面的最粗糙的匹配逻辑(这个场景不行,因为两个条件都是不等值,需要人工造一个无用的关联),在 select 中对于右表的列,在使用时进行 on 条件的完整判断 if,如果满足,返回右表列的值;否则返回 null。这种方法缺点是每一个用到右表的列,都需要这么处理。

……
, get_interval_power_usage as (
    select
      e.month_start as month_start
    , e.month_end as month_end
    -- 注意取两个日期区间的交集
    -- 非常重要,别忘了取区间的交集
    -- 起点取最后面的那个,终点取最早的那个
    , greatest(r.period_start,e.month_start) as period_start
    , least(r.period_end,e.month_end) as period_end
    , nvl(r.daily_electricity_usage,0) as daily_electricity_usage
    from get_every_month_end e
    left outer join data_exercise.dwd_electricity_usage_records r
        on r.period_end >= e.month_start
        and r.period_start <= e.month_end
)
……

4.这是最后求结果的 SQL 部分,注意前面取交集后,会划分称成若干“子”区间,每个区间计算时用原来的日均值乘以区间长度(注意 datediff 结果加 1)计算该区间的总电量消耗。group by 加和整月的电量,整月的长度可以用子区间长度叠加 days_cnt 列,也可以直接求 days_cnt2 列,注意我这里的 month_startmonth_end 在最开始与最晚日期是根据实际日期定义的,因此可以用 days_cnt2 的逻辑。

另外,这里只靠 month_start 就足以标记统计的自然月,没必要将 month_end 也作为 group by 的分组标识,用 max/min 当成维度值处理,这也是最基本的技巧。(我已经验证Hive 3.1.3 也没有 any_value)在数据量很大时或者分组 key 比较多时,有助于提高计算速度、减少资源消耗:

……
select
  month_start
, max(month_end) as month_end
  
, sum(daily_electricity_usage * (datediff(period_end, period_start)+1)) 
    / sum(datediff(period_end, period_start)+1) as new_daliy_usage
-- 用哪个做分母都行    
-- , sum(datediff(period_end, period_start)+1) as days_cnt
--, max(datediff(month_end, month_start)+1) as days_cnt2
from get_interval_power_usage
group by month_start 

5.完整的 SQL 语句:

with get_date_interval as (
    -- 获取总体的开始、结束日期:这里只是一个电表
    -- 多个电表注意后续的逻辑该带分组的分组
    select
      min(period_start) as all_start
    , max(period_end) as all_end
    from data_exercise.dwd_electricity_usage_records
)
, get_inner_months as (
    -- 获取两个日期之间有多少个月;为什么不用 months_between
    -- 请参看文章的解释;如果按照“数数”的规则,注意结果加 1
    select
      all_start, all_end
    , (year(all_end) - year(all_start))*12 + month(all_end) - month(all_start) + 1 as month_cnt
    from get_date_interval
)
, get_every_month_start as (
    select
     (case
        -- 对于非开始日期的那个月,取当月的 1 号
        when idx > 0 then add_months(trunc(g.all_start, 'MM'), idx)
        -- 如果是开始日期,直接用开始日期,不管它是不是当月的 1 号
        when idx = 0 then g.all_start 
     end) as month_start
    , all_end
    from get_inner_months g
    lateral view posexplode(split(repeat(',', month_cnt-1), ',')) month_table as idx, m
)
, get_every_month_end as (
    select
      month_start
      -- 如果是结束日期的那个月,结束日期就取截止日期
      -- 否则取每个月的最后一天,即 last_day
    , if(date_format(month_start, 'yyyy-MM') = date_format(all_end, 'yyyy-MM'), all_end,
    last_day(month_start)) as month_end
    from get_every_month_start
)
, get_interval_power_usage as (
    select
      e.month_start as month_start
    , e.month_end as month_end
    -- 注意取两个日期区间的交集
    -- 非常重要,别忘了取区间的交集
    -- 起点取最后面的那个,终点取最早的那个
    , greatest(r.period_start,e.month_start) as period_start
    , least(r.period_end,e.month_end) as period_end
    , r.daily_electricity_usage as daily_electricity_usage
    from get_every_month_end e
    left outer join data_exercise.dwd_electricity_usage_records r
        on r.period_end >= e.month_start
        and r.period_start <= e.month_end
)

select
  month_start
, max(month_end) as month_end
  
, sum(daily_electricity_usage * (datediff(period_end, period_start)+1)) 
    / sum(datediff(period_end, period_start)+1) as new_daliy_usage
-- 用哪个做分母都行    
-- , sum(datediff(period_end, period_start)+1) as days_cnt
--, max(datediff(month_end, month_start)+1) as days_cnt2
from get_interval_power_usage
group by month_start

😁😁😁
我现在正在求职数据类工作(主要是数据分析或数据科学);如果您有合适的机会,即时到岗,不限城市。

原创作者: data-analytics 转载于: https://www.cnblogs.com/data-analytics/p/18874386

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

<think>我们正在处理用户在SQLServer中按周汇总数据的需求。根据站内引用,虽然没有直接给出按周查询的示例,但我们可以结合窗口函数、分组聚合和日期函数来实现。关键点:在SQLServer中,我们可以使用DATEPART函数或FORMAT函数来获取周的信息,然后按周分组。注意:按周汇总可能涉及跨年问题,因此最好同时考虑年份。常见的法是使用年份和周数组合(例如,'2021-01'表示2021年第1周)。但要注意,DATEPART函数返回的周数(wk)是从1到53的整数,不同的年份可能有相同的周数,所以我们应该在分组时同时按年份和周数分组。有两种常用方式:1.使用DATEPART函数获取年份和周数,然后按这两个字段分组。2.使用CONVERT函数将日期转换为特定格式的周开始日期(例如,每周的开始是星期日或星期一),然后按这个转换后的日期分组。方法1示例:SELECTDATEPART(year,OrderDate)ASYear,DATEPART(week,OrderDate)ASWeek,SUM(Sales)ASTotalSalesFROMSalesGROUPBYDATEPART(year,OrderDate),DATEPART(week,OrderDate)ORDERBYYear,Week方法2:将日期调整到每周的第一天(例如,假设每周从星期天开始)SELECTDATEADD(day,-(DATEPART(dw,OrderDate)-1)%7,OrderDate)ASWeekStartDate,SUM(Sales)ASTotalSalesFROMSalesGROUPBYDATEADD(day,-(DATEPART(dw,OrderDate)-1)%7,OrderDate)ORDERBYWeekStartDate但是注意:方法2中,DATEPART(dw,OrderDate)返回的是星期几(默认情况下,星期天是1,星期一是2,...,星期六是7)。但不同的设置可能会导致一周的第一天不同(由@@DATEFIRST控制)。为了确保正确,我们可以设置SETDATEFIRST来指定一周的第一天。或者,我们可以使用一个更稳定的方法:计算每周的星期一(或星期日)的日期。例如,计算每周的星期一:SELECTDATEADD(day,(1-DATEPART(weekday,OrderDate))%7,OrderDate)ASWeekStart,...但是这种方法同样受@@DATEFIRST影响。为了简化,我们可以使用:DATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0)来得到当前日期所在周的周一(注意:这里0代表1900-01-01,是星期一)。这种方法不受DATEFIRST设置影响,因为它始终计算从1900-01-01(星期一)开始的周数。因此,更推荐以下方法(按周一分组):SELECTDATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0)ASWeekStart,SUM(Sales)ASTotalSalesFROMSalesGROUPBYDATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0)ORDERBYWeekStart如果希望按周日分组,那么可以将周一减去6天,但要注意边界年。或者,也可以使用:SELECTDATEADD(day,-1*(@@DATEFIRST+6)%7,DATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0))但这样复杂且不稳定。考虑到用户的需求是按周汇总,并不指定每周从哪一天开始,通常业务中一周的开始可能是周一或周日。因此,我们可以先按照ISO周(国际标准周,从星期一开始)来处理,或者按系统默认设置。但鉴于SQLServer没有内置的ISO周函数(有isoww,但用于获取ISO周数,不是用于分组),我们可以使用上述计算周一的日期作为周开始。因此,我们给出按周一作为一周开始的分组方法(通用且不受设置影响):SELECTDATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0)ASMondayOfWeek,SUM(Sales)ASTotalSalesFROMSalesGROUPBYDATEADD(WEEK,DATEDIFF(WEEK,0,OrderDate),0)ORDERBYMondayOfWeek同时,如果用户需要显示周数,可以同时选择年份和DATEPART(week,OrderDate),但是注意这个周数可能因为开始日期的不同而不同(例如,11日所在的周可能算作上一年的最后一周或新年的第一周,取决于星期几)。如果需要符合ISO标准的周,可以使用DATEPART(isoww,OrderDate)和DATEPART(isoyear,OrderDate)。这样,我们可以按ISO周分组:SELECTDATEPART(isoyear,OrderDate)ASISOYear,DATEPART(isoww,OrderDate)ASISOWeek,SUM(Sales)ASTotalSalesFROMSalesGROUPBYDATEPART(isoyear,OrderDate),DATEPART(isoww,OrderDate)ORDERBYISOYear,ISOWeek综合考虑,我们给出两种方法的示例。示例1:按常规周(一周从周日开始)分组,使用DATEPART(year,date)和DATEPART(week,date)(受@@DATEFIRST影响,但通常系统默认周日为第一天)示例2:按ISO周分组(一周从周一开始,符合国际标准)由于用户没有明确一周从哪一天开始,我们提供两种常见的示例。但注意:如果使用示例1,为了确保结果一致,可以在查询前设置SETDATEFIRST7(将星期天作为一周的第一天),然后按年和周分组。根据引用[1]中提到的窗口函数等高级技术,我们也可以使用窗口函数来按周累计等,但用户需求是汇总(分组聚合)。我们提供一个使用常规周(年和周数)和ISO周的示例,并给出按周一作为周开始日期的示例(即按周开始日期分组)。下面给出具体的查询语句示例:</think> ### SQL Server 按周汇总数据查询方法 在 SQL Server 中按周汇总数据主要依靠**日期函数**和**分组聚合**实现。以下是三种常用方法及示例(假设数据表为 `Sales`,日期列为 `OrderDate`,汇总列为 `SalesAmount`): --- #### 方法1:使用 `DATEPART()` 函数(精确到年和周) ```sql SELECT YEAR(OrderDate) AS OrderYear, DATEPART(week, OrderDate) AS WeekNumber, SUM(SalesAmount) AS WeeklySales FROM Sales GROUP BY YEAR(OrderDate), DATEPART(week, OrderDate) ORDER BY OrderYear, WeekNumber; ``` - ✅ **优点**:直接返回年和周数 - ⚠️ **注意**:`DATEPART(week)` 受服务器配置影响(可通过 `SET DATEFIRST` 调整周起始日) --- #### 方法2:ISO周格式(跨年周兼容) ```sql SELECT DATEPART(isoyear, OrderDate) AS ISOYear, -- ISO标准年份 DATEPART(iso_week, OrderDate) AS ISOWeek, -- ISO周数(周一到周日) SUM(SalesAmount) AS WeeklySales FROM Sales GROUP BY DATEPART(isoyear, OrderDate), DATEPART(iso_week, OrderDate) ORDER BY ISOYear, ISOWeek; ``` - ✅ **优点**:符合国际标准 ISO 8601,自动处理跨年周 - 📌 **关键函数**:`iso_week` 确保每周从周一开始 --- #### 方法3:按周起始日分组(生成日历周标签) ```sql SELECT CONVERT(varchar(10), DATEADD(DAY, 1-DATEPART(WEEKDAY, OrderDate), OrderDate), 120) AS WeekStartDate, -- 本周周一日期 SUM(SalesAmount) AS WeeklySales FROM Sales GROUP BY DATEADD(DAY, 1-DATEPART(WEEKDAY, OrderDate), OrderDate) ORDER BY WeekStartDate; ``` - ✅ **优点**:直接显示每周起始日期 - 🔧 **自定义**:调整 `DATEPART(WEEKDAY)` 偏移量可定义周起始日(如改 `1-` 为 `-5` 可设周日为起始) --- ### 关键说明 1. **跨年处理**: 推荐优先使用 `iso_week` + `isoyear`(方法2)避免跨年周归属错误[^1]。 2. **性能优化**: ```sql -- 建立日期索引 CREATE INDEX IX_Sales_OrderDate ON Sales(OrderDate) INCLUDE (SalesAmount); ``` 3. **结果增强版**(含周日期范围): ```sql SELECT MIN(OrderDate) AS WeekStart, MAX(OrderDate) AS WeekEnd, SUM(SalesAmount) AS WeeklySales FROM ( SELECT OrderDate, SalesAmount, DATEADD(DAY, 1-DATEPART(WEEKDAY, OrderDate), OrderDate) AS WeekStartDay FROM Sales ) tmp GROUP BY WeekStartDay; ``` --- ### 📌 注意事项 - 使用 `DATEPART(week)` 时,不同年份的第1周定义可能不同(如11日是否在当周) - 通过 `SET DATEFIRST {1|2|3...7}` 可更改周起始日(1=周一,7=周日) - 高频查询建议将周计算列持久化到表中减少运行时开销
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值