背景
在微服务/多租户系统里,经常会出现两张结构相近但用途不同的表:
- SRC_T(运行期完整数据,含租户字段
TENANT_COL
) - DST_T(初始化模板表,无租户字段,字段顺序也不同)
当运营同事在测试环境通过 UI 配置完一整套数据后,需要把生成的 SRC_T INSERT 语句“迁移”到 DST_T,供初始化脚本复用。
手动改字段非常痛苦,于是写了一个小脚本,一条命令搞定。
两张表的区别
字段 | SRC_T | DST_T |
---|---|---|
TENANT_COL | ✅ | ❌ |
STATUS_COL | ✅ | ❌ |
FLAG_A | 1/2 | 统一 2 |
FLAG_B | 0/1 | 统一 1 |
FLAG_C | 0/1 | 统一 0 |
字段顺序 | 24 列 | 22 列 |
脚本功能
- 读取任意
.sql
文件,自动提取所有INSERT INTO … SRC_T …
语句。 - 自动补齐缺失字段,并重排顺序。
CODE_COL
为空时,用ID_COL
的 MD5 生成 32 位唯一值。- 生成可直接执行的
dst.sql
。
源码(单文件,零依赖)
复制即可运行,Python 3.7+
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
convert_generic.py
把 SRC_T 的 INSERT SQL 转成 DST_T 的 INSERT SQL
"""
import re
import hashlib
from pathlib import Path
# 目标表字段顺序(按业务需要调整)
TARGET_COLS = [
'COL_01', 'COL_02', 'COL_03', 'COL_04', 'COL_05', 'COL_06', 'COL_07',
'COL_08', 'COL_09', 'COL_10', 'COL_11', 'COL_12', 'COL_13', 'COL_14',
'COL_15', 'COL_16', 'COL_17', 'COL_18', 'COL_19', 'COL_20', 'COL_21',
'COL_22'
]
# 统一写死的默认值
DEFAULTS = {'COL_15': 2, 'COL_16': 1, 'COL_17': 0}
def md5_32(s: str) -> str:
return hashlib.md5(s.encode()).hexdigest()
def parse_insert(line: str) -> dict:
line = line.strip().rstrip(';')
m = re.search(r'VALUES\s*\((.+)\)', line, re.S | re.I)
if not m:
return None
values = [v.strip().strip("'\"") or None
for v in re.split(r''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', m.group(1))]
# 源表字段顺序(按实际调整)
SRC_COLS = [
'COL_01', 'COL_02', 'COL_03', 'COL_04', 'COL_05', 'COL_06', 'COL_07',
'COL_08', 'COL_09', 'COL_10', 'COL_11', 'COL_12', 'COL_13', 'COL_14',
'STATUS_COL', 'FLAG_C', 'FLAG_A', 'COL_18', 'COL_19', 'COL_20',
'COL_21', 'COL_22', 'COL_23', 'COL_24', 'TENANT_COL', 'FLAG_B'
]
record = dict(zip(SRC_COLS, values))
# 类型转换示例
for k in ['COL_01', 'COL_04', 'COL_05', 'COL_18', 'COL_20']:
if str(record.get(k, '')).lstrip('-').isdigit():
record[k] = int(record[k])
record['COL_11'] = str(record.get('COL_11', '0'))
# 空 CODE 自动生成
if not record.get('COL_02'):
record['COL_02'] = md5_32(str(record['COL_01']))
return record
def convert(src_file: Path, dst_file: Path):
text = src_file.read_text(encoding='utf-8')
inserts = re.findall(
r'INSERT\s+INTO\s+(?:`?\w+`?\.)?`?SRC_T`?\s*\([^)]*\)\s*VALUES\s*\([^)]*\);',
text, re.I | re.S
)
records = [parse_insert(i) for i in inserts if parse_insert(i)]
sql_lines = []
for rec in records:
for k, v in DEFAULTS.items():
rec.setdefault(k, v)
values = []
for col in TARGET_COLS:
val = rec.get(col)
if val is None or str(val).upper() == 'NULL':
values.append('NULL')
elif isinstance(val, int):
values.append(str(val))
else:
values.append(f"'{str(val).replace(\"'\", \"''\")}'")
cols_sql = ', '.join(f'`{c}`' for c in TARGET_COLS)
vals_sql = ', '.join(values)
sql_lines.append(f"INSERT INTO DST_T ({cols_sql}) VALUES ({vals_sql});")
dst_file.write_text('\n'.join(sql_lines), encoding='utf-8')
print(f'✅ 已生成 {dst_file},共 {len(sql_lines)} 条记录。')
if __name__ == '__main__':
import sys
src_path = Path(sys.argv[1]) if len(sys.argv) > 1 else Path('input.sql')
dst_path = src_path.with_name('dst.sql')
convert(src_path, dst_path)
使用方法
- 把脚本保存为
convert_generic.py
。 - 把需要转换的 SQL 文件(例如
input.sql
)放到相同目录。 - 打开终端:
python convert_generic.py input.sql
- 目录下会生成
dst.sql
,直接导入即可:
mysql -h127.0.0.1 -uroot -p target_db < dst.sql
效果示例
输入(SRC_T 片段)
INSERT INTO `db_placeholder`.`SRC_T`
(`COL_01`, `COL_02`, `COL_03`, `COL_04`, `COL_05`, `COL_06`, ...)
VALUES
(1234567890123456000, 'abc123', '示例菜单', 0, 1, 'path_demo', ...);
输出(DST_T)
INSERT INTO DST_T
(`COL_01`, `COL_02`, `COL_03`, `COL_04`, `COL_05`, `COL_06`, ...)
VALUES
(1234567890123456000,'abc123','示例菜单',0,1,'path_demo',...);
小结
- 纯正则解析,不依赖数据库客户端。
- 支持任意库名前缀(
db.SRC_T
)。 - 一键生成,再也不用手工改字段啦!
如需扩展(增删列、过滤条件等),直接改 TARGET_COLS
、SRC_COLS
与 DEFAULTS
字典即可。