如何在MariaDB SQL转储中替换指定empId对应JSON的salary值
问题说明
MariaDB中存在一张5列的表,其中1列为存储JSON格式数据的longtext(压缩字符串)类型字段。现有该表的SQL转储文件,需要根据JSON结构中empId属性的取值,修改对应JSON结构内的salary属性值,示例转储内容如下:
INSERT INTO Employee VALUES (1,"ram","1243","19-03-14",{"name":"ram",age:"23","empId":"1234","address":{"city":{"name":"",.*},"country":{"name":"",.*}},"gender":"male","hobbies":"travel","salary":"40000","qualification":"BE","marrital-status":"married",.*), (2,"komal","1243","19-03-14",{"name":"komal",age:"21","empId":"1534","address":{"city":{"name":"",.*},"country":{"name":"",.*}},"gender":"male","hobbies":"music","salary":"30000","qualification":"BE","marrital-status":"married",.*), (3,"ramya","1243","19-03-14",{"name":"ramya",age:"22","empId":"1754","address":{"city":{"name":"",.*},"country":{"name":"",.*}},"gender":"male","hobbies":"travel","salary":"40000","qualification":"BE","marrital-status":"married",.*), (4,"raj","1243","19-03-14",{"name":"raj",age:"23","empId":"1364","address":{"city":{"name":"",.*},"country":{"name":"",.*}},"gender":"male","hobbies":"playing","salary":"40000","qualification":"BE","marrital-status":"married",.*);
现有一份CSV文件存储empId与调整后薪资的映射关系,原计划遍历CSV文件,在转储文件中根据empId匹配替换对应salary取值,尝试编写的sed正则替换命令如下:
:%s/\("empId":"1243"\),\(.*\),\("address":{"city":{\),\(.*\),\(}\),\("country":{\),\(.*\),\(}}\),\(.*\),"salary":[0-9\"]*/\1,\2,\3,\4,5,\6,\7,\8,\9,"salaray":"50000"/
执行后返回报错:
E872: (NFA regexp) Too many '(' E51: Too many \( E476: Invalid command
需要通过sed或其他Shell脚本方案,解析转储文件中的JSON内容完成目标属性值修改。
解决方案
原正则报错核心原因有三点:
- 默认sed使用的基础正则(BRE)最多仅支持9个捕获组,编写的分组数量超过上限触发报错;
- 正则中大量使用贪婪匹配
.*,极易出现跨记录匹配的问题,替换结果不可控; - 替换字符串中将
salary错拼为salaray,即使正则执行成功也会生成错误字段。
针对不同场景可选以下方案:
方案1:Perl一行式批量处理(推荐)
Perl正则支持非贪婪匹配,无捕获组数量限制,可直接读取CSV映射完成全量替换,无需逐个手动指定empId。
假设薪资映射CSV格式为empId,new_salary,示例内容如下:
1234,55000 1534,38000 1754,42000 1364,48000
直接执行以下命令即可生成替换后的转储文件:
perl -ane ' BEGIN { # 读取薪资映射表存入哈希 open my $fh, "<", "salary_map.csv" or die $!; while (<$fh>) { chomp; my ($eid, $sal) = split /,/; $salary_map{$eid} = $sal; } close $fh; } # 非贪婪匹配同一块JSON内的empId和后续salary,完成替换 s/("empId":"(\d+)")(.*?)"salary":"?\d+"?/$1$3"salary":"$salary_map{$2}"/g; print; ' dump.sql > updated_dump.sql
- 非贪婪匹配
.*?只会匹配当前JSON块内的内容,不会跨多条记录误替换; - 正则兼容
salary值带引号、不带引号两种格式。
方案2:简化sed正则(仅适合单条临时替换)
如果仅需要临时替换单个empId对应的薪资,可以简化正则减少捕获组数量,不需要拆分无需修改的固定片段:
# 将empId为1234的记录薪资替换为50000 sed 's/\("empId":"1234".*"salary":"\)[0-9]*/\150000/g' dump.sql
注意:sed默认.*为贪婪匹配,如果单行内存在多条员工记录,会出现匹配错位,不适合批量处理场景。
方案3:数据库原生JSON函数处理(数据量大/JSON结构复杂时首选)
如果转储文件数据量很大、JSON结构嵌套复杂,正则匹配存在边界风险,可以直接将转储导入临时库,用MariaDB原生JSON函数更新后重新导出,准确率100%:
-- 1. 先在临时库导入原表结构和数据 -- 2. 导入薪资映射CSV到临时表 LOAD DATA INFILE 'salary_map.csv' INTO TABLE salary_map FIELDS TERMINATED BY ',' (empId, new_salary); -- 3. 关联更新JSON字段 UPDATE Employee e JOIN salary_map m ON JSON_UNQUOTE(JSON_EXTRACT(e.json_col, '$.empId')) = m.empId SET e.json_col = JSON_SET(e.json_col, '$.salary', m.new_salary); -- 4. 重新导出转储文件即可
内容的提问来源于stack exchange,提问作者Abirami Ramkumar
相关产品推荐
相关产品推荐

