数据库CONVERT转换函数详解:类型转换、日期格式化与性能避坑
做数据核对那几年,最怕的不是写不出复杂查询,而是明明逻辑没问题,结果却对不上——后来发现十次里有八次卡在类型上。时间字段导出成一串 20240115,金额在报表里变成了科学计数法,前端传过来的“12.5”跟数据库里的 decimal 一比就报错。这些问题的共同解法,基本都落在同一个函数上:CONVERT 转换函数。它的语法短得可怜,三个参数,看一眼就觉得自己会了,但真到了生产环境,日期格式随语言环境漂移、长度参数省掉被截断、函数包住索引列导致全表扫描,坑一个接一个。这篇内容面向的是已经会写基本查询、但经常被类型问题绊住的人,也适合刚接触数据库、想把这一个函数彻底吃透的新手。我按“想清楚—拆语法—跑场景—排故障—换环境”的顺序把 CONVERT 捋一遍,中间穿插我实际踩过的坑和能直接抄的写法,你看完至少能少加几个班。
1. 先把“转什么”想明白:CONVERT 的定位与选型
1.1 三个同名函数,三套完全不同的规则
“CONVERT”这个名字在不同工具里指的根本不是一回事,这一点必须先分清楚,否则查资料的时候会看到完全矛盾的答案。在 T-SQL 方言的关系型数据库里,CONVERT 是类型转换函数,负责把一种数据类型的值变成另一种数据类型,最常见的组合是日期转字符串、字符串转数值、数值转字符串。在另一款开源数据库里,CONVERT 有两个重载:一个用于字符集转换,一个用于类型转换,前者用得更多。而在电子表格软件里,CONVERT 又是纯粹的计量单位换算函数,把英里换成公里、把摄氏度换成华氏度。
我见过有人拿着电子表格的 CONVERT 文档去查数据库用法,翻了半天说“参数怎么对不上”,其实就是找错了门。所以你在搜索之前,先确认自己手里是什么环境:看函数是不是出现在 SELECT 的字段列表里、参数是不是data_type加expression这种结构,基本就能判断是数据库那一套。这篇内容的主线放在数据库侧的 CONVERT 上,因为它的参数最多、坑也最深,末尾会单独拿一节讲另外两种环境的差异。
1.2 为什么类型转换值得单独拿出来讲
很多人觉得类型转换是个“顺手就做了”的事,数据库不是会自动转吗?确实会,这叫隐式转换,但它只在类型优先级允许的方向上自动发生,而且转换结果不一定是你以为的那个。比如字符串和数字比较时,数据库会把字符串转成数字,这条规则听起来很贴心,可一旦表里混进了'12a'这样的脏数据,整条查询直接报错中断,而不是跳过那一行。
再比如日期。数据库内部存日期用的是二进制数值,你在客户端看到2024-01-15只是显示格式。当这个值被拼进字符串、被导出成 CSV、被传到另一套系统时,格式就变成了“谁转谁说了算”。我做数据同步的时候遇到过一次,源端导出01/02/2024,目标端按dd/mm/yyyy解析,结果一月的数据全跑到了二月。这种问题不会在测试环境暴露,因为测试数据往往是同一批人用同一种格式造的。
显式转换的价值就在这儿:你明确告诉数据库“按这个类型、这个格式来”,结果可预期,出问题也能定位到具体那一行。CONVERT 就是显式转换里最常用的一个,它的第三个参数让你能精确控制输出格式,这是 CAST 做不到的。
1.3 CONVERT 和 CAST 怎么选
这两个函数做的是同一件事,区别在语法和表达能力。CAST 是标准 SQL 的写法,CAST(expression AS data_type),可移植性好,换数据库基本不用改。CONVERT 是 T-SQL 方言的扩展,CONVERT(data_type, expression, style),多了一个 style 参数。
我的选型习惯很粗暴:只要涉及日期格式化,一律用 CONVERT;纯类型转换、不涉及格式,用 CAST。原因很实际——日期格式这件事,用 CAST 只能得到数据库默认的样式,你想要yyyy-mm-dd还是mm/dd/yyyy完全不受控,而 CONVERT 的 style 参数能精确到每一种常见排列组合。
代价是移植性。CONVERT 的 style 参数是方言特性,换到别的数据库就得改写。所以我在写那种要跨环境跑的脚本时,会先把 CONVERT 包一层,或者干脆在应用层做格式化。这个取舍没有标准答案,取决于你的系统是长期只跑一套数据库,还是随时可能迁移。前者用 CONVERT 效率高,后者老老实实用标准写法更省心。
2. 语法拆解:三个参数里藏着大部分坑
2.1 参数顺序与必填项
标准形态是CONVERT(data_type, expression, style)。第一个参数是目标类型,第二个是要转的值,第三个是样式,可省略。顺序跟直觉有点反,因为大部分人习惯“把什么东西转成什么”,而它是“转成什么,什么东西”。写多了就顺手了,但新手期特别容易写反,报错信息又往往是“语法错误”,不太直观。
第一个参数有几个细节要注意。目标类型可以带长度,比如varchar(10)、decimal(18,2)、nvarchar(50)。带不带长度,结果差别很大,后面单独说。另外,目标类型不能是text、ntext、image这类大对象类型,也不能直接转成用户自定义表类型,遇到这些需求得绕路。
第二个参数可以是列、常量、变量、表达式,甚至是另一个函数的返回值。这里有个容易忽略的点:如果 expression 本身是 NULL,转换结果还是 NULL,不会报错。这一点很重要,很多“数据怎么少了”的问题,最后追下去就是 NULL 被转换后依然是 NULL,而后续的字符串拼接把整个结果变成了 NULL。
2.2 长度参数省掉的后果
这是我最想强调的一条。目标类型写成varchar不带长度,默认长度是 30;写成varchar(10),超过 10 个字符的部分会被直接砍掉,而且不报错、不警告。静默截断是最危险的一类问题,因为查询看起来成功了。
我踩过一次:把一列备注从varchar(200)转成varchar(50)做临时表,想着“反正只是中间结果”,结果下游系统拿着被截断的数据做了对账,差异查了两天才定位到。从那以后我养成了一个习惯——目标长度永远比源数据的最长值多留 20%。具体做法是先跑一句SELECT MAX(LEN(col)) FROM tbl,拿到实际最大长度,再往上加余量。
| 写法 | 实际效果 | 风险等级 |
|---|---|---|
CONVERT(varchar, col) | 默认长度 30,超出截断 | 高 |
CONVERT(varchar(50), col) | 最长 50,超出截断 | 中 |
CONVERT(varchar(200), col) | 按实际需要留余量 | 低 |
CONVERT(nvarchar(200), col) | 支持多字节字符 | 低 |
还有一点,varchar和nvarchar别混用。源数据里如果有中文、日文、特殊符号,转成varchar之后可能变成问号,尤其在排序规则不匹配的情况下。稳妥做法是全程nvarchar,代价是占用空间翻倍,但在字符安全面前这点空间不算什么。
2.3 style 参数:日期格式的总开关
style 是 CONVERT 的灵魂。不写 style 的时候,日期转字符串得到的是数据库的默认格式,通常是mon dd yyyy hh:miAM这种,既不美观也不通用。写上 style,输出就变成你指定的形状。
常用的 style 值我整理成了一张表,日常九成场景够用:
| style 值 | 输出格式 | 典型用途 |
|---|---|---|
| 0 或 100 | Jan 15 2024 10:30AM | 默认展示 |
| 1 | 01/15/24 | 短日期 |
| 2 | 24.01.15 | 欧洲习惯 |
| 3 | 15/01/24 | 日在前 |
| 10 | 01-15-24 | 短横线 |
| 11 | 24/01/15 | 年月日斜杠 |
| 12 | 240115 | 纯数字紧凑型 |
| 20 或 120 | 2024-01-15 10:30:00 | 最通用,排序友好 |
| 21 或 121 | 2024-01-15 10:30:00.000 | 带毫秒 |
| 23 | 2024-01-15 | 只要日期 |
| 101 | 01/15/2024 | 美式长日期 |
| 102 | 2024.01.15 | 点分隔 |
| 103 | 15/01/2024 | 英式 |
| 105 | 15-01-2024 | 短横线日在前 |
| 112 | 20240115 | 纯数字八位 |
| 126 | 2024-01-15T10:30:00.000 | ISO 8601 |
| 130 | 15 Jan 2024 10:30AM | 带月份名 |
两个容易被忽视的规律值得记一下。第一,100 及以上是 0 到 99 的四位数年份版本,比如1是两位年份、101是四位年份,做长期数据存档一定要用三位数版本,否则 2099 年之后就不对了。第二,输出是字符串不是日期,转换后这列就不能再用日期函数比较了,WHERE里拿它跟日期常量比,数据库又得把字符串转回去,纯属白折腾。
数值转字符串时 style 是另一套含义:0或省略表示保留两位小数,1表示带千分位和两位小数,2表示四位小数不带千分位,3表示科学计数法。这个细节知道的人不多,但做报表导出时特别实用,不用在应用层再拼一遍格式。
2.4 隐式转换的优先级与意外结果
数据库内部有一套类型优先级,大致是数值高于字符串,日期高于字符串。当两种类型混在一起比较或运算时,优先级低的会被自动抬高。这条规则带来两个后果,一个是方便,一个是灾难。
方便的例子是WHERE int_col = '100',字符串自动转成数字,索引照样能用(因为转换发生在常量侧)。灾难的例子是WHERE varchar_col = 100,这时转换方向反过来了,数据库会把整列的值逐个转成数字再比较。如果这一列里有一条'abc',查询直接报错;即使全部能转,索引也必然失效,因为列被“加工”过了。
我的判断标准很简单:看转换发生在哪一侧。转换常量、转换函数返回值,问题不大;转换列本身,一律当作性能隐患对待。真需要转换列,宁可新建一个计算列并给它建索引,也不要在WHERE里裸写函数。
注意:隐式转换的优先级规则在不同版本、不同兼容级别下会略有差异。升级数据库后如果发现原本跑得好的查询变慢,先检查执行计划里有没有新增的隐式转换标记。
3. 实操演练:六类高频场景的写法
3.1 日期与字符串互转
这是 CONVERT 用得最多的场景。转成字符串的写法已经很熟了:
SELECT CONVERT(varchar(10), order_date, 23) AS date_iso, -- 2024-01-15 CONVERT(varchar(19), order_date, 120) AS datetime_full, -- 2024-01-15 10:30:00 CONVERT(varchar(8), order_date, 112) AS yyyymmdd, -- 20240115 CONVERT(varchar(23), order_date, 126) AS iso8601 -- 2024-01-15T10:30:00.000 FROM orders WHERE order_date >= '2024-01-01';反过来,字符串转日期更容易出事。CONVERT(date, '2024-01-15', 23)里的 style 参数在“转成日期”这个方向上意义不大,因为解析规则主要受SET DATEFORMAT和会话语言影响,style 更多是给“日期转字符串”用的。很多人以为写CONVERT(datetime, '15/01/2024', 103)就能安全解析,实测在某些语言环境下依然会被当成mm/dd解析并报错。
真正稳的做法是用不受区域设置影响的格式,也就是yyyy-mm-dd(style 23 或 120 的输出)或者 ISO 8601 带 T 的格式。这两种写法在几乎所有语言环境下都能正确解析,是我做跨系统对接时的默认选择。
-- 稳:不受语言设置影响 SELECT CONVERT(date, '2024-01-15', 23); SELECT CONVERT(datetime, '2024-01-15T10:30:00', 126); -- 不稳:随会话语言漂移 SELECT CONVERT(datetime, '01/15/2024');3.2 数值与字符串互转的精度控制
金额相关的转换,我基本只用decimal,不碰float。原因是float是近似值,0.1 + 0.2那套经典问题在财务场景里是真的会出事的,对账差一分钱都够你查半天。decimal(18,2)表示总共 18 位、其中 2 位小数,整数部分最多 16 位,够绝大多数业务用。
SELECT CONVERT(decimal(18,2), '1234.567') AS rounded_to_2, -- 1234.57,四舍五入 CONVERT(decimal(18,4), amount) AS keep_4, -- 保留 4 位 CONVERT(varchar(20), amount, 1) AS with_comma, -- 1,234.57 CONVERT(varchar(20), amount, 2) AS four_decimals -- 1234.5670 FROM payments;这里有个细节:从字符串转decimal时,超出小数位数的部分是四舍五入而不是截断。'1234.567'转成decimal(18,2)得到1234.57,不是1234.56。这个行为符合直觉,但在做“向下取整”类业务(比如手续费计算)时要特别小心,最好显式套一层FLOOR或ROUND,别依赖隐式规则。
反向转换也有坑。把decimal转成varchar时如果不带 style,默认是两位小数,但如果原值小数位更多,会丢掉精度。我曾经把decimal(18,6)的汇率转成字符串做拼接,结果六位小数变成了两位,误差在批量换算时被放大了上万倍。正确写法是CONVERT(varchar(30), rate, 2),style 2 保留四位,或者干脆用CAST配合FORMAT函数精确控制。
3.3 定长与变长字符的截断问题
char和varchar的差别不只是“定长变长”这么简单。char(10)会补空格到 10 位,取出来的时候如果没做RTRIM,拼接到别的字符串里会多出一串空格,导致WHERE name = 'abc'匹配失败——因为实际值是'abc '。
-- 危险:char 补空格导致匹配失败 SELECT * FROM users WHERE CONVERT(char(10), user_name) = 'admin'; -- 正确:用 varchar 或显式去空格 SELECT * FROM users WHERE CONVERT(varchar(10), user_name) = 'admin'; SELECT * FROM users WHERE RTRIM(CONVERT(char(10), user_name)) = 'admin';另一个高频问题是长文本转短类型。比如把varchar(max)的日志内容转成varchar(100)做摘要,超过 100 的部分静默丢失。这类需求我一般会先LEN判长度,超长的单独标记出来,而不是让它默默截断。
提示:目标长度一定要大于源数据最大长度,宁可开大一点。空间是便宜的,数据丢了再想找回来,成本高得多。
3.4 NULL 与空串的处理
前面提过,NULL 转换后还是 NULL。这条规则本身没问题,问题出在后续操作上。比如字符串拼接,'编号:' + CONVERT(varchar(20), null_col)结果整个是 NULL,而不是'编号:'。这是标准 SQL 的三值逻辑,但用惯了其他语言的人第一次遇到会懵。
-- 会得到 NULL SELECT '编号:' + CONVERT(varchar(20), remark) FROM t; -- 想要空串,得显式兜底 SELECT '编号:' + ISNULL(CONVERT(varchar(20), remark), '') FROM t; -- 或 SELECT CONCAT('编号:', CONVERT(varchar(20), remark)) FROM t;CONCAT函数会把 NULL 当空串处理,这一点比+友好。我在做数据导出时统一用CONCAT,省掉一堆ISNULL。
空串是另一个麻烦。空串转数值会报错,转日期也会报错。所以清洗脚本里,我通常第一步就把空串归一化成 NULL:
UPDATE t SET amount_str = NULLIF(LTRIM(RTRIM(amount_str)), '') WHERE amount_str IS NOT NULL;NULLIF加两侧TRIM,能干掉空格串、制表符串这类看着像空、实际不为空的脏数据。这一步做完了,后续的CONVERT成功率能提升一大截。
3.5 在 WHERE、JOIN、ORDER BY 里的写法
这三个位置是性能问题的重灾区。先说WHERE:
-- 慢:函数包住列,索引失效 SELECT * FROM orders WHERE CONVERT(varchar(10), order_date, 23) = '2024-01-15'; -- 快:改成范围查询,索引可用 SELECT * FROM orders WHERE order_date >= '2024-01-15' AND order_date < '2024-01-16';原理不复杂。索引是按列的原始值排序的,一旦你给列套上函数,数据库必须对每一行先算一遍函数值再比较,原来的有序结构就用不上了。范围查询等价于“同一天”,但保留了列的原貌,索引能正常发挥。
JOIN场景同理。两张表关联时如果一边是varchar一边是int,隐式转换会让索引失效。我的做法是在建模阶段就把关联键类型统一,实在统一不了,就在子查询里先把小表转好再关联,而不是在ON条件里裸转。
ORDER BY相对简单,但有个反直觉的点:字符串排序和数值排序结果不一样。'10'排在'9'前面,因为按字符逐位比。做编号排序时如果编号是字符串类型,必须显式转成数值:
SELECT * FROM items ORDER BY CONVERT(int, item_no);前提是item_no全部能转成功,混进字母就报错。这种情况我会用TRY_CONVERT,后面单独讲。
3.6 一段完整的数据清洗脚本
把上面这些串起来,给你一段我实际用过的清洗逻辑。场景是:外部导入的订单表,日期、金额、数量全是字符串,还有空串和脏数据。
-- 第一步:空串归一化 UPDATE raw_orders SET order_date_str = NULLIF(LTRIM(RTRIM(order_date_str)), ''), amount_str = NULLIF(LTRIM(RTRIM(amount_str)), ''), qty_str = NULLIF(LTRIM(RTRIM(qty_str)), ''); -- 第二步:只清洗能转成功的行,脏数据先隔离 UPDATE raw_orders SET order_date = TRY_CONVERT(date, order_date_str, 23), amount = TRY_CONVERT(decimal(18,2), amount_str), qty = TRY_CONVERT(int, qty_str); -- 第三步:把转换失败的行挑出来人工看 SELECT id, order_date_str, amount_str, qty_str FROM raw_orders WHERE (order_date_str IS NOT NULL AND order_date IS NULL) OR (amount_str IS NOT NULL AND amount IS NULL) OR (qty_str IS NOT NULL AND qty IS NULL);这段脚本的关键在第二步用了TRY_CONVERT而不是CONVERT。前者转换失败返回 NULL,不会中断整批;后者只要有一条脏数据,整个UPDATE回滚。数据量大的时候,一条脏数据卡住整批,排查起来非常痛苦。先用TRY_CONVERT全量跑一遍,再把失败的行单独拎出来处理,这个模式我在所有数据导入项目里都用。
4. 报错与排查:那些让人卡住半天的现场
4.1 常见报错速查
CONVERT 相关的报错就那么几种,认熟了能省很多时间。
| 报错信息关键片段 | 根本原因 | 处理方式 |
|---|---|---|
| 将 varchar 值转换成 int 时失败 | 字符串含非数字字符 | 先筛出col NOT LIKE '%[^0-9-]%'的行 |
| 从字符串转换日期时失败 | 格式与语言设置不匹配 | 统一用yyyy-mm-dd,或显式指定 style |
| 算术溢出错误 | 数值超出目标类型范围 | 加大精度,如decimal(18,2)改decimal(28,6) |
| 将截断字符串或二进制数据 | 目标长度不足 | 先查MAX(LEN(col))再定长度 |
| 不允许从数据类型转换 | 类型间无合法转换路径 | 走中间类型,如先转varchar再转目标 |
| 转换无效,因为结果超出范围 | 日期越界,如0001-01-01 | 检查源数据是否有默认值或零日期 |
行数越界这条值得多说一句。不少系统用1900-01-01或者0001-01-01表示“空日期”,转成某些日期类型时直接越界。我的处理方式是转换前先过滤:
WHERE order_date_str > '1900-01-02'简单粗暴,但有效。
4.2 日期格式随语言环境漂移
这是我遇到过最隐蔽的一类问题。同一句CONVERT(datetime, '01/02/2024'),在开发机上跑出来是 1 月 2 日,在服务器上是 2 月 1 日,原因是两边的默认语言设置不一样。开发机装的是英文环境,服务器是中文环境,dd/mm和mm/dd的顺序就这么反过来了。
排查方法很简单,查一下当前会话的语言和日期格式:
SELECT @@LANGUAGE AS current_language; SET DATEFORMAT ymd; -- 临时改成 年月日但根治的办法不是改会话设置,而是从源头上禁用有歧义的格式。我在所有涉及日期字符串的地方,一律强制yyyy-mm-dd或 ISO 8601。这两个格式没有歧义,不需要依赖任何环境设置。
还有一类漂移更隐蔽:CONVERT(varchar(10), getdate(), 101)输出01/15/2024,这个101是美式格式,跟语言设置无关,是写死的。但如果有人把 style 写成了1,输出是01/15/24,两位年份在跨世纪的时候会出问题。这种细节只能靠代码规范来约束,我在团队里推的规则是:日期转字符串只允许用 23、112、120、126 这四个 style,其他一律不批。
4.3 函数包列导致的性能塌方
性能问题的表现形式很有迷惑性:查询不报错,就是慢,而且慢得没规律。小表上完全看不出来,数据量一上来,从几百毫秒变成几十秒。
根因还是索引失效。判断方法看执行计划,如果有CONVERT_IMPLICIT这样的标记,或者出现全表扫描而你以为该走索引,基本就是这个问题。
-- 典型反例:对索引列做转换 SELECT * FROM logs WHERE CONVERT(varchar(10), create_time, 120) = '2024-01-15'; -- 改写:保留列原貌 SELECT * FROM logs WHERE create_time >= '2024-01-15' AND create_time < '2024-01-16';还有一种情况是JOIN时的类型不一致。orders.user_id是int,users.id是varchar,关联时数据库会把varchar那侧转成int,两边的索引都用不上,直接退化成嵌套循环。解决办法是在子查询里先把类型统一,或者干脆改表结构。这类问题越早发现越好,等数据量到千万级再改,代价就大了。
注意:不是所有函数包列都会导致索引失效。像
WHERE col + 0 = 100这种在某些优化器下仍能走索引,但不要赌,改写永远是更稳的选择。
4.4 TRY_CONVERT 与兜底策略
TRY_CONVERT是CONVERT的安全版本,语法完全一样,唯一区别是转换失败时返回 NULL 而不是抛异常。这个特性在数据清洗、外部数据接入、用户输入处理这几个场景里几乎是必备的。
SELECT raw_value, TRY_CONVERT(int, raw_value) AS parsed_int, CASE WHEN TRY_CONVERT(int, raw_value) IS NULL AND raw_value IS NOT NULL THEN 'INVALID' ELSE 'OK' END AS status FROM staging_data;注意最后一个 CASE 里的条件,必须加raw_value IS NOT NULL,否则正常的 NULL 值也会被标成INVALID。这个细节我踩过,导致一批合法的空值被误判成脏数据。
TRY_CONVERT也有代价。它是逐行调用的,数据量大时比CONVERT慢一些,而且失败信息被吞掉了,你只知道“转不了”,不知道“为什么转不了”。所以我的用法是:批量清洗用 TRY_CONVERT,定位问题用 CONVERT。先用TRY_CONVERT把能转的转完,再用CONVERT单独跑一遍失败的行,让数据库把具体的错误信息吐出来。
5. 换个环境:MySQL 与表格工具里的转换写法
5.1 MySQL 的 CONVERT 只做字符集和类型两件事
MySQL 里的CONVERT有两个用法,跟 T-SQL 的差别不小。第一种是CONVERT(expr USING charset),做字符集转换,比如把latin1的列转成utf8mb4。第二种是CONVERT(expr, type),做类型转换,等价于CAST(expr AS type)。
-- 字符集转换 SELECT CONVERT(title USING utf8mb4) FROM articles; -- 类型转换,注意语法是逗号不是 AS SELECT CONVERT('123', SIGNED); SELECT CONVERT('2024-01-15', DATE);最大的差异是没有 style 参数。日期格式化得靠DATE_FORMAT(),跟CONVERT是两回事:
SELECT DATE_FORMAT(order_date, '%Y-%m-%d') FROM orders;所以从 T-SQL 迁移到 MySQL 时,CONVERT(varchar(10), d, 23)要改写成DATE_FORMAT(d, '%Y-%m-%d'),不是简单换个函数名就行。反过来迁移也一样。我在做跨库兼容时,通常会在应用层统一格式化日期,数据库只管存取原始类型,这样两边都不用写方言函数,可维护性高很多。
另外 MySQL 的类型转换更宽松,CONVERT('abc', SIGNED)返回 0 而不是报错,这一点跟 T-SQL 的严格模式完全相反。宽松有宽松的好处,不会因为一条脏数据中断批量任务,但代价是错误被静默吞掉,你以为是 0,其实是转换失败。判断方法是配合REGEXP先做格式校验。
5.2 电子表格里的 CONVERT 是单位换算
电子表格软件里的CONVERT跟数据库完全无关,它做的是计量单位换算,语法是CONVERT(number, from_unit, to_unit)。
=CONVERT(100, "km", "mi") -- 100 公里转英里 =CONVERT(37, "C", "F") -- 37 摄氏度转华氏度 =CONVERT(1, "lbm", "kg") -- 1 磅转千克单位代码是区分大小写的,"C"是摄氏度,"c"不是。这一点跟数据库里的 style 数字完全不同,但同样容易写错。常用单位覆盖长度、重量、温度、时间、压强、能量这些类别,做工程计算或者跨境业务换算时挺方便,省得自己记换算系数。
如果你在数据库里也需要做单位换算,别指望CONVERT,得自己维护一张换算系数表,用乘法实现。我做过一个物流项目,涉及公斤和磅、厘米和英寸的换算,最后就是在数据库里建了张unit_conversion表,字段是from_unit、to_unit、factor,查询时 JOIN 一下,比硬编码灵活得多。
5.3 跨库迁移时的改写思路
最后说说迁移。CONVERT 这类方言函数是迁移时最容易被漏掉的部分,因为它在代码里到处都是,而且往往藏在动态拼接的 SQL 字符串里,正则搜索都搜不全。
我的做法是分三步。第一步,把代码里所有CONVERT(的出现位置列出来,按用途分类:日期格式化、数值转换、字符串截断。第二步,日期格式化这类统一抽到一个数据访问层的辅助方法里,数据库方言只在这一个地方出现。第三步,数值和字符串转换优先改成标准CAST,能不用方言就不用。
抽辅助方法这一步看着麻烦,但回报很高。后来我们又换过一次数据库,因为有这层隔离,改的只是一个文件里的十几个方法,业务代码一行没动。如果没有这层封装,几千处CONVERT散落在各个角落,那才叫真正的迁移噩梦。
对我来说,CONVERT 这个函数最值得记住的不是它的语法,而是它暴露出来的思维习惯:类型转换永远不该是“顺手做了”的事,得明确知道源是什么、目标是什么、失败了怎么办。我刚开始写 SQL 的时候图省事,全靠隐式转换,结果线上出过一次整批数据日期错位的事故,事后复盘发现就是一句CONVERT少了 style 参数。从那以后,凡是涉及转换的地方,我都会多写一行判断、多留一个余量,看着啰嗦,但真出事的时候,这几行就是救命的。