初始化提交
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/.trae
|
||||
/out
|
||||
/sql
|
||||
104
README.md
Normal file
104
README.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# SQL差异比较工具
|
||||
|
||||
## 功能描述
|
||||
|
||||
该工具用于比较两个由mysqldump生成的SQL文件,找出表结构差异,并自动生成update.sql脚本。
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 同步两个数据库的结构差异
|
||||
- 项目A可能是旧数据库,项目B创建了新的字段和表
|
||||
- 当给项目A更新服务端时,确保数据库结构一致
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **读取SQL文件**:从`sql`文件夹中获取两个SQL文件
|
||||
2. **解析表结构**:提取每个SQL文件中的表结构信息
|
||||
3. **比较差异**:找出新增的表和新增的字段
|
||||
4. **生成脚本**:根据差异生成对应的`CREATE TABLE`和`ALTER TABLE`语句
|
||||
5. **输出结果**:将生成的SQL脚本保存到`out/update.sql`文件中
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 准备SQL文件
|
||||
|
||||
使用`mysqldump`命令生成两个数据库的结构文件:
|
||||
|
||||
```bash
|
||||
# 导出旧数据库结构
|
||||
mysqldump -u root -p -d old_database > sql/old.sql
|
||||
|
||||
# 导出新数据库结构
|
||||
mysqldump -u root -p -d new_database > sql/new.sql
|
||||
```
|
||||
|
||||
### 2. 运行脚本
|
||||
|
||||
```bash
|
||||
python sql_diff.py
|
||||
```
|
||||
|
||||
### 3. 查看结果
|
||||
|
||||
生成的`update.sql`文件将保存在`out`文件夹中,包含:
|
||||
- 新增表的`CREATE TABLE`语句
|
||||
- 新增字段的`ALTER TABLE`语句
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
sql_utils/
|
||||
├── sql/ # 存放待比较的SQL文件
|
||||
├── out/ # 存放生成的update.sql文件
|
||||
├── sql_diff.py # 主程序文件
|
||||
└── README.md # 项目说明文档
|
||||
```
|
||||
|
||||
## 代码说明
|
||||
|
||||
### 主要函数
|
||||
|
||||
1. **get_sql_files()**:获取sql文件夹中的SQL文件
|
||||
2. **parse_sql_file(file_path)**:解析SQL文件,提取表结构信息
|
||||
3. **compare_tables(old_tables, new_tables)**:比较两个表结构字典,找出差异
|
||||
4. **generate_update_sql(old_file, new_file, diff)**:根据差异生成update.sql语句
|
||||
5. **main()**:主函数,协调各个步骤
|
||||
|
||||
### 依赖
|
||||
|
||||
- Python 3.x
|
||||
- 标准库:os, re, datetime
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保`sql`文件夹中只有两个SQL文件
|
||||
2. 建议使用文件名中的`old`关键字标识旧数据库文件,方便程序自动识别
|
||||
3. 该工具仅比较表结构,不比较数据
|
||||
4. 生成的SQL脚本需要人工审核后再执行
|
||||
|
||||
## 示例
|
||||
|
||||
### 输入
|
||||
|
||||
`sql/old.sql`(旧数据库结构)
|
||||
`sql/new.sql`(新数据库结构)
|
||||
|
||||
### 输出
|
||||
|
||||
```sql
|
||||
-- SQL差异更新脚本
|
||||
-- 生成时间: 2023-01-01 12:00:00
|
||||
-- 比较文件: old.sql (旧) -> new.sql (新)
|
||||
|
||||
-- 新增表
|
||||
|
||||
CREATE TABLE `new_table` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(255) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- 新增字段
|
||||
|
||||
ALTER TABLE `existing_table` ADD COLUMN `new_field` varchar(255) DEFAULT NULL;
|
||||
```
|
||||
214
sql_diff.py
Normal file
214
sql_diff.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
SQL差异比较工具
|
||||
用于比较两个由mysqldump生成的SQL文件,找出表结构差异,并生成update.sql
|
||||
|
||||
使用场景:
|
||||
- 同步两个数据库的差异
|
||||
- 项目A可能是旧数据库,项目B创建了新的字段和表
|
||||
- 当给项目A更新服务端时,确保数据库结构一致
|
||||
|
||||
使用方法:
|
||||
1. 将两个SQL文件(由mysqldump -u root -p -d database > dump.sql生成)放在sql文件夹中
|
||||
2. 运行脚本:python sql_diff.py
|
||||
3. 生成的update.sql文件将放在out文件夹中
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def get_sql_files():
|
||||
"""
|
||||
获取sql文件夹中的SQL文件
|
||||
:return: 包含两个SQL文件路径的列表
|
||||
"""
|
||||
sql_dir = "sql"
|
||||
# 检查sql文件夹是否存在
|
||||
if not os.path.exists(sql_dir):
|
||||
print(f"错误:{sql_dir}文件夹不存在")
|
||||
return []
|
||||
|
||||
# 获取文件夹中的所有.sql文件
|
||||
sql_files = [os.path.join(sql_dir, f) for f in os.listdir(sql_dir) if f.endswith('.sql')]
|
||||
|
||||
# 确保只有两个SQL文件
|
||||
if len(sql_files) != 2:
|
||||
print(f"错误:{sql_dir}文件夹中需要有且只有两个SQL文件,当前有{len(sql_files)}个")
|
||||
return []
|
||||
|
||||
return sql_files
|
||||
|
||||
|
||||
def parse_sql_file(file_path):
|
||||
"""
|
||||
解析SQL文件,提取表结构信息
|
||||
:param file_path: SQL文件路径
|
||||
:return: 表结构字典,格式为 {表名: {字段名: 字段定义}}
|
||||
"""
|
||||
tables = {}
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 匹配CREATE TABLE语句
|
||||
table_pattern = re.compile(r'CREATE TABLE `(.*?)`\s*\(([\s\S]*?)\) ENGINE=', re.MULTILINE)
|
||||
matches = table_pattern.findall(content)
|
||||
|
||||
for table_name, table_content in matches:
|
||||
# 解析字段定义
|
||||
field_pattern = re.compile(r'\s*`(.*?)`\s+(.*?)(?:,|\s*$)', re.MULTILINE)
|
||||
fields = {}
|
||||
|
||||
for field_match in field_pattern.finditer(table_content):
|
||||
field_name = field_match.group(1)
|
||||
field_def = field_match.group(2).strip()
|
||||
# 排除约束行
|
||||
if not any(keyword in field_def for keyword in ['PRIMARY KEY', 'UNIQUE KEY', 'KEY', 'INDEX']):
|
||||
fields[field_name] = field_def
|
||||
|
||||
tables[table_name] = fields
|
||||
|
||||
return tables
|
||||
|
||||
|
||||
def compare_tables(old_tables, new_tables):
|
||||
"""
|
||||
比较两个表结构字典,找出差异
|
||||
:param old_tables: 旧表结构字典
|
||||
:param new_tables: 新表结构字典
|
||||
:return: 差异字典,包含新增表和新增字段
|
||||
"""
|
||||
diff = {
|
||||
'new_tables': [], # 新增的表
|
||||
'added_fields': [] # 新增的字段,格式为 (表名, 字段名, 字段定义)
|
||||
}
|
||||
|
||||
# 找出新增的表
|
||||
for table_name in new_tables:
|
||||
if table_name not in old_tables:
|
||||
diff['new_tables'].append(table_name)
|
||||
|
||||
# 找出共同表中新增的字段
|
||||
for table_name in new_tables:
|
||||
if table_name in old_tables:
|
||||
old_fields = old_tables[table_name]
|
||||
new_fields = new_tables[table_name]
|
||||
|
||||
for field_name in new_fields:
|
||||
if field_name not in old_fields:
|
||||
diff['added_fields'].append((table_name, field_name, new_fields[field_name]))
|
||||
|
||||
return diff
|
||||
|
||||
|
||||
def generate_update_sql(old_file, new_file, diff):
|
||||
"""
|
||||
根据差异生成update.sql语句
|
||||
:param old_file: 旧SQL文件路径
|
||||
:param new_file: 新SQL文件路径
|
||||
:param diff: 差异字典
|
||||
:return: update.sql内容
|
||||
"""
|
||||
# 使用Python内置的datetime模块获取当前日期和时间
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
update_sql = "-- SQL差异更新脚本\n"
|
||||
update_sql += f"-- 生成时间: {current_time}\n"
|
||||
update_sql += f"-- 比较文件: {os.path.basename(old_file)} (旧) -> {os.path.basename(new_file)} (新)\n\n"
|
||||
|
||||
# 读取新文件内容,用于提取完整的CREATE TABLE语句
|
||||
with open(new_file, 'r', encoding='utf-8') as f:
|
||||
new_content = f.read()
|
||||
|
||||
# 添加新增表的CREATE TABLE语句
|
||||
if diff['new_tables']:
|
||||
update_sql += "-- 新增表\n\n"
|
||||
for table_name in diff['new_tables']:
|
||||
# 提取完整的CREATE TABLE语句
|
||||
table_pattern = re.compile(r'(CREATE TABLE `' + table_name + r'`[\s\S]*?ENGINE=.*?;)', re.MULTILINE)
|
||||
table_match = table_pattern.search(new_content)
|
||||
if table_match:
|
||||
update_sql += table_match.group(1) + '\n\n'
|
||||
|
||||
# 添加新增字段的ALTER TABLE语句
|
||||
if diff['added_fields']:
|
||||
update_sql += "-- 新增字段\n\n"
|
||||
for table_name, field_name, field_def in diff['added_fields']:
|
||||
alter_sql = f"ALTER TABLE `{table_name}` ADD COLUMN `{field_name}` {field_def};\n"
|
||||
update_sql += alter_sql
|
||||
update_sql += '\n'
|
||||
|
||||
if not diff['new_tables'] and not diff['added_fields']:
|
||||
update_sql += "-- 未发现差异\n"
|
||||
|
||||
return update_sql
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
主函数,协调各个步骤
|
||||
"""
|
||||
# 获取SQL文件
|
||||
sql_files = get_sql_files()
|
||||
if len(sql_files) != 2:
|
||||
return
|
||||
|
||||
# 确定旧文件和新文件(根据文件名中的old关键字判断)
|
||||
if 'old' in sql_files[0].lower():
|
||||
old_file = sql_files[0]
|
||||
new_file = sql_files[1]
|
||||
else:
|
||||
old_file = sql_files[1]
|
||||
new_file = sql_files[0]
|
||||
|
||||
print(f"比较文件: {os.path.basename(old_file)} (旧) -> {os.path.basename(new_file)} (新)")
|
||||
|
||||
# 解析SQL文件
|
||||
print("解析SQL文件...")
|
||||
old_tables = parse_sql_file(old_file)
|
||||
new_tables = parse_sql_file(new_file)
|
||||
|
||||
print(f"旧文件包含 {len(old_tables)} 个表")
|
||||
print(f"新文件包含 {len(new_tables)} 个表")
|
||||
|
||||
# 比较表结构
|
||||
print("比较表结构...")
|
||||
diff = compare_tables(old_tables, new_tables)
|
||||
|
||||
# 生成update.sql
|
||||
print("生成update.sql...")
|
||||
update_sql_content = generate_update_sql(old_file, new_file, diff)
|
||||
|
||||
# 确保out文件夹存在
|
||||
out_dir = "out"
|
||||
if not os.path.exists(out_dir):
|
||||
os.makedirs(out_dir)
|
||||
|
||||
# 写入文件
|
||||
out_file = os.path.join(out_dir, "update.sql")
|
||||
with open(out_file, 'w', encoding='utf-8') as f:
|
||||
f.write(update_sql_content)
|
||||
|
||||
print(f"update.sql已生成: {out_file}")
|
||||
print(f"新增表: {len(diff['new_tables'])}")
|
||||
print(f"新增字段: {len(diff['added_fields'])}")
|
||||
|
||||
# 显示差异详情
|
||||
if diff['new_tables']:
|
||||
print("新增表列表:")
|
||||
for table in diff['new_tables']:
|
||||
print(f" - {table}")
|
||||
|
||||
if diff['added_fields']:
|
||||
print("新增字段列表:")
|
||||
for table, field, _ in diff['added_fields']:
|
||||
print(f" - {table}.{field}")
|
||||
|
||||
if not diff['new_tables'] and not diff['added_fields']:
|
||||
print("未发现差异")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user