应显式使用str_to_date()并严格匹配格式,避免隐式转换;postgresql中宜用范围查询而非::date截断,以防时区导致数据遗漏。

MySQL里用STR_TO_DATE()把字符串转成日期再比较
直接拿字符串和DATETIME字段比,MySQL会隐式转换,但结果常出人意料——比如'2023-10-05'可能被当成'2023-10-05 00:00:00',而'2023-10-05 14:30'这种带时间的字符串又可能被截断或报错。
稳妥做法是显式转成日期类型:
- 用
STR_TO_DATE('2023-10-05', '%Y-%m-%d')转日期,确保格式严格匹配 - 如果字符串含时间,比如
'2023-10-05 14:30:22',得用STR_TO_DATE('2023-10-05 14:30:22', '%Y-%m-%d %H:%i:%s') - 注意:格式符大小写敏感,
%H是24小时制,%h是12小时制;错一个字母就返回NULL - 别依赖
CAST('2023-10-05' AS DATE),它在某些MySQL版本里对非法格式静默失败,不如STR_TO_DATE()可控
PostgreSQL中避免用::date粗暴截断时间戳
PostgreSQL支持timestamp '2023-10-05 14:30:22'::date,看起来方便,但容易漏掉时区和边界问题。
真实场景里,你查“10月5日全天”的数据,用created_at::date = '2023-10-05'看似正确,实则可能丢掉2023-10-06 00:00:00+00这种UTC时间(本地时区是+8的话,它实际是10月5日20点)。
- 更安全的是用范围查询:
created_at >= '2023-10-05' AND created_at - 如果字段带时区(
timestamptz),先用created_at AT TIME ZONE 'Asia/Shanghai'对齐业务时区再截断 -
::date会丢掉时区信息,且无法利用索引(除非建函数索引),范围查询更容易走索引
SQLite里datetime()函数必须指定修饰符才可靠
SQLite没有原生日期类型,全靠函数解析字符串。直接写date_column = '2023-10-05'等于字符串比对,不校验合法性,也不处理时区。
要用datetime()统一转成标准格式再比:
-
datetime(date_column) = datetime('2023-10-05')—— 这样能兼容'2023-10-05'、'2023-10-05 12:00'甚至'10/05/2023'(但后者需额外格式修饰符) - 遇到
'05/OCT/2023'这种格式,得加修饰符:datetime(date_column, 'strftime', '%d/%b/%Y')(注意SQLite 3.40+才支持) - 别用
julianday()做减法比大小,可读性差,还容易因浮点误差出错 - 如果字段存的是Unix时间戳(整数),先用
datetime(date_column, 'unixepoch')转,否则datetime(1696492800)会被当成“公元1696年”
跨数据库写SQL时,别碰NOW()和CURRENT_DATE的隐式行为
同一个CURRENT_DATE,在MySQL里是服务器本地时间,在PostgreSQL里默认是事务开始时间,在SQLite里是每次执行时计算——三者在长事务或分布式场景下可能差几秒甚至几分钟。
如果你要查“今天新增的数据”,不能只靠函数名:
- 明确时区:MySQL加
CONVERT_TZ(NOW(), '+00:00', '+08:00');PostgreSQL用NOW() AT TIME ZONE 'Asia/Shanghai' - 避免在WHERE里反复调用
NOW(),像created_at BETWEEN NOW() - INTERVAL 1 DAY AND NOW(),两次调用可能返回不同值 - 更稳的方式是用变量或参数绑定:比如预计算好
@start_time := DATE_SUB(NOW(), INTERVAL 1 DAY),再在查询里复用 - 测试时记得关掉连接池的“连接复用时间戳”,有些ORM会缓存第一次调用的
NOW()结果
时间比较最麻烦的不是语法,而是你永远不知道数据里混着多少没声明时区的字符串、被截断的毫秒、或者前端传来的ISO格式里偷偷带了Z。动手前先SELECT MIN(created_at), MAX(created_at) FROM table LIMIT 5看一眼真实数据长啥样。










