当前位置:首页 > 问答 > 正文

MSSQL里怎么搞定月份查询那点事儿,老是出错真烦人怎么办

(信息来源:CSDN博客《SQL Server日期查询实战技巧》、知乎专栏《T-SQL常见坑点排查》)

先别急,这事儿太常见了,十个用MSSQL查日期的人里,起码有八个在月份查询上栽过跟头,问题就出在日期格式的千变万化和函数使用的细微差别上,咱们今天就不讲那些高大上的理论,直接上干货,告诉你最常见的错误和怎么轻松搞定它们。

第一大坑:忘记处理月份的天数差异

这是最经典的错误,比如你想查上个月的所有数据,很多人第一反应是这么写:

SELECT * FROM 订单表
WHERE 订单日期 >= DATEADD(MONTH, -1, GETDATE())

(信息来源:个人踩坑经验及技术社区常见问题汇总)

乍一看没毛病吧?用DATEADD函数减去一个月,但这里有个大问题:如果今天是3月31日,减去一个月是2月31日,可2月根本没有31号!SQL Server会很“聪明”地帮你处理成2月28日或29日,这样一来,你实际查的是从2月28日到3月31日的数据,根本不是完整的2月份!

正确姿势:获取月份的第一天和最后一天

想准确查某个月份,核心思路是找到这个月的第一天和最后一天,这里给你两个万金油方法:

MSSQL里怎么搞定月份查询那点事儿,老是出错真烦人怎么办

  1. 用DATEFROMPARTS函数(推荐,SQL Server 2012及以上版本) 这个函数特别直白,就是根据你给的年份、月份、日子来构造一个日期,比如你想查2023年11月的数据:

    SELECT * FROM 订单表
    WHERE 订单日期 >= DATEFROMPARTS(2023, 11, 1)  -- 11月第一天
    AND 订单日期 < DATEFROMPARTS(2023, 12, 1)    -- 12月第一天(注意是小于,不包含)

    (信息来源:Microsoft官方文档《DATEFROMPARTS (Transact-SQL)》)

    为什么第二个条件是小于12月1日?因为这样能完美包含11月30日23:59:59的所有数据,还避免了用BETWEEN可能遇到的时间精度问题(比如如果日期字段包含时间部分,用BETWEEN '2023-11-01' AND '2023-11-30'会漏掉11月30日当天所有时间不为00:00:00的记录)。

  2. 用EOMONTH函数(也是SQL Server 2012及以上) 这个函数专门用来获取某个月的最后一天,特别方便。

    SELECT * FROM 订单表
    WHERE 订单日期 BETWEEN DATEFROMPARTS(2023, 11, 1) AND EOMONTH(DATEFROMPARTS(2023, 11, 1))

    这里用BETWEEN是安全的,因为EOMONTH返回的是当月的最后一天,日期部分是精确的,但如果你字段里有时间部分,保险起见可以这么写:

    SELECT * FROM 订单表
    WHERE 订单日期 >= DATEFROMPARTS(2023, 11, 1)
    AND 订单日期 < DATEADD(DAY, 1, EOMONTH(DATEFROMPARTS(2023, 11, 1))) -- 下个月第一天

    (信息来源:Stack Overflow高赞回答关于日期范围查询的讨论)

    MSSQL里怎么搞定月份查询那点事儿,老是出错真烦人怎么办

第二大坑:字符串格式的隐式转换

很多人喜欢直接把日期写成字符串,比如WHERE 订单日期 BETWEEN '2023-11-01' AND '2023-11-30',这其实是在逼着SQL Server做隐式转换,风险很大,如果你的系统日期格式设置不是yyyy-mm-dd,或者字符串格式写错了(比如写成'2023-11-1'少了前导零),查询结果就可能完全不对,甚至报错。

正确姿势:显式转换或使用日期函数

要么用上面推荐的DATEFROMPARTS等日期函数,要么就用CONVERT函数明确指定格式:

SELECT * FROM 订单表
WHERE 订单日期 >= CONVERT(DATETIME, '20231101', 112) -- 112是'yyyymmdd'格式的代码
AND 订单日期 < CONVERT(DATETIME, '20231201', 112)

(信息来源:Microsoft官方文档《CONVERT (Transact-SQL)》中的日期格式代码表)

用这种无分隔符的yyyymmdd格式是最保险的,不受任何语言或区域设置的影响。

MSSQL里怎么搞定月份查询那点事儿,老是出错真烦人怎么办

第三大坑:忽略时间部分

如果你的日期字段是DATETIMEDATETIME2这种带时间部分的类型,查询2023-11-01其实相当于2023-11-01 00:00:00,如果你只存了日期,那没问题,但如果记录的时间是2023-11-30 下午5:30,你用WHERE 订单日期 = '2023-11-30'是查不到这条记录的,因为'2023-11-30'等价于'2023-11-30 00:00:00'

正确姿势:始终使用范围查询

这就是为什么上面一直强调用>= 月初 AND < 下个月初的模式,它能确保把整个月份的所有时间点都囊括进来,一劳永逸。

实用技巧:查询当前月或相对月份

  • 查当前月数据:

    SELECT * FROM 订单表
    WHERE 订单日期 >= DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)
    AND 订单日期 < DATEADD(MONTH, 1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
  • 查上个月数据:

    SELECT * FROM 订单表
    WHERE 订单日期 >= DATEADD(MONTH, -1, DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1))
    AND 订单日期 < DATEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), 1)

最后总结一下核心心法:

  1. 别信任直接的月份加减:尤其是涉及月末日期时。
  2. 瞄准月初和下个月初:用>= 月初 AND < 下个月初这个模式永远没错。
  3. 多用现代日期函数DATEFROMPARTSEOMONTH让你的代码更清晰、更安全。
  4. 避免字符串隐式转换:要么用函数,要么用CONVERT显式转换。
  5. 永远记得时间部分:只要字段可能包含时间,就用范围查询代替等值查询。

把这些套路记熟了,月份查询这点事儿就再也难不住你了,下次再出错的时候,回来看看这几条,准能快速找到问题所在。