SQL编写WHERE子句时如何取两个日期中的较早值作为筛选条件
表结构定义
- Control表字段:
ControlID、Date1、Date2、Date3 - Sale表字段:
ID、ControlID、SaleDate
业务需求
查询销售日期从Date1起始,截止到Date2与Date3中较早日期的所有销售数据。
待完善的初始SQL写法如下:
SELECT * FROM SALE S JOIN CONTROL C ON S.CONTROLID=C.ID WHERE S.SALEDATE>=C.DATE1 AND S.SALEDATE<EARLIER(DATE2, DATE3)
注:初始SQL的关联条件存在笔误,Control表的主键字段为
ControlID,正确关联条件应为S.ControlID = C.ControlID
核心疑问:上述SQL中EARLIER(DATE2, DATE3)的逻辑应当如何正确编写?是否需要实现为新的标量函数?另有方案提出可以用如下写法实现逻辑,是否可行:
AND S.SALEDATE<C.DATE2 AND S.SALEDATE<C.DATE3
不需要额外自定义标量函数,以下两种写法逻辑完全等价,都可以正确满足需求,可根据场景选择:
方案1:使用数据库原生最小值函数
所有主流关系型数据库都内置了多值取最小值的能力,可直接替换EARLIER占位符,无需自行开发函数:
- MySQL、PostgreSQL、Oracle、SQL Server 2022及以上版本:直接使用内置
LEAST()函数,逻辑写为LEAST(C.Date2, C.Date3) - SQL Server 2022以下版本:用CASE表达式实现即可,写法为
CASE WHEN C.Date2 < C.Date3 THEN C.Date2 ELSE C.Date3 END
对应完整过滤条件示例:
WHERE S.SALEDATE >= C.Date1 AND S.SALEDATE < LEAST(C.Date2, C.Date3)
这种写法的优势是语义和业务需求完全对齐,代码可读性高,后续维护人员可以直接理解“取两个日期中更早的一个作为截止点”的逻辑。
方案2:双条件并行比较
提出的S.SALEDATE<C.DATE2 AND S.SALEDATE<C.DATE3写法是完全正确的,和取最小值的逻辑等价:如果一个日期同时小于Date2和Date3,必然小于两个日期中的较早值;反之如果一个日期小于两个日期的较早值,也必然同时小于两个日期。
这种写法的优势是全数据库版本兼容,不需要考虑函数版本支持问题,且在部分老版本数据库中,优化器对这种多条件比较的索引选择率判断更准确,在Date2、Date3字段建有索引时查询性能可能更优。缺点是语义不够直观,如果后续新增更多截止日期判断,需要新增对应AND条件,维护成本更高。
不推荐方案
不要自定义标量函数实现取较早日期的逻辑。自定义标量函数在多数数据库的老版本中会产生额外的调用开销,甚至会阻碍谓词下推等优化器逻辑,导致查询性能下降,属于冗余实现。
内容的提问来源于stack exchange,提问作者variable

