ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL日期与字符串转换避坑指南:四大数据库写法对比

SQL日期与字符串转换避坑指南:四大数据库写法对比 做SQL开发的朋友应该都有过这种经历拿到一批数据日期字段是字符串“20240115”这种要跟日期字段做比较报表系统传过来的是“2024-01-15 13:20:33”结果要展示成“2024年1月15日”或者从接口里接到的日期是“15-JAN-24”要转成标准的datetime类型。这些听起来都是小事但真要在SQL里写转换逻辑不同数据库的写法完全是天壤之别。我最早做项目时在SQL Server里写惯了CONVERT后来切到MySQL随手就是CONVERT(date, 20240115)结果直接报错。那时候才意识到日期和字符串的转换看起来是个API调用实际上背后牵扯到格式串语法、隐式转换规则、语言环境、排序规则这些容易忽略的细节。这篇文章就把我自己在SQL Server、MySQL、Oracle、PostgreSQL几种常见数据库里的实操经验整理出来包括每一步怎么写、为什么会报错、怎么排查希望能帮你少踩几个坑。1. 日期与字符串互转为什么这个操作会出现在几乎所有项目里1.1 从实际场景看转换需求日期与字符串的转换最常见的是这几个场景。一个是报表与查询条件。前端传过来的是字符串比如用户在页面上选择了“2024-01-01”到“2024-01-31”那SQL收到的基本是字符串格式。你要么在SQL里转成日期再比较要么在程序里转好再拼SQL。很多团队喜欢直接拿字符串跟日期字段比较在MySQL里可能没问题但在某些数据库里会因为隐式转换规则不同而出现不可预测的行为。另一个是数据迁移与清洗。不同系统的数据格式不一样A系统导出的是“2024/01/15”B系统是“20240115”C系统是“Jan 15 2024”。数据到了数仓里第一步就是把这些乱七八糟的格式统一成标准日期类型。这个场景最容易出问题因为字符串转日期一旦遇到无法解析的格式直接报错不说还会让整个批处理任务中断。还有一个是展示与接口对接。数据库里存的是datetime但接口返回给前端要的是“yyyy-MM-dd”这种字符串或者反过来。有些公司要求所有接口日期字段统一用ISO 8601格式这样就省去了前端解析的麻烦。转换在这个场景里是业务规范的一部分。1.2 转换本质上是三件事很多人混淆了“格式化”和“解析”其实它们是相反的两个方向。日期转字符串格式化通常叫Format把一个datetime类型的值按照你指定的格式模板输出成字符串。字符串转日期解析通常叫Parse把一个字符串按照你指定的格式模板读成datetime类型的值。隐式转换数据库在比较、计算、插入时自动做的转换这个最危险因为你没有显式指定格式完全依赖数据库的默认规则。我们这篇文章主要讨论前两种。理解这一点很关键因为很多报错和诡异现象都源于“我以为我写了日期其实数据库按字符串处理了”或者反过来。1.3 为什么转换看起来简单实际却容易翻车核心原因有两个。第一日期格式字符串在不同数据库里的语法不一样。SQL Server用的是“yyyy-MM-dd HH:mm:ss”但有坑比如“yyyy”和“YYYY”在某些情况下语义不同MySQL用的是“%Y-%m-%d %H:%i:%s”这个“%Y”是全靠记忆的Oracle用的是“YYYY-MM-DD HH24:MI:SS”又不一样。你在一门数据库里写熟了换到另一门代码基本上要重写。第二日期字符串的语义存在歧义。“01/02/2024”到底是1月2日还是2月1日这取决于语言环境、日期格式设置和数据库的默认参数。所以解析字符串转日期时最稳妥的做法永远是显式指定格式而不是依赖数据库猜。这一点在后面会反复提到。2. 日期转字符串主流数据库的写法对比2.1 SQL ServerCONVERT与FORMAT并用SQL Server里最常用的是CONVERT和FORMAT函数。用CONVERT转成标准格式核心写法是给一个style参数-- 转为 yyyy-mm-dd hh:mi:ss(24小时制) SELECT CONVERT(VARCHAR(19), GETDATE(), 120) AS date_str; -- 转为 yyyy-mm-dd SELECT CONVERT(VARCHAR(10), GETDATE(), 23) AS date_str; -- 转为 yyyymmdd SELECT CONVERT(VARCHAR(8), GETDATE(), 112) AS date_str;这里style参数就是那个数字120、23、112你得背下来。常用的其实就那么几个120是“yyyy-mm-dd hh:mi:ss”23是“yyyy-mm-dd”112是“yyyymmdd”108是“hh:mi:ss”101是“mm/dd/yyyy”。如果只是给程序对接120和112基本够用。到了SQL Server 2012以后多了FORMAT函数写法跟.NET很像SELECT FORMAT(GETDATE(), yyyy-MM-dd HH:mm:ss) AS date_str; SELECT FORMAT(GETDATE(), yyyy年MM月dd日) AS date_str;FORMAT用起来很直观但它走的是.NET的格式化机制性能比CONVERT差不少。我在一个百万行级别的查询里试过FORMAT一多响应时间明显变长。所以我的建议是能用CONVERT就用CONVERT实在需要自定义格式、比如带中文的“2024年01月15日”再用FORMAT。2.2 MySQLDATE_FORMAT与DATE操作MySQL里日期转字符串的核心函数是DATE_FORMAT。-- 转为 yyyy-mm-dd SELECT DATE_FORMAT(NOW(), %Y-%m-%d) AS date_str; -- 转为 yyyy-mm-dd hh:mm:ss SELECT DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s) AS date_str; -- 转为 yyyymmdd SELECT DATE_FORMAT(NOW(), %Y%m%d) AS date_str;注意MySQL的格式符跟SQL Server完全不同%Y是四位的年%y是两位年%m是两位月%c是月份数字不带前导0%d是两位日%e是日数字不带前导0%H是24小时制的小时%h是12小时制%i是分钟%s是秒。这个“%i”我一开始特别不习惯总写成%M后来才记住%M在MySQL里是月份名称比如“January”。MySQL还有一个很常用的做法就是直接拼接DATE_FORMAT的结果到查询里做分组。我做过一个按小时统计订单量的需求SELECT DATE_FORMAT(order_time, %Y-%m-%d %H:00:00) AS hour_bucket, COUNT(*) FROM orders WHERE order_time 2024-01-01 00:00:00 AND order_time 2024-02-01 00:00:00 GROUP BY hour_bucket;这样把时间对齐到小时做报表很方便。还有DATE函数SELECT DATE(NOW()); -- 2024-01-15 SELECT DATE_FORMAT(NOW(), %Y/%m/%d); -- 2024/01/152.3 Oracle与达梦TO_CHAR是绝对主力Oracle里日期转字符串基本就是TO_CHAR-- 转为 yyyy-mm-dd hh24:mi:ss SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS) FROM DUAL; -- 转为 yyyy-mm-dd SELECT TO_CHAR(SYSDATE, YYYY-MM-DD) FROM DUAL; -- 转为 yyyymmdd SELECT TO_CHAR(SYSDATE, YYYYMMDD) FROM DUAL;注意Oracle的格式串里月份是MM分钟是MI小时是HH2424小时制或HH12小时制。秒是SS。这个跟SQL Server很接近但容易写错的是分钟SQL Server里分钟是MI吗其实CONVERT靠style参数不需要写格式串一旦你用FORMAT又是.NET的语法。所以Oracle老手到了SQL Server用FORMAT(yyyy-MM-dd HH:mm:ss)看着没问题但结果是分钟会被当成mm也能输出因为.NET里mm代表分钟而Oracle里MM代表月份——就是这个差异导致一批人跨数据库写代码时非常痛苦。在达梦数据库里TO_CHAR的行为跟Oracle基本一致SELECT TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS);如果你的公司用的是国产数据库但没统一规范这类兼容写法要格外当心。2.4 PostgreSQLTO_CHAR与灵活的类型转换PostgreSQL里日期转字符串同样是TO_CHAR-- 转为 yyyy-mm-dd hh24:mi:ss SELECT TO_CHAR(NOW(), YYYY-MM-DD HH24:MI:SS); -- 转为 yyyy-mm-dd SELECT TO_CHAR(NOW(), YYYY-MM-DD); -- 转为 yyyymmdd SELECT TO_CHAR(NOW(), YYYYMMDD);PostgreSQL还有一个特性类型转换非常灵活。如果你只是想拿日期部分SELECT NOW()::date; SELECT NOW()::text;但要注意直接转文本得到的格式是依赖于系统配置的可能是“2024-01-15”也可能是“2024/01/15”。生产环境里我一般不会依赖这种隐式格式而是明确用TO_CHAR指定格式。2.5 四种数据库日期转字符串速查对照需求SQL ServerMySQLOracle/达梦PostgreSQLyyyy-mm-ddCONVERT(VARCHAR(10), GETDATE(), 23)DATE_FORMAT(NOW(), %Y-%m-%d)TO_CHAR(SYSDATE, YYYY-MM-DD)TO_CHAR(NOW(), YYYY-MM-DD)yyyymmddCONVERT(VARCHAR(8), GETDATE(), 112)DATE_FORMAT(NOW(), %Y%m%d)TO_CHAR(SYSDATE, YYYYMMDD)TO_CHAR(NOW(), YYYYMMDD)yyyy-mm-dd hh:mi:ssCONVERT(VARCHAR(19), GETDATE(), 120)DATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s)TO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS)TO_CHAR(NOW(), YYYY-MM-DD HH24:MI:SS)年-月-日 中文FORMAT(GETDATE(), yyyy年MM月dd日)DATE_FORMAT(NOW(), %Y年%m月%d日)TO_CHAR(SYSDATE, YYYY年MM月DD日)TO_CHAR(NOW(), YYYY年MM月DD日)这个表格建议收藏一下用到不同数据库的时候直接对号入座。3. 字符串转日期解析的规律与容错设计3.1 SQL Server字符串转日期的推荐写法SQL Server里字符串转日期用CONVERT给了style参数才能稳定解析-- 把 2024-01-15 13:20:33 转为 datetime SELECT CONVERT(DATETIME, 2024-01-15 13:20:33, 120); -- 把 20240115 转为 datetime SELECT CONVERT(DATETIME, 20240115, 112); -- 把 01/15/2024 转为 datetime SELECT CONVERT(DATETIME, 01/15/2024, 101);注意SQL Server在没有显式style时解析月份和日期靠的是会话的语言设置。比如SET LANGUAGE US_ENGLISH那“01/02/2024”会被当作1月2日如果是简体中文设置行为就可能不一样。所以我的习惯是写CONVERT时永远带style参数哪怕看起来啰嗦至少结果可控。CAST也能转SELECT CAST(2024-01-15 AS DATETIME);但CAST没法指定格式只能按数据库默认规则解析。我在处理复杂字符串时基本不用CAST来做日期解析宁可先用字符串函数整理成标准格式再用CONVERT带style转。一个实战技巧如果外部给的日期字符串格式很乱比如可能带斜杠、可能带横杠、可能全数字我一般先做一层清洗用REPLACE把斜杠替换成横杠再根据长度判断是不是要拼上时间部分最后统一交给CONVERT。这种清洗虽然SQL写起来长一点但比让数据库猜要稳得多。3.2 MySQL的STR_TO_DATEMySQL里字符串转日期核心函数是STR_TO_DATE-- 解析标准格式 SELECT STR_TO_DATE(2024-01-15 13:20:33, %Y-%m-%d %H:%i:%s); -- 解析纯数字 SELECT STR_TO_DATE(20240115, %Y%m%d); -- 解析带斜杠的格式 SELECT STR_TO_DATE(2024/01/15, %Y/%m/%d);STR_TO_DATE的格式符跟DATE_FORMAT是一套对照起来记就好。如果解析失败会返回NULL注意是NULL不是报错。我遇到过这种情况查询结果莫名其妙少了某几行排查很久才发现是STR_TO_DATE返回了NULL关联条件直接失效。还有一个容易犯的错用STR_TO_DATE解析“2024-1-5”这种不带前导零的字符串。格式串里写成%Y-%m-%dMySQL大概率也能解析成功因为%m、%d允许1到2位。但如果你把格式串写成%Y-%m-%d且字符串里是“2024-1-5”它也是能解析的这点比Oracle宽松。不过我还是建议在程序侧统一输出标准格式少给自己找麻烦。3.3 Oracle的TO_DATEOracle里字符串转日期的标准函数是TO_DATE-- 解析标准格式 SELECT TO_DATE(2024-01-15 13:20:33, YYYY-MM-DD HH24:MI:SS) FROM DUAL; -- 解析纯数字 SELECT TO_DATE(20240115, YYYYMMDD) FROM DUAL; -- 解析带斜杠 SELECT TO_DATE(2024/01/15, YYYY/MM/DD) FROM DUAL;TO_DATE在解析上有个习惯跟其他数据库不太一样如果字符串多余的数据没有在格式串里体现会直接报ORA-01861“文字与格式字符串不匹配”。比如你用TO_DATE(2024-01-15 13:20:33, YYYY-MM-DD)它会报错因为字符串里多了“ 13:20:33”没被消费掉。在MySQL里同样是STR_TO_DATE多的部分反而可能被忽略。这种宽容度的差异导致从MySQL切到Oracle的朋友经常被ORA-01861折磨得一肚子火。解决办法有两个要么在格式串里把所有内容都写全要么先把字符串用SUBSTR截成需要的部分再转换。我写存储过程时一般用后者因为数据来源不可控时截断比解析更安全。3.4 PostgreSQL的TO_DATE与类型转换PostgreSQL里字符串转日期可以用TO_DATE或者直接类型转换-- 使用TO_DATE SELECT TO_DATE(2024-01-15, YYYY-MM-DD); SELECT TO_DATE(20240115, YYYYMMDD); -- 直接类型转换 SELECT 2024-01-15::date; SELECT 2024-01-15 13:20:33::timestamp;PostgreSQL的TO_DATE返回的是date类型TO_TIMESTAMP返回的是timestamp类型。有一个细节TO_DATE(20240115, YYYYMMDD)的结果是2024-01-15但如果你写TO_DATE(20240115, YYYY-MM-DD)它是会报错的因为格式串里的横杠在源字符串里没有。这跟其他数据库的逻辑一致格式串只是告诉数据库怎么去读不是给字符串化妆的。所以在跨库迁移时我看到很多人喜欢把格式串写得“格式很好”比如YYYY-MM-DD但源数据是20240115那必然出错。正确的姿势是先看字符串长什么样再写对应的格式串。3.5 字符串转日期最容易出错的三个点字符串转日期这块我总结三个高频坑。第一个是月份和日期的顺序歧义。01/02/2024在不同数据库、不同语言环境下解析结果可能完全不同。解决办法只有一个显式指定格式或者先对字符串做标准化处理。第二个是格式串与字符串对不上。源字符串里是横杠格式串里写的是斜杠那必然报错。建议先从SELECT里肉眼检查几个样本确认分隔符、位数、是否有前导零。第三个是解析失败与业务异常的处理。像MySQL返回NULLOracle直接报错PostgreSQL报错SQL Server有时能自动纠正。生产环境里最怕的不是报错而是没有报错但数据被静默丢弃或转换成了错误的值。所以我在做数据清洗时会额外写一步校验解析后的值是否在合理日期范围内比如月份不在1到12之间就标记异常。4. 格式字符与隐式转换那些看着正常却暗藏问题的细节4.1 大小写的语义差异YYYY与yyyy在SQL Server的FORMAT里都代表四位年份但在某些数据库里MON与Mon、MI与mi的含义不同。这个我重点想说的是Oracle和PostgreSQL的格式串毫米的MM是月份分钟的MI是分钟但SQL Server的FORMAT用的是.NET格式mm是分钟MM是月份而且SQL Server的FORMAT不区分大小写时不严谨——这就导致了跨库代码审查时同一串“yyyy-MM-dd HH:mm:ss”在SQL Server里能输出正确格式在Oracle里就变成“2024-01-15 13:20:33”里分钟位置显示正常但月份位置可能被误读。处理办法写代码前先确定目标数据库然后按目标数据库的官方格式串文档写清楚。最好在团队规范里固定每种数据库的格式串模板禁止成员自行发明。4.2 语言环境与服务器默认设置字符串转日期时如果没显式指定格式数据库默认怎么解析跟你服务器或会话的语言设置强相关。SQL Server里的SET LANGUAGEOracle里的NLS_TERRITORY和NLS_DATE_FORMATMySQL里的lc_time_names都会影响结果。比如Oracle的NLS_DATE_FORMAT如果设置为YYYY-MM-DD HH24:MI:SS那你直接写TO_DATE(2024-01-15)而不带格式串在某些版本里也可能成功但换一台服务器NLS_DATE_FORMAT变成DD-MON-YY同样的SQL就会报错。这给我们的教训是不要在SQL里依赖会话参数来解析日期一定要在语法层面把格式写死。否则同样的代码在开发环境跑得好好的到生产环境就出问题而且这种问题很难排查因为它不是逻辑错误而是环境差异。4.3 隐式转换与索引失效这一点非常重要当列的日期类型和查询条件的字符串类型不一致时数据库会做隐式转换。有时候隐式转换会导致索引失效。比如在MySQL里SELECT * FROM orders WHERE order_date 2024-01-15;这里order_date是datetime类型字符串2024-01-15会被隐式转换为日期在绝大多数情况下不影响索引使用。但如果你反过来SELECT * FROM orders WHERE DATE_FORMAT(order_date, %Y-%m-%d) 2024-01-15;那order_date被函数包裹后索引大概率失效全表扫描。这个不算严格的“转换”问题但跟我今天讲的主题直接相关在写日期条件时能直接在SQL里把字符串传给日期列比较就不要自己先转成字符串再比较。你的程序里看到的“转换”在数据库层面可能就是一次索引失效。同理在SQL Server里SELECT * FROM orders WHERE CONVERT(VARCHAR(10), order_date, 120) 2024-01-15;这个写法把order_date转成字符串再比较索引几乎必然失效。正确写法是SELECT * FROM orders WHERE order_date 2024-01-15 00:00:00 AND order_date 2024-01-16 00:00:00;或者干脆直接SELECT * FROM orders WHERE order_date 2024-01-15 AND order_date 2024-01-16;这就既用上了索引又保持结果正确。这类细节在实际排查慢SQL时特别有用。5. 常见问题排查实录与避坑经验5.1 典型报错速查表我整理了一个报错速查表都是实际项目里遇到过的高频问题建议收藏。场景数据库报错信息处理方式字符串多出时间部分OracleORA-01861: literal does not match format string格式串写全或先截断字符串月份或日期超出范围各库ORA-01847 / conversion failed / value out of range检查源数据是否有2月30日这种脏值分隔符不匹配各库conversion failed确认字符串里的斜杠/横杠与格式串一致CONVERT不带styleSQL Serverconversion failed when converting date and/or time from character string补上style参数如120、112MySQL解析返回NULLMySQL无报错但结果是NULL检查STR_TO_DATE的格式串单独SELECT验证PostgreSQL类型转换失败PostgreSQLinvalid input syntax for type date: 2024/01/15使用TO_DATE并指定格式串日期字符串带有中文各库转换失败先用REPLACE去掉中文或改成全数字格式这个表里最坑的是MySQL那条它不报错只返回NULL。如果SQL里用了STR_TO_DATE去关联查不到数据时不要急着怀疑业务逻辑先单独跑一下SELECT STR_TO_DATE(脏数据, %Y-%m-%d)一看是NULL问题就清楚了。5.2 日期边界与时区问题比格式更要命的坑日期转换还容易栽在时区上。特别是做国际化项目时同一个时间戳在UTC和东八区解析出来的日期不一样。SQL Server里GETDATE是服务器本地时间GETUTCDATE是UTC时间。MySQL里NOW()取会话时区UTC_TIMESTAMP取UTC。Oracle里SYSDATE是操作系统时间SYSTIMESTAMP带时区CURRENT_TIMESTAMP是会话时区。有一次做数据统计业务方要求按“东八区自然日”统计但数据库服务器设置的时区是UTC结果每天统计出来的数据都少了几小时。最后排查到原因用UTC_TIMESTAMP去截断日期当然跟业务自然日对不上。修复方案就是在转换前先做时区偏移或者统一在应用层把时间转换成目标时区字符串再传下去。另一个边界问题是日期精度。datetime和datetime2在SQL Server里的精度不同timestamp和datetime在MySQL里精度也不同。如果有跨凌晨的业务比如“2024-01-15 00:00:00”到“2024-01-15 23:59:59”这种写法建议用大于等于开始日期且小于次日零点的方式避免丢掉23:59:59.999这种边界数据。5.3 与外部程序交互时的格式约定写SQL转换时真正让我觉得省心的是提前跟上下游约定好统一格式。比如接口层我一般要求所有日期字段统一输出为“yyyy-MM-dd HH:mm:ss”这个格式既方便人类阅读也方便程序解析。数据文件导入导出CSV里统一用“yyyy-MM-dd”如果有时间再加“HH:mm:ss”不要用斜杠不要用中文不要用“JAN”这类缩写。因为一旦混合多种格式解析代码就要写一堆分支判断。有一次对接一个老系统导出的数据里日期是“2024-01-15 13:20:33.123456”带6位微秒。MySQL的datetime默认精度是0位导入时如果放到datetime字段微秒会被丢弃。后来发现可以定义字段为datetime(6)或者数据导入时用STR_TO_DATE按%Y-%m-%d %H:%i:%s.%f解析。这个经验就是接外部数据时不要假设对方的格式跟你完全一致。5.4 最佳实践一套稳妥的转换策略最后我把摸爬滚打这几年总结出的转换策略分享出来。第一能用标准格式就用标准格式。日期时间字符串的标准格式是“YYYY-MM-DD HH24:MI:SS”所有主流数据库都认识不需要额外指定格式也能大概率正确解析。自定义格式只用于展示不用于存储和传输。第二解析字符串转日期时永远显式给出格式串。哪怕这个格式串看起来默认也能解析也写出来。这既是对读者的提示也是防止会话参数改变导致行为漂移。第三清洗数据时先验证可解析性。写查询之前先把可疑数据用正则或LIKE筛出来看一眼。比如长度不对、包含了字母、日期明显不合理这些都是数据质量问题应该在进入SQL转换之前就处理好。第四转换操作不要放在WHERE条件里的列上。像前面说的对列做转换会破坏索引。宁可把条件另一边转换也不要动列。第五把不同数据库的转换代码封装成可复用的视图或函数。比如在项目里统一提供一个日期格式化函数团队里的人只调用这个函数而不是每个人自己写DATE_FORMAT或TO_CHAR。这样一旦遇到格式问题只需要改一处。我在实际项目里还养成了一个习惯写转换脚本时先做一次小样本测试确认转换结果跟预期完全一致再放全量跑。特别是涉及数据清洗的SQL哪怕转换逻辑看起来沒问题也一定要先跑SELECT版本看看有没有NULL、有没有乱值、有没有时区偏移。毕竟日期这东西一旦错位报表上的数据看起来“像是正确的”实际却差了几个小时甚至几天这种隐性错误比报错更可怕。
返回列表