1. to_date()为什么在MySQL里报错:一个从Oracle带过来的"惯性动作"
先讲一个我自己的事。前几年接手一个老系统改造,把某数据库迁到MySQL。代码是从其他数据库方言直接抄过来的,大量写法类似to_date('2024-03-15', 'yyyy-mm-dd'),当时想都没想就放到MySQL里执行,结果第一条就把我干懵了:
ERROR 1305 (42000): FUNCTION test_db.to_date does not exist看到这个报错的时候我才反应过来——MySQL压根就没有to_date()这个内置函数。那会儿我第一反应是"是不是函数名拼错了",还专门翻了一圈官方函数列表,确认真没有。这个细节其实挺有代表性的,因为很多从Oracle或者PostgreSQL转过来的人,最初几天都会栽在"函数名惯性"上。就连我自己,一开始也总觉得"日期转换不就该有个to_date函数吗",但MySQL的哲学不太一样,它给了你一套自己的日期处理工具,只是名字不叫这个。
说个数据感受一下差距:在Oracle里,TO_DATE('2024-03-15', 'YYYY-MM-DD')是标准到不能再标准的写法,几乎所有SQL语句涉及日期入参时都会用到它;但MySQL这边靠的是STR_TO_DATE()、CAST()、DATE()这一组函数,而且人家还特别傲娇——格式占位符体系跟Oracle完全不通用。你用'yyyy-mm-dd'作为格式串去调MySQL的STR_TO_DATE(),八成得到NULL,因为MySQL的格式符是%Y-%m-%d这种带百分号的风格。这个坑我后面具体展开,先记住一个结论:MySQL里没有to_date(),它的核心替代品是STR_TO_DATE(),偶尔用CAST()或DATE()就够了。
不过你如果只是想把一个日期字符串转换成日期类型,还有更偷懒的方式。MySQL对标准格式的日期字符串是自动识别的,比如'2024-03-15'、'20240315'、'2024/03/15'这类写法,直接CAST('2024-03-15' AS DATE)就行,连格式串都不用写。所以对很多人来说,从Oracle迁过来之后第一件要改的习惯,就是"别一上来就找to_date,先想想这个字符串是不是标准格式,如果是,CAST就完了"。
这篇文章就说清楚这几件事:MySQL里日期转换的完整函数清单是什么、怎么用、格式串那套%规则到底怎么回事、哪些场景最容易踩坑,以及从Oracle迁移过来时怎么写等价SQL。全程用实际例子说话,你对照着敲一遍,基本就不会再在日期上翻车了。
提示:如果你是从MySQL往其他数据库迁,比如迁到PostgreSQL,那边也没有
to_date()的同名函数(但那边叫TO_DATE()),这套"先确认目标数据库函数清单"的思路同样适用。
2. 真正干活的日期函数:STR_TO_DATE、CAST、DATE、DATE_FORMAT各有分工
MySQL的日期处理函数确实多,而且一眼看去容易混。我先给你一张表,把这些函数的分工一次理清楚,后面再逐个讲细节。
| 函数 | 作用 | 典型写法 | 返回类型 |
|---|---|---|---|
STR_TO_DATE(str, format) | 按指定的格式串,把字符串解析成日期 | STR_TO_DATE('20240315', '%Y%m%d') | DATE/DATETIME |
CAST(str AS DATE) | 将标准格式字符串强转为日期 | CAST('2024-03-15' AS DATE) | DATE |
DATE(expr) | 从日期时间表达式里提取日期部分 | DATE('2024-03-15 10:30:00') | DATE |
DATE_FORMAT(date, format) | 把日期格式化为指定格式的字符串 | DATE_FORMAT(NOW(), '%Y-%m-%d') | VARCHAR |
FROM_UNIXTIME(ts) | 将Unix时间戳转为日期时间 | FROM_UNIXTIME(1710000000) | DATETIME |
UNIX_TIMESTAMP(date) | 把日期转为Unix时间戳 | UNIX_TIMESTAMP('2024-03-15') | INT |
CONVERT(str, DATE) | 同CAST,也属于标准转日期路径 | CONVERT('2024-03-15', DATE) | DATE |
这表里最关键的其实是前三个。STR_TO_DATE是Oracleto_date()的正宗平替,它把"字符串"和"格式串"两个参数一起接收,解析完返回日期类型。CAST则适合偷懒,只要字符串本身是MySQL认识的格式,就没必要再写格式串。DATE()则是拿来从DATETIME里截取日期部分,比如你有个created_at是2024-03-15 10:30:00,想按天分组统计,写DATE(created_at)就能干净地拿到2024-03-15。
另一个容易忽视的点:STR_TO_DATE()返回的到底是DATE还是DATETIME,取决于你的格式串里有没有时间部分。比如STR_TO_DATE('2024-03-15', '%Y-%m-%d')返回DATE;但如果格式串里带着%H:%i:%s,那返回的就是DATETIME。这个细节在后续比较和分组时会有影响,最好心里有数。
2.1 什么时候用STR_TO_DATE,什么时候用CAST
按我的经验,判断规则很简单:
- 字符串是
YYYY-MM-DD、YYYYMMDD、YYYY/MM/DD、YYYY-MM-DD HH:MM:SS这类MySQL原生能识别的格式,直接用CAST或CONVERT,一行搞定,不用写格式串。 - 字符串格式不标准,比如
'15/Mar/2024'、'2024年3月15日'、'20240315 103020',或者你从CSV、Excel导进来的各种奇葩格式,必须用STR_TO_DATE,自己指定格式串告诉MySQL怎么解析。
举个实际例子。假设你有一张导入表,日期列存的是03/15/2024这种美式格式,直接CAST会返回NULL,因为MySQL不认这种格式。但你写成:
SELECT STR_TO_DATE('03/15/2024', '%m/%d/%Y');返回2024-03-15,干净利落。
再比如整个系统要从Oracle迁过来,原代码里全是TO_DATE('20240315', 'YYYYMMDD'),在MySQL里等价写法就是:
SELECT STR_TO_DATE('20240315', '%Y%m%d');注意格式串从YYYYMMDD变成了%Y%m%d,这就是两个数据库之间最经典的一处差异。很多人第一次写MySQL的时候,照着Oracle的习惯把'YYYYMMDD'原封不动丢给STR_TO_DATE,最后只会得到一个NULL,然后开始怀疑人生。实际上MySQL的格式符体系跟Oracle完全不同,下一节详细讲。
2.2 标准字符串直接CAST就够了,效率更高
顺便说一个容易被忽略的点:在某些版本和配置下,STR_TO_DATE因为要做格式解析,执行开销比CAST略高一点点。虽然单个查询感知不明显,但如果你的查询在WHERE条件里对大表做日期过滤,比如:
SELECT * FROM orders WHERE STR_TO_DATE(order_date_str, '%Y-%m-%d') = '2024-03-15';这会导致这列就算有索引也用不上,因为你对列做了函数运算,索引直接失效。但写成:
SELECT * FROM orders WHERE order_date_str = DATE('2024-03-15');如果order_date_str本身是标准字符串且建立了索引,就能命中索引。这类性能问题的经验是:能用标准格式存,就别在查询时再去做转换;转换尽量放在写入阶段,查询阶段直接比对原始列。你在设计表结构的时候,如果知道这个字段将来要做范围查询或分组,一定要存成DATE/DATETIME类型,别存VARCHAR。很多老系统就是因为把日期存成字符串,导致后面统计SQL又长又慢,还得靠STR_TO_DATE在查询时硬转。这是我的第一条建议:能用类型解决的问题,不要用函数解决。
3. 格式占位符才是最大的坑:%Y和yyyy完全是两码事
如果说to_date()不存在是MySQL给Oracle迁移者的"下马威",那格式符差异就是“持久战”里的地雷了。我见过太多人在STR_TO_DATE里写'%Y-%m-%d'以外的格式串然后翻车,这里把MySQL格式符完整列一遍,你直接收藏当字典查。
| 格式符 | 含义 | 示例输出 | 对应的Oracle习惯 |
|---|---|---|---|
%Y | 四位数年份 | 2024 | YYYY |
%y | 两位数年份 | 24 | RR或YY |
%m | 两位数月份(01-12) | 03 | MM |
%c | 月份数字,无前导零(1-12) | 3 | MM有些用法 |
%d | 两位数日期(01-31) | 15 | DD |
%e | 日期数字,无前导零(1-31) | 3 | DD有些用法 |
%H | 24小时制小时(00-23) | 14 | HH24 |
%h | 12小时制小时(01-12) | 02 | HH |
%i | 两位数分钟(00-59) | 08 | MI |
%s | 两位数秒(00-59) | 05 | SS |
%f | 微秒(6位数) | 123456 | FF |
%p | AM或PM | PM | AM |
%W | 星期名(Sunday-Monday) | Friday | DAY |
%a | 缩写星期名 | Fri | DY |
%M | 月份名(January-December) | March | MONTH |
%b | 缩写月份名 | Mar | MON |
%j | 一年中的第几天(001-366) | 075 | DDD |
%T | 时间,24小时制(HH:MM:SS) | 14:08:05 | HH24:MI:SS |
%r | 时间,12小时制(HH:MM:SS AM/PM) | 02:08:05 PM | HH:MI:SS AM |
这张表你第一眼会觉得内容多,但实际工作中真正高频的就这么几个:%Y、%m、%d、%H、%i、%s。还有一个看似不起眼但很容易出错的:%i是分钟,不是%M,也不是%m。Oracle里分钟用MI,很多人照搬过来,在MySQL里写成'%Y-%m-%d %H:%MI:%SS',结果解析出来的分钟永远是00或者直接NULL。因为%M在MySQL里是英文月份名(March),%m是数字月份,五花八门的组合确实容易绕晕。
3.1 字符串里的非格式字符必须和格式串一一对应
这是一个特别容易踩但报错又很隐蔽的点。STR_TO_DATE('2024-03-15 14:08:05', '%Y-%m-%d %H:%i:%s')能正常工作,是因为格式串里的连字符-、空格、冒号都和字符串里的实际分隔符一一对应。如果你写的格式串跟字符串的结构对不上,MySQL不会报错,而是默默返回NULL。
典型案例如下:
-- 这个是错的,字符串里是/分隔,格式串里却是- SELECT STR_TO_DATE('2024/03/15', '%Y-%m-%d'); -- 结果:NULL -- 这个才对 SELECT STR_TO_DATE('2024/03/15', '%Y/%m/%d'); -- 结果:2024-03-15更隐蔽的情况是,字符串里用了T作为日期和时间的分隔符,比如ISO格式2024-03-15T14:08:05。MySQL的STR_TO_DATE对ISO格式其实有自动识别能力,直接CAST('2024-03-15T14:08:05' AS DATETIME)就能成功;但如果你非要写格式串,就得写成'%Y-%m-%dT%H:%i:%s',注意那个T要原样写进去。我见过有人在这里调了半天,最后发现是格式串里漏了字母T,这类"非格式字符必须逐字对应"的规则,在做自定义日期格式时一定要记牢。
3.2 12小时制的坑:不写%p,下午全变上午
记得有一次做考勤统计,需要解析一批打卡记录,时间格式是'2024-03-15 02:08:05 PM'。我一开始写的格式串是'%Y-%m-%d %h:%i:%s',结果所有下午的记录全部解析成了凌晨2点。原因就是12小时制必须跟%p配合,格式串得写成'%Y-%m-%d %h:%i:%s %p'才能正确识别PM后缀。
-- 错误: SELECT STR_TO_DATE('2024-03-15 02:08:05 PM', '%Y-%m-%d %h:%i:%s'); -- 结果:2024-03-15 02:08:05(下午2点被当成凌晨2点) -- 正确: SELECT STR_TO_DATE('2024-03-15 02:08:05 PM', '%Y-%m-%d %h:%i:%s %p'); -- 结果:2024-03-15 14:08:05所以我的建议是:解析字符串时尽量用24小时制%H,少用12小时制%h,能少一堆麻烦。只有在数据源本身就带着AM/PM后缀且无法改变格式时,才用%p去配合。
4. 别让错误数据悄悄混进去:解析宽容度与sql_mode防线
日期转换还有一个很多人没意识到的角落:MySQL解析日期的"宽容度"是可配置的,默认情况下它可能帮你自动纠正错误,也可能直接拒绝,这取决于你的sql_mode设置。先看一段实际表现:
-- 正常情况下,这个应该报错或者返回NULL SELECT STR_TO_DATE('2024-02-30', '%Y-%m-%d');2月30日这个日期根本不存在。在默认的MySQL配置下,不少版本会返回NULL,但有些版本可能把它解析成2024-02-29或者干脆报错。这个行为差异容易让人困惑,尤其是你把同样的SQL从开发环境搬到生产环境,两个环境的sql_mode不一样,表现就不一样。要把这个坑彻底堵住,你要理解NO_ZERO_DATE、NO_ZERO_IN_DATE这两个sql_mode参数的含义。
| sql_mode值 | 作用 | 影响 |
|---|---|---|
NO_ZERO_DATE | 不允许日期中的“零日期” | '0000-00-00'会被拒绝或转为NULL |
NO_ZERO_IN_DATE | 不允许日期中有零部分 | '2024-00-15'、'2024-03-00'会被拒绝或转为NULL |
ERROR_FOR_DIVISION_BY_ZERO | 除零时报错 | 与日期无关,但常一起出现 |
STRICT_TRANS_TABLES | 严格模式,数据非法时拒绝写入 | 插入非法日期时会报错而不是警告 |
ALLOW_INVALID_DATES | 允许存储无效日期 | 与严格模式相矛盾,会放宽日期的校验 |
一个典型的场景:你把一个CSV文件通过LOAD DATA导入,里面有一行日期写的是'2024-02-30'。如果sql_mode不带NO_ZERO_IN_DATE和NO_ZERO_DATE,MySQL可能会把这一行以"警告"级别接收,存进去一个NULL或者一个被纠正过的日期。如果你没看警告,这个脏数据就这么进了库。等你后面做统计,就会发现某个月的订单数莫名其妙少了一条,排查半天才能追到这个原始导入问题。
我的建议是,在日期敏感的项目里,把sql_mode设置得严格一点:
SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';这样非法日期在写入阶段就直接报错,你当场就能发现数据问题,而不是让脏数据在库里潜伏几个月。
4.1 转换失败的三个返回值:NULL、0日期、报错
用STR_TO_DATE解析失败时,MySQL的返回结果有几种状态,理解它们能帮你快速定位问题:
- 返回NULL:最常见的失败状态,原因通常是格式串不匹配、字符串里有非法字符、日期逻辑不存在(如2月30日,且严格模式下)。
- 返回
0000-00-00:在不带NO_ZERO_DATE的模式下,解析一个"空"日期可能会返回这个值。 - 直接报错:在严格模式下,非法日期会导致SQL执行失败,错误信息通常是
Incorrect date value或Truncated incorrect datetime value。
所以我在写导入脚本时,习惯加一层"预检查"逻辑,用STR_TO_DATE去验证每一行数据,如果结果是NULL就单独捞出来记日志:
-- 找出导入表里日期不合法的记录 SELECT row_id, raw_date_string FROM import_data WHERE STR_TO_DATE(raw_date_string, '%Y-%m-%d') IS NULL;这个查询还有个额外好处:当你的格式串写错了,这个SQL会瞬间把大量行标成"不合法",你就能第一时间发现是格式串的问题还是数据本身的问题。这个方法我在处理多个导入项目时都用上了,效率很高。
注意:
STR_TO_DATE('', '%Y-%m-%d')和STR_TO_DATE(NULL, '%Y-%m-%d')都返回NULL,但语义不同。导入数据时如果源字段是空字符串,最好在SQL里先判断一下,别把"空值"和"格式错误"混在一个排查逻辑里。
5. 日期转换实战复盘:从CSV导入到报表统计
工具原理讲完,必须拿实际场景来验证。这里从一个日常项目里抽三个片段,完整走一遍日期转换的链路,你对照着能直接抄。
5.1 场景一:CSV文件里"8位数字串"的批量导入
很多业务系统导出的CSV,日期列是20240315这种纯数字串。直接LOAD DATA导入后,VARCHAR列里存的全是数字。后续要按日期分组统计,就需要转换。通常我在导入阶段就会把它转成真正的DATE类型,避免查询时再转。
-- 建一张带DATE列的导入目标表 CREATE TABLE import_orders ( order_id INT PRIMARY KEY, order_date DATE, amount DECIMAL(10,2) ); -- 从CSV导入,利用STR_TO_DATE在导入时转换 LOAD DATA LOCAL INFILE '/data/orders.csv' INTO TABLE import_orders FIELDS TERMINATED BY ',' (order_id, @order_date_var, amount) SET order_date = STR_TO_DATE(@order_date_var, '%Y%m%d');这个写法的关键在于@order_date_var先把原始字符串读进用户变量,然后通过SET子句里的STR_TO_DATE完成转换再落库。如果原始字符串里有脏数据,LOAD DATA会警告,你可以用SHOW WARNINGS;查看具体哪几行出了问题。注意,由于这里用了DATE类型,以后做任何日期比较、分组,索引都能正常用上,不用再套函数。
5.2 场景二:时间戳字段转成可读日期
另一个常见需求是把Unix时间戳转成日期。比如某张日志表有个ts BIGINT字段,存的是毫秒时间戳,要统计每天的日志量:
-- 毫秒时间戳先除以1000,再用FROM_UNIXTIME转换 SELECT DATE(FROM_UNIXTIME(ts / 1000)) AS log_day, COUNT(*) AS log_count FROM api_logs WHERE ts >= 1709308800000 GROUP BY DATE(FROM_UNIXTIME(ts / 1000)) ORDER BY log_day;这个查询的关键点在于:FROM_UNIXTIME只能处理秒级时间戳,毫秒必须先除1000。你如果直接传毫秒,得到的日期会大得离谱,1970年加几万天那种,一眼就能看出不对劲。另外,这里在GROUP BY里用了DATE()函数把DATETIME转成日期,跟前面讲的一样,如果数据量大且能接受一定的冗余,建议在表中冗余一个日期字段,避免每次都做函数运算。
5.3 场景三:统一各种"口语化日期格式"
真实业务里,同一张表里可能出现各种日期写法,比如有的来源填'2024-03-15',有的填'3/15/2024',还有的填'20240315'。遇到这种"多格式混杂"的脏数据,直接转换会大量失败。我当时的处理方法是写一个"格式化归一"的小SQL,规则如下:
SELECT CASE WHEN raw_date REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' THEN STR_TO_DATE(raw_date, '%Y-%m-%d') WHEN raw_date REGEXP '^[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}$' THEN STR_TO_DATE(raw_date, '%c/%e/%Y') WHEN raw_date REGEXP '^[0-9]{8}$' THEN STR_TO_DATE(raw_date, '%Y%m%d') ELSE NULL END AS normalized_date, raw_date FROM messy_date_table;这种CASE + REGEXP + STR_TO_DATE的组合看起来长,但实际运行很稳定。其中%c和%e是无前导零的月和日,正好匹配'3/15/2024'这种写法。当你处理客户导入的Excel、第三方API的回传数据时,这三条规则覆盖了绝大多数情况。剩下的乱格式,要么在源头修,要么就拎出来人工处理。我当时统计了一下,这个SQL一次性把这批数据的可转换率从不到60%拉到了95%以上,剩下5%是真的地址瞎填的,只能单独处理。
6. 从Oracle/PostgreSQL迁到MySQL:日期转换的等价写法对照
整个章节最实用的部分在这里。如果你的项目正在做数据库迁移,或者你要同时维护两套数据库方言的代码,下面这张等价写法对照表可以省你大量查文档的时间。
| Oracle/PostgreSQL写法 | MySQL等价写法 | 说明 |
|---|---|---|
TO_DATE('2024-03-15', 'YYYY-MM-DD') | STR_TO_DATE('2024-03-15', '%Y-%m-%d') | 基本等价,注意格式符变化 |
TO_DATE('20240315', 'YYYYMMDD') | STR_TO_DATE('20240315', '%Y%m%d') | 纯数字串,最常用 |
TO_CHAR(SYSDATE, 'YYYY-MM-DD') | DATE_FORMAT(NOW(), '%Y-%m-%d') | 日期格式化输出 |
SYSDATE | NOW()或CURRENT_TIMESTAMP | 当前时间 |
TO_CHAR(SYSDATE, 'YYYYMMDDHH24MISS') | DATE_FORMAT(NOW(), '%Y%m%d%H%i%s') | 时间戳字符串 |
TO_DATE('2024-03-15 14:08:05', 'YYYY-MM-DD HH24:MI:SS') | STR_TO_DATE('2024-03-15 14:08:05', '%Y-%m-%d %H:%i:%s') | 带时间部分 |
EXTRACT(YEAR FROM SYSDATE) | YEAR(NOW())或EXTRACT(YEAR FROM NOW()) | 提取年份 |
TO_CHAR(SYSDATE, 'MON') | DATE_FORMAT(NOW(), '%b') | 缩写月份名 |
写这段的时候我回忆起一个具体的坑:Oracle的'YYYY-MM-DD HH24:MI:SS'写法在MySQL里直接魔改成'%Y-%m-%d %H:%i:%s',其中分钟从MI变成%i,这个是最容易忽略的。有个同事当时从Oracle迁过来,几乎所有格式串都改对了,唯独分钟那里一直用%MI,结果每次解析出来的分钟都是00。排查了一个下午,最后发现是格式符写错。这种细节,不踩一次真的记不住。
另外,还有一个很多迁移项目会忽略的差异:Oracle的TO_DATE对字符串和格式串的匹配相对宽松,某些情况下会自动做小范围纠正;MySQLSTR_TO_DATE的严格程度明显更高,格式不完全匹配就会返回NULL。所以迁移时,原本在Oracle能正常解析的'2024-3-5'这种不补零的日期,在MySQL的'%Y-%m-%d'格式下会失败,得改用'%Y-%c-%e'才能解析。这算是一个"隐性不兼容"的典型案例:
-- Oracle里可能正常: TO_DATE('2024-3-5', 'YYYY-MM-DD'); -- MySQL里必须写成: SELECT STR_TO_DATE('2024-3-5', '%Y-%c-%e');这类细节在迁移测试用例设计时一定要覆盖到,否则上线后某个边界数据一触发就是线上事故。
6.1 别在Java/Python代码里转换日期,尽量交给数据库
最后说一个架构层面的建议。很多团队习惯在代码里先把日期字符串拼好再传给SQL,比如Java里用SimpleDateFormat先在业务层把Date对象格式化成String,再拼到SQL里传给MySQL。这种做法不是说不行,但会带来两个麻烦:
- 代码时区、格式化风格和数据库不一致时,会出现"程序认为对,数据库不认"的尴尬。
- SQL里没法直接利用索引优化,因为传入的是格式化后的字符串,而不是数据库理解的日期类型。
更好的做法是:在代码层直接传Date对象或时间戳,让数据库自己处理日期格式。比如MyBatis里用#{}绑定参数时,传入一个java.util.Date,MySQL驱动会自动转成数据库识别的日期类型,既不丢精度,也不容易出现格式歧义。日期格式化输出这类工作,也统一放到SQL的DATE_FORMAT里做,至少保证所有格式化逻辑集中在一处,后续要改格式只改SQL,不用翻代码。
我在实际项目里还养成了一个习惯:项目里所有跟日期转换相关的SQL,统一写在一个专门的mapper XML或视图文件里,方便团队review时专门盯。日期这种问题,一旦出错,影响面往往是"统计少一天"或"时间差8小时"这种特别隐蔽的线上故障,比字段名拼写错误难查得多。集中管理、专项review,能提前拦住大部分问题。
7. 最后几个必须养成的日期处理习惯
这篇聊了不少函数和参数,最后按我个人经验,分享几个值得保持的好习惯,有兴趣可以慢慢培养:
第一个:永远先确认输入字符串的实际格式,再写格式串。不要想当然认为"这列就是yyyy-mm-dd",先看几行真实数据,用REGEXP规则验证一下再动手。因为很多时候源系统改了导出格式,但文档没更新,你写的转换SQL就会突然开始静默地返回NULL。
第二个:排查转换问题时,先单独执行一次SELECT STR_TO_DATE('具体值', '格式串'),确认这个函数没问题,再去查整表数据。这个"单点验证"习惯能帮你把问题快速定位到"数据脏"还是"格式串错"两个方向之一,节省大量时间。
-- 排查套路第一步:拿一条真实数据试 SELECT STR_TO_DATE('2024-03-15 14:08:05', '%Y-%m-%d %H:%i:%s');返回正常,就说明格式串没错,问题出在数据本身;返回NULL,就先检查格式串的字母大小写、分隔符是否和数据匹配。
第三个:导入脏数据前,先跑一轮"预检查查询",把非法的日期值都捞出来,再决定是清洗还是拒收。很多数据质量事故,都是因为偷懒跳过这一步,直接把数据LOAD进去,等统计报表对不上账了才回头查。与其事后痛苦排查,不如事前一分钟检查。
第四个:新表设计阶段,所有日期字段一律用DATE/DATETIME/TIMESTAMP类型,不要用VARCHAR。这看着像废话,但很多系统的历史表里偏偏就有大量VARCHAR日期,留着就是给后来人埋雷。如果你在做表结构评审,看到日期字段是VARCHAR,一定要问一句"为什么",大部分时候答案都是"当初图省事"——省事一时,麻烦一世。
我在一次项目交付中发现,凡是日期字段设计得规范的表,后端的BI统计、报表查询、定时任务写起来都特别顺手;反之,那些VARCHAR日期字段的表,每个查询都绕不开一层STR_TO_DATE,性能和可维护性都差了一个档次。日期转换本质上是一个"少量函数+一套格式规则"的小问题,但处理不好,就是贯穿整个项目的数据质量隐患。把转换逻辑放在写入和清洗阶段,把类型设计规范放在建表阶段,比任何"万能转换函数"都管用。