☰
MySQL字符串日期格式转换:STR_TO_DATE实战与清洗方案
2026/10/14 4:28:58 网站建设 项目流程

干后端开发这几年,最让我头皮发麻的 SQL 场景不是复杂 JOIN,也不是深分页,而是处理一堆烂得五花八门的日期字符串。"2024/03/15 10:30:22"、"15-03-2024 14:30:22"、"20240315143022"、"2024年3月15日"……这些数据从电子表格导入、从第三方接口同步、从旧系统迁移过来,落在 MySQL 里全是 VARCHAR。你要做报表、要做时间范围筛选、要按时间排序,不先把字符串转成真正的 DATE 或者 DATETIME,后面所有操作都会变得很别扭。这几天刚好在帮一个项目清洗历史数据,又一次把 MySQL 字符串日期格式转换的各个套路过了一遍,所以抽空把能用得到的函数、格式说明符、常见坑和可直接复用的 SQL 模板整理出来,方便自己,也方便遇到同样问题的同行。

下面这套方法适合两种人。一种是刚开始接触数据库的初学者,看完能直接照着写,不再怕"日期格式千奇百怪"这种事;另一种是被历史脏数据折磨的运维和业务开发,手头攒了一批字符串日期还没洗,这篇能给你一套完整可落地的清洗方案。我所有例子都带完整 SQL 和预期结果,你可以在本地环境里先跑一遍再上生产。

1. 字符串日期到底有哪些形态,为什么不能直接拿来用

1.1 我在真实表里见到最多的三种字符串日期

第一种是"看起来就差一步"的标准近似型。比如 '2024-03-15 10:30:22' 这种本来就很规整,只差一个类型转换;但更多见的是 '2024/03/15'、'2024.03.15' 这种把日期连接符换掉的形态。这类数据通常是从网页表单、外部接口或者各种报表工具导出的,它们为了显示友好,故意把日期拼成了带斜杠或者点号的样子。你以为是小事,但直接扔给 MySQL 的日期函数处理时,经常解析失败或者被当成普通字符串参与比较,结果完全意想不到。

第二种是紧凑型,典型代表是 '20240315143022' 这种 14 位纯数字,也就是 YYYYMMDDHHMMSS 拼接。这种形态在日志文件、流水表、某些老旧系统的导出文件里特别常见,好处是省空间、排序稳定、跨系统传输不容易被转义搞坏;坏处是人不友好、MySQL 也没法直接把它当日期使。还有 8 位版本 '20240315',只到天,逻辑类似,处理方式也差不多。

第三种是"人类友好但数据库一脸懵"的文本型。比如 'Mar 15, 2024'、'15-Mar-2024'、'2024年3月15日'。这些往往来自国际化系统,或者国内某些业务系统导出的展示字段,人是看得很明白,数据库却完全不知道这能当日期用。遇到这种,必须先明确源串里每个部分对应年月日时的什么位置,才能写对转换格式。你还会碰到年月日顺序都反的(DMY 和 MDY),这是我在跨时区业务里经常踩的坑,后面实战部分会演示统一处理思路。

1.2 一句话结论:库里就不该用字符串存日期

这里先说个容易被忽略的前提。字符串日期转换的根本原因,是历史遗留或外部数据导入,而不是 MySQL 存不了日期。DATETIME、TIMESTAMP、DATE 这些原生类型才是正经选择,它们有严谨的范围校验、内置的运算函数和索引优化。字符串存日期,除了让你在查询时多写一堆转换函数之外,几乎没有任何好处。所以如果你还在建表阶段,我的建议非常明确:日期列一律用原生日期类型,不要用 VARCHAR 存;字符串日期转换只是补救工具,不是长期设计。

不过现实是,很多团队改不动历史表,或者数据是外部合作方推过来的,你不能指望对方先帮你格式化好。这个时候,SQL 层的转换能力就成了唯一可靠且可控的兜底。接下来的内容,我会围绕"在不改表结构的前提下,把字符串日期安全地转换成日期类型,并让它能正确参与筛选、排序和运算"来展开。

2. 核心函数选型:STR_TO_DATE 是主线,CAST 是辅助

2.1 STR_TO_DATE 的语法和格式规则

STR_TO_DATE(str, format) 是 MySQL 解析字符串日期的首选函数,核心作用是把字符串按你声明的格式模板解析成日期或日期时间。它和 DATE_FORMAT 是互为逆运算的关系:DATE_FORMAT 把日期转成字符串,STR_TO_DATE 把字符串转回日期。用法上,你只需要把源串里每个位置的日期分量,用对应的格式说明符替换掉即可。

举个例子:

SELECT STR_TO_DATE('2024/03/15 10:30:22', '%Y/%m/%d %H:%i:%s'); -- 返回 2024-03-15 10:30:22

这里 %Y 匹配四位年份,%m 匹配数字月份,%d 匹配数字日,%H 匹配 24 小时制小时,%i 匹配分钟,%s 匹配秒,而斜杠和空格是源串里的普通字符,必须原样出现在格式串里。换句话说,格式串就是一张"描红纸",把源串每个字符的位置对上,MySQL 就能解析出来。

下面这张表是我平时写转换 SQL 前都会翻一眼的常用说明符清单,建议收藏。

格式符含义匹配示例
%Y四位年份2024
%y两位年份24
%m数字月份,可带前导零03 或 3
%c数字月份,不带前导零3
%d数字日,可带前导零15 或 5
%e数字日,不带前导零5
%H24 小时制小时(00-23)14
%h / %I12 小时制小时(01-12)02
%i分钟(00-59)30
%s / %S秒(00-59)22
%f微秒,六位数字123456
%M英文月份全称March
%b英文月份缩写Mar
%W英文星期全称Friday
%a英文星期缩写Fri
%j一年中的第几天(001-366)075
%pAM / PM 标记PM
%T等价于 %H:%i:%s14:30:22
%r12 小时制,等价于 %h:%i:%s %p02:30:22 PM

这里有个容易忽略的细节:STR_TO_DATE 对 %m 和 %d 这类数字说明符是允许一位或两位数字的,所以 '2024-3-5' 用 '%Y-%m-%d' 也能解析成 2024-03-05。但如果你的源串里月份和日期的前导零是稳定的,尽量写成 %Y-%m-%d 这种带零形式,可读性和排错都更好。反过来,如果源串是 '2024年3月5日' 这种不带前导零的中文日期,用 %Y年%c月%e日 反而更贴切。

再强调一下字面量匹配:格式串里除了 % 开头的说明符,其余字符都必须和源串逐字对上。'2024-03-15' 不会匹配 '%Y/%m/%d',因为中间符号一个是横杠一个是斜杠。这个规则同样适用于中文分隔字符、英文逗号和空格,漏掉任何一个都会解析失败。我见过不少新手在这里翻车,以为是格式不认,实际就是分隔符没写对,或者源串里多了个肉眼看不见的尾随空格。

2.2 CAST 和 CONVERT:能干的活有限但胜在简单

MySQL 还提供 CAST('2024-03-15 10:30:22' AS DATETIME) 和 CONVERT('2024-03-15 10:30:22', DATETIME) 这类转换写法。它的好处是简洁,只要字符串长得和标准日期字面量差不多,就能转。像 '2024-03-15'、'2024-03-15 10:30:22' 这种标准形式,CAST 直接搞定,连格式串都不用写。连字符、斜杠、点号这类常见分隔符,CAST 也有不小的容忍度,'2024/03/15' 同样能转成日期。

但它的短板也很明显:紧凑型 '20240315143022'、中文 '2024年3月15日'、英文 'Mar 15, 2024' 这类非标准形态,CAST 基本无能为力,而且它不支持你自定义格式模板。所以我的选型原则是:标准形态的字符串可以用 CAST 图省事,一旦遇到自定义格式,立刻换 STR_TO_DATE,不要来回试。另外,CONVERT 在 MySQL 里的作用基本和 CAST 一致,只是它作为老语法在部分场景下写法不同,日常用 CAST 就够,没必要记两套。

我自己的习惯是统一用 STR_TO_DATE 处理所有需要自定义格式的字符串转换。原因很简单:格式串写清楚了,同事 review 代码时一眼就能看出源串长什么样。CAST 虽然省字,但遇到边界情况时,它到底认不认某种写法,还得查文档、做实验去猜,不如 STR_TO_DATE 来得可控。标准形态的解析需求其实 STR_TO_DATE 也能覆盖,所以从一致性和可维护性角度,全用 STR_TO_DATE 也不亏。

2.3 反向转换:DATE_FORMAT 和 FROM_UNIXTIME

转换是双向的。DATE_FORMAT(date, format) 负责把日期类型按模板转成字符串,是生成"给人看的日期"的主力。比如:

SELECT DATE_FORMAT('2024-03-15 14:30:22', '%Y/%m/%d %H:%i:%s'); -- 返回 '2024/03/15 14:30:22'

它用的格式说明符和 STR_TO_DATE 基本一致,所以学会了 STR_TO_DATE,DATE_FORMAT 等于顺手就会,只是方向反过来。实际工作中,你经常需要把一个 DATETIME 列输出成前端要的 'YYYY/MM/DD HH:mm:ss' 这种格式,或者生成文件名的时间段标记,DATE_FORMAT 就是为这类需求准备的。

如果你的数据源是 Unix 时间戳,比如一张表里存的是一堆 10 位秒级数字,那要用的就是 FROM_UNIXTIME 和 UNIX_TIMESTAMP。FROM_UNIXTIME(1710498622) 会把时间戳转成日期时间,UNIX_TIMESTAMP('2024-03-15 10:30:22') 把日期时间转回时间戳。这里必须提醒一句:FROM_UNIXTIME 的显示结果依赖会话时区,默认是服务器时区。做跨时区业务时,前后端对不上时间大多不是转换函数的问题,而是时区没统一,这点我在第 4 节会专门展开。

另外 DATE() 这类函数也可以顺手记一下:它能把包含日期时间的字符串或日期时间提取成 DATE 类型,比如 DATE('2024-03-15 14:30:22') 返回 '2024-03-15',适用于只要日期不要时间的场景。虽然它不是严格意义上的字符串转日期,但日常清洗时经常配合用,能少写一层格式串。

3. 高频实战拆解:六类转换直接抄作业

3.1 斜杠日期加时间:最经典的导入清洗

业务里最常见的源串是 '2024/03/15 10:30:22'。对应的 SQL 是:

SELECT STR_TO_DATE('2024/03/15 10:30:22', '%Y/%m/%d %H:%i:%s'); -- 返回 2024-03-15 10:30:22

如果源串里斜杠不固定,或者日期部分是 '2024/3/5' 这种没补零的,格式串依然写 '%Y/%m/%d %H:%i:%s',MySQL 能容忍一位数月份和日期。这里唯一需要注意的就是别把 %i 和 %s 写反,分钟是 %i 不是 %s,秒才是 %s,我见过不止一个人栽在这里。还有欧洲常见的倒序日期 '15-03-2024 14:30:22',格式串写成 '%d-%m-%Y %H:%i:%s' 就能转,返回结果同样是 2024-03-15 14:30:22。

对批量导入场景,我的做法是先做一次探活查询,确认解析成功率再动手 UPDATE。探活 SQL 可以写成:

SELECT str_col, STR_TO_DATE(str_col, '%Y/%m/%d %H:%i:%s') AS parsed FROM 源表 WHERE str_col IS NOT NULL LIMIT 50;

看到 parsed 列没有 NULL,再正式执行更新。如果某些行返回 NULL,别急着改格式串,先把行内容完整拉出来看,大概率是源数据里混入了 '2024-03-15 10:30:22' 这种横杠版本,或者带着前后空格,导致格式串不匹配。

3.2 紧凑字符串:日志和流水表的最爱

14 位紧凑串 '20240315143022' 的解析是:

SELECT STR_TO_DATE('20240315143022', '%Y%m%d%H%i%s'); -- 返回 2024-03-15 14:30:22

这个格式串里面没有任何分隔符,全部紧贴,MySQL 按说明符逐个截位:%Y 取四位,%m 取两位,%d 取两位,后面时间和日期同理。这种方式在处理日志文件名、流水号、批量导入文件时非常实用,尤其是从运维日志里提取时间戳时,紧凑串占比很高。

8 位紧凑串 '20240315' 更简单,SELECT STR_TO_DATE('20240315', '%Y%m%d'); 就能得到 2024-03-15。如果你只想从 14 位串里取日期,也可以用 LEFT('20240315143022', 8) 先截取再转,但我个人更推荐直接对原串做 STR_TO_DATE,少一步截取就少一个出错机会。还有一个值得注意的点:紧凑串如果要参与字符串排序,本身因为定长所以字典序就是时间顺序,但如果不同记录长度不一致(有的到秒,有的到天),排序就会乱,这种情况务必先统一解析成 DATE 类型再排。

3.3 中文日期和英文月份的解析

中文日期 '2024年3月15日' 的解析格式串是 '%Y年%c月%e日':

SELECT STR_TO_DATE('2024年3月15日', '%Y年%c月%e日'); -- 返回 2024-03-15

关键点是"年""月""日"三个中文字必须原样写在格式串里,它们不是格式符,只是分隔字符。如果月份或日期带了前导零,比如 '2024年03月15日',格式串换成 '%Y年%m月%d日' 更贴切。两种写法能不能互相容忍?不一定,所以最好先确认源串的实际形态,再决定用 %c/%e 还是 %m/%d,不要想当然。

英文月份典型的源串是 'Mar 15, 2024 2:30:22 PM'。对应 SQL:

SELECT STR_TO_DATE('Mar 15, 2024 2:30:22 PM', '%b %e, %Y %h:%i:%s %p'); -- 返回 2024-03-15 14:30:22

这里用 %b 匹配月份缩写,%e 匹配不带前导零的日,%h 匹配 12 小时制小时,%p 匹配 AM/PM,%Y 匹配四位年份。注意逗号必须原样写,%h 和 %p 必须成对出现,否则上午下午就分不清了。对比一下:如果把 %e 误写成 %d,'Mar 5' 这种一位日期依然能解析,因为 %d 也容忍一位;但把 %h 换成 %H 就会出错,因为 '2' 在 12 小时制下不是 24 小时制的 02,语义完全变了。选说明符时,核心原则还是"源串长什么样,格式串就描什么样"。

3.4 时间戳互转:秒级数字和日期时间

Unix 时间戳转日期时间用 FROM_UNIXTIME:

SELECT FROM_UNIXTIME(1710498622); -- 东八区下结果为 2024-03-15 18:30:22 左右

反向操作用 UNIX_TIMESTAMP:

SELECT UNIX_TIMESTAMP('2024-03-15 10:30:22'); -- 返回对应的秒级时间戳

两个函数配合,就能在时间戳表和日期时间表之间来回搬运数据。无论转哪边,都要先搞清楚数据是秒级还是毫秒级。接口返回 13 位毫秒时间戳是常态,MySQL 的 FROM_UNIXTIME 默认按秒处理,13 位直接传进去,日期会给你算到离谱的年份,

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询