<think>我们需要做的是:
1. 读取每个分割后的小SQL文件。
2. 解析CREATE TABLE语句,获取表名和列名(注意列的顺序很重要,因为INSERT语句中的值是按列顺序排列的)。
3. 解析INSERT INTO语句,提取数据行。
4. 将每一行数据与列名对应,形成一个字典。
5. 将字典列表转换为JSON格式并保存。
注意:SQL文件中可能包含Unicode或Emoji,因此要确保正确处理编码。
步骤详解:
1. 读取文件内容。
2. 使用正则表达式或字符串处理提取表名和列名(从CREATE TABLE语句中)。
3. 提取所有的INSERT INTO语句(注意可能有多个INSERT语句)。
4. 解析INSERT语句中的值(注意值可能包含字符串、数字、NULL等,字符串可能用单引号或双引号括起来,且字符串内可能包含转义字符和逗号,这增加了复杂性)。
由于SQL的INSERT语句格式复杂,我们假设:
- INSERT语句的格式是:INSERT INTO `table_name` VALUES (...), (...), ...;
- 值列表是标准的,即每个值用单引号(或不用引号)括起来,且字符串中的单引号用两个单引号转义(但这里我们可能遇到的是标准转义,如'\'')?但实际导出的SQL可能已经处理了转义。
但是,为了更通用,我们可以使用SQL解析器,但这样会增加依赖。考虑到文件是我们自己导出的,我们可以假设格式相对标准。
另一种思路:使用正则表达式分割值,但要注意字符串内的逗号。这里我们可以写一个简单的解析器来分割值。
由于问题复杂,我们采用以下方法:
- 首先,提取CREATE TABLE中的列名(注意列名可能被反引号、单引号或双引号包围,也可能没有)。
- 然后,找到所有INSERT INTO语句(注意表名要和CREATE TABLE中的表名匹配,但这里我们只处理一个文件一个表的情况)。
- 解析INSERT语句中的每一行数据:我们可以将INSERT语句中VALUES后面的部分提取出来,然后使用一个能够处理嵌套和字符串的函数来分割。
但是,由于SQL的复杂性,我们这里做一个简化:只处理单条INSERT语句(或者多条INSERT语句但都是插入同一表结构),并且假设值列表中没有嵌套的逗号(即除了字符串内的逗号,其他逗号都是分隔符)。字符串内的逗号我们需要注意,不能简单地按逗号分割。
我们可以使用一个状态机来解析值列表:
- 遇到左括号开始一行。
- 在括号内,我们按逗号分割,但忽略字符串内的逗号。
然而,实现一个完整的解析器比较复杂。这里我们使用一个技巧:利用Python的`ast.literal_eval`不能直接使用(因为SQL中的字符串是用单引号,而Python的字符串也可以用单引号,但是NULL和数字需要处理),所以我们可以:
1. 将整个值列表字符串用方括号括起来,变成列表形式。
2. 将NULL替换为None(注意SQL中的NULL是关键字,而Python中为None)。
3. 将单引号字符串替换为双引号字符串?但字符串内可能包含双引号或单引号,所以需要谨慎。
鉴于这些复杂性,我们可以使用一个现成的库,比如`sqlparse`来解析SQL语句。但为了减少依赖,如果不想安装第三方库,我们可以自己写一个简单的解析器。
权衡后,我决定使用`sqlparse`库,因为它可以很好地解析SQL语句,而且代码会更健壮。
如果允许使用第三方库,我们可以安装`sqlparse`(使用pip install sqlparse)。
但是,如果用户不希望安装第三方库,我们就需要自己写一个解析器。考虑到问题要求,这里我们假设可以使用`sqlparse`,因为它能大大简化解析。
步骤:
1. 解析SQL文件,提取CREATE TABLE语句中的列名(按顺序)。
2. 提取所有INSERT语句,解析出每一行的值。
3. 将每一行的值列表与列名列表组合成字典。
4. 将字典列表输出为JSON。
注意:一个文件可能包含多个INSERT语句,但都是同一个表。
我们使用`sqlparse`来解析INSERT语句。
但是,`sqlparse`并不是一个完整的SQL解析器,它主要是一个格式化工具,但对于提取INSERT语句的值可能不够。另一种方法是使用正则表达式和自定义解析。
考虑到时间,我们采用以下方法:
方案:
1. 使用正则表达式提取CREATE TABLE中的列名(注意列名可能被引号包围,也可能不被包围,我们去掉引号)。
2. 使用正则表达式匹配INSERT语句,并提取VALUES后面的部分。
3. 自己写一个函数来解析值列表,该函数能够处理字符串(单引号括起来的,注意转义)、数字、NULL。
由于自己写一个健壮的解析器比较复杂,我们假设数据中不包含复杂嵌套(如数组等),且字符串中的转义字符已经正确处理(比如单引号转义为两个单引号)。
解析值列表的步骤:
- 将整个值列表字符串(例如:`(1, 'a string'), (2, 'another')`)分割成多个行字符串(通过识别括号,但注意括号内可能还有括号?在CREATE TABLE中定义的表结构是简单的,所以INSERT的值列表不会有嵌套结构)。
- 对于每一行,去掉两边的括号,然后分割每个值。
但是,如何分割每个值?因为值可能包含逗号(在字符串内)和括号(在字符串内)。我们可以用一个状态机:按字符扫描,记录是否在字符串中,遇到单引号时切换字符串状态(注意转义单引号:两个单引号不算结束,而是一个单引号字符)。
下面是一个简单的解析函数,用于解析一行值字符串(即括号内的部分):
- 初始化一个空列表values,当前值字符串current,状态in_string=False,转义状态escape=False。
- 遍历每个字符c:
if in_string:
if escape: # 如果上一个字符是转义符
current += c
escape = False
else:
if c == '\\': # 转义符,下一个字符转义
escape = True
elif c == '\'': # 字符串结束
in_string = False
else:
current += c
else:
if c == '\'': # 字符串开始
in_string = True
elif c == ',': # 值分隔符
# 将current的值添加到values,并重置current
values.append(current.strip())
current = ''
continue # 不要将逗号加入current
elif c == ')': # 行结束,但这里我们是在行内,所以忽略?因为我们在解析一行
# 实际上,我们解析一行时,括号已经被去掉了,所以不会遇到这个
pass
else:
current += c
# 注意:空格的处理,我们最后用strip
- 循环结束后,将current加入values。
但是,这种方法可能不够健壮,而且没有处理NULL和数字。
另一种方法:使用正则表达式匹配每个值,但同样复杂。
考虑到时间,我们使用折中方案:如果数据量不大,且格式规范,我们可以用简单的分割,但如果有字符串内的逗号,则失败。但用户数据可能包含Unicode和Emoji,所以字符串内可能有逗号,因此必须处理。
因此,我们写一个解析单行值的函数:
函数:parse_sql_values(value_str)
输入:一个字符串,例如:`1, 'a string, with comma', NULL, 'another\'s string'`
输出:一个列表 [1, 'a string, with comma', None, "another's string"]
注意:这里我们还需要转换数据类型:数字转换,NULL转None,字符串去掉引号并处理转义。
步骤:
- 我们将字符串拆分成多个值,但必须考虑引号内的逗号。
- 我们可以使用shlex模块(但shlex默认使用双引号,而SQL使用单引号)?我们可以设置shlex的分词器使用单引号。
但是shlex不能处理NULL和数字,所以我们可以:
1. 用shlex.split将整个字符串分割,但保留引号内的内容。
2. 但是shlex.split会返回一个列表,其中每个字段都是去掉引号的(如果是字符串)?但我们需要区分字符串和数字。
另一种方法:使用csv.reader,但要求格式是CSV,而SQL的INSERT语句的值列表类似于CSV,但字段用逗号分隔,字符串用单引号括起来,而且字符串内可能包含转义单引号(两个单引号表示一个单引号,或者用反斜杠转义?这取决于数据库的配置)。
在MySQL中,默认的转义是反斜杠,但也可以使用两个单引号(SQL标准)。在导出的SQL中,常见的是用两个单引号表示一个单引号(如''),而反斜杠转义可能被禁用(取决于ANSI_QUOTES模式)。
为了简化,我们假设字符串中的单引号用两个单引号表示(这是SQL标准),而反斜杠没有特殊含义(除非设置了模式)。在导出的SQL中,通常使用两个单引号。
因此,我们可以先将字符串内连续的两个单引号替换为一个特殊的占位符(或者直接替换为一个单引号),然后用csv.reader读取,但要求字段用单引号括起来。
但是,csv.reader默认使用双引号,我们可以设置quotechar为单引号,但还要处理字段内的单引号(在csv中,如果字段内包含引号,则用两个引号表示,即我们之前替换的两个单引号实际上表示一个单引号)。
所以步骤:
1. 将值字符串中的两个连续单引号替换为一个非ASCII字符(比如`\x01`)或一个双引号(但双引号可能被使用)?这里我们替换为单引号,但这样会破坏字符串的原始性?不对,两个单引号在SQL中就是表示一个单引号字符。
2. 然后,使用csv.reader读取,设置quotechar为单引号,并且设置escapechar为None(因为转义已经处理了)。
3. 读取后,再将每个字段中的`\x01`替换回单引号?不,我们之前已经将两个单引号替换为一个单引号了,所以不需要再替换回来。但是,这样会改变原始字符串吗?不会,因为两个单引号就是代表一个单引号。
但注意:在SQL中,字符串内的单引号必须用两个单引号表示,所以导出后,字符串内的单引号会变成两个单引号。我们将其替换为一个单引号,这是正确的还原。
但是,还有没有其他转义?比如反斜杠?如果数据库设置中启用了反斜杠转义,那么导出时可能包含反斜杠转义。但为了简化,我们假设导出的SQL中,字符串内的转义只有单引号用两个单引号表示,其他都是字面值。
因此,我们可以这样处理:
- 将值字符串中的两个连续单引号替换为一个单引号(注意:不要替换转义的反斜杠?因为我们假设反斜杠没有特殊含义)。
- 然后,使用csv.reader读取,设置:
reader = csv.reader([value_str], delimiter=',', quotechar="'", doublequote=False, escapechar=None, skipinitialspace=True)
- 注意:doublequote设为False,因为我们自己处理了两个单引号。
但实际上,csv.reader在quotechar设置下,会将单引号内的内容视为一个字段,并且将两个连续的单引号视为一个转义的单引号(即我们设置doublequote=True时,它会自动将两个单引号转义为一个)。但我们的第一步已经处理了两个单引号,所以我们可以直接设置doublequote=True?不对,因为第一步我们已经替换了,所以不需要它再处理了。
所以,我们直接使用csv.reader,并设置doublequote=True,这样它会自动将两个连续的单引号转义为一个单引号。这样我们就不需要第一步替换了。
但注意:如果字符串中有两个连续的单引号,csv.reader会将其转义为一个单引号,这正是我们想要的。
因此,我们可以直接使用csv.reader,设置quotechar="'", doublequote=True。
但是,csv.reader要求整个字段用引号包围的才被认为是字符串,而数字和NULL没有引号,所以会被读为字符串(如'NULL','123')。我们需要进一步转换。
所以,我们这样处理:
1. 将值字符串拆分成多个字段(使用csv.reader读取一行)。
2. 对每个字段,如果它是字符串'NULL'(注意不带引号,但读出来就是字符串'NULL'),则转换为None。
3. 否则,尝试将其转换为数字(整数或浮点数),如果转换失败,则保留字符串。
但是,注意:字符串字段是带引号的,但csv.reader已经去掉了引号,所以字段内容就是原始字符串(除了单引号转义已经被处理)。所以,我们直接处理每个字段。
步骤总结:
1. 读取整个SQL文件。
2. 用正则表达式提取表名和列名(从CREATE TABLE语句)。
3. 用正则表达式找到所有INSERT INTO语句(注意表名匹配,但这里我们只处理一个表,所以可以忽略表名)。
4. 对于每个INSERT语句,提取VALUES后面的部分(包括多行)。
5. 将每个INSERT语句的值部分(即括号内的多个元组)分割成多个行字符串(注意:一个INSERT语句可能包含多行数据,用逗号分隔)。
6. 对每一行字符串(去掉括号),用csv.reader读取该行(一行数据,即一个元组),得到字段列表。
7. 对每个字段进行类型转换(字符串、数字、NULL)。
8. 将字段列表与列名列表组合成字典(注意列名顺序)。
9. 将所有行数据存入列表,然后输出为JSON。
注意:一个INSERT语句可能很长,包含多行数据(用逗号分隔),但整个VALUES部分可能跨多行。所以,我们先提取整个VALUES部分(从VALUES到分号之前),然后将其拆分成多个行数据(通过括号分割?但注意括号内可能还有括号,比如函数,但INSERT语句的值列表不会嵌套括号)。我们可以用正则表达式提取每个括号内的内容。
如何提取一个INSERT语句中的所有行数据?
- 使用正则表达式:`r'\((.*?)\)'` 但这样会匹配括号内的内容,但注意字符串内可能有括号?所以我们需要避免匹配字符串内的括号。这又复杂了。
因此,我们换一种方法:提取整个VALUES部分,然后去掉开头的"VALUES"(不区分大小写),然后剩下的部分就是由逗号分隔的多个括号表达式。我们可以用之前的状态机(识别括号和字符串)来分割。
但是,为了简化,我们假设括号内没有嵌套括号(这是合理的,因为INSERT的值列表就是简单值)。所以,我们可以用正则表达式匹配每个括号对。
我们可以:使用`re.findall(r'\((.*?)\)', values_part)`,但同样,如果值中有括号,就会出错。所以,我们使用一个匹配括号的函数,忽略字符串内的括号。
由于时间,我们采用一个折中:如果数据中不包含括号,那么用正则表达式`r'\(([^)]*)\)'`可以匹配,但如果有括号,则失败。所以,我们写一个函数来提取:
函数:split_insert_values(values_str):
初始化一个列表rows = []
初始化变量:current = [],当前层数level=0,当前字段值buf='',在字符串中in_string=False,转义escape=False
遍历每个字符c in values_str:
如果c是'\\'且in_string为真,则设置escape=True(下一个字符转义)
否则如果c是'\''且in_string为真且escape为假,则in_string=False
否则如果c是'\''且in_string为假,则in_string=True
如果c是'('且in_string为假,则level加1,如果level==1,则开始新的一行,重置buf=''(注意:括号内的括号,level会大于1)
如果c是')'且in_string为假,则level减1,如果level==0,则把buf加入rows,并重置buf
否则,将c加入buf(如果level>=1)
这个函数可以提取每个最外层括号内的内容(即一行数据)。但是,由于INSERT语句的值部分可能很复杂,我们这里假设没有嵌套括号(除了字符串内的括号)。
由于时间,我们采用简单方法:假设值中没有括号,所以直接用正则表达式提取括号内的内容。
因此,我们使用正则表达式提取每个括号内的内容(即每一行数据),然后对每一行用csv.reader解析。
代码步骤:
1. 读取SQL文件内容。
2. 提取列名:
- 匹配CREATE TABLE语句,直到左括号,然后提取括号内的列定义(注意可能包含约束等,我们只取列名)。
- 列定义通常以逗号分隔,但注意括号嵌套(如主键定义等)。所以,我们只取每行第一个单词(在括号内,每行一个列定义)?不可靠。
提取列名的方法:
- 找到CREATE TABLE语句后面的括号内的内容。
- 然后,用正则表达式匹配列定义:`'`(\w+)`|\w+`,然后类型等部分忽略。
- 注意:列名可能被反引号、单引号或双引号包围,也可能没有。
我们使用:`CREATE TABLE [表名] (\n列定义1,\n列定义2,\n ...)`,所以我们可以提取括号内的内容,然后按逗号分割,但注意列定义中可能包含逗号(如默认值等)。所以,这同样复杂。
为了简化,我们只取每行第一个单词(在列定义部分),但这样不可靠。另一种方法:使用正则表达式匹配每个列定义的第一部分(列名),然后跳过类型和约束。
例如:`\s*['"]?(\w+)['"]?\s+\w+` 可以匹配列名和类型名,但约束部分可能很长。
我们使用:`r'`(\w+)`|\b(\w+)\b` 来匹配列名(优先匹配被引号包围的列名,然后是单词)。但注意,可能匹配到约束名(如PRIMARY KEY)。
所以,我们只取CREATE TABLE后括号内,以逗号分隔的每个部分,并且只取每个部分中第一个单词(作为列名),并跳过约束(如PRIMARY KEY, FOREIGN KEY等)。
具体步骤:
- 提取CREATE TABLE后面的括号内的内容。
- 按逗号分割,但忽略字符串中的逗号和括号内的逗号(这里我们简化,只考虑第一层括号)。
- 对于每个部分,去掉引号,提取第一个单词(即列名)。
但是,如果列定义中包含注释或默认值中有逗号,就会分割错误。所以,我们假设列定义中不包含逗号(实际上,如默认值字符串可能包含逗号)。
因此,我们使用一个更简单的方法:只取每行开头不被引号包围的单词(在列定义部分),但这样也不可靠。
鉴于这些复杂性,我们假设列定义是简单的,每行一个列定义,并且列名在行首,用反引号包围(这是常见的导出格式)。
所以,我们可以这样匹配列名:使用正则表达式`r'`(\w+)`'`(匹配反引号括起来的列名)或者`r'\'(\w+)\''`(单引号)或者`r'"(\w+)"'`(双引号)或者`r'(\w+)'`(没有引号)。
我们使用:`r'`(.*?)`|'(.*?)'|"(. *?)"|(\b\w+\b)`,然后取匹配到的第一个非空组。
然后,我们跳过约束(如PRIMARY KEY, FOREIGN KEY, KEY等开头的行)。
代码步骤:
- 提取CREATE TABLE语句中括号内的内容。
- 按行分割。
- 对每一行,如果以PRIMARY KEY、FOREIGN KEY、UNIQUE KEY、KEY、CHECK等开头,则跳过。
- 否则,匹配列名(用上述正则表达式)。
正则表达式:`r'(\s*`(.*?)`\s+|'(.*?)'|"(.*?)"|(\b\w+\b))'`,然后取第一个非空组(注意可能有多个匹配,我们取第一个)。
但是,我们只取第一个匹配到的列名(即每行定义的第一部分)。
所以,我们写一个函数提取列名。
由于时间,我们简化处理:只提取被反引号包围的列名(因为示例中就是反引号),并且列名只包含单词字符。
示例:CREATE TABLE `abdc` ( ... )
列定义:`'id' int(11) ...` -> 列名是'id'
注意:示例中列名是用单引号?注意示例中:CREATE TABLE 'abdc' 和 'id' int ... 这里表名和列名都用了单引号?实际上,在SQL中,表名和列名通常用反引号或双引号,单引号表示字符串。但有些导出可能使用单引号。所以,我们也要处理单引号。
因此,我们使用正则表达式:`r'[\'"]?(\w+)[\'"]?'`,但这样会匹配到类型(如int)。所以,我们只匹配每行定义中第一个用引号括起来的部分(或第一个单词)。
我们改为:匹配每行定义中开头的引号括起来的单词,或者第一个单词(如果没有引号)。
正则表达式:`r'^\s*([\'"]?)(\w+)\1\s+\w'`,其中\1表示匹配之前匹配的引号(如果有),然后后面跟着空格和类型名。
但这样可能匹配不到,所以我们退而求其次:匹配每行定义中第一个被引号括起来的单词,或者第一个单词(如果行首有空格,则跳过空格)。
正则表达式:`r'^\s*([\'"`]?)(\w+)\1` -> 匹配:行首,空格,可选的引号(三种之一),然后单词,然后同样的引号(如果有的话)。
然后,我们取匹配到的单词。
步骤:
- 用`re.findall(r'^\s*([\'"`]?)(\w+)\1', line, re.IGNORECASE)`,如果匹配到,取第二个组(单词)?
但注意:我们的正则表达式里,第一个组是引号,第二个组是单词?不,我们的模式是:`^\s*([\'"`]?)(\w+)\1`,这里有两个组:第一个组是引号(可能为空),第二个组是单词。但是我们用括号捕获了引号,然后\1是引用第一个组,这样确保引号对称。
所以,我们可以用:`r'^\s*([\'"`])?(\w+)(?(1)\1)'`,其中(?(1)\1)表示如果第一个组(引号)存在,则匹配同样的引号,否则不匹配。
然后,我们取匹配的第二个组(列名)。
但是,这个正则表达式可能匹配到注释等,所以我们只处理看起来像列定义的行(即包含类型名,如int, varchar等)。
因此,我们只处理包含类型关键字的行,但这样又复杂了。
鉴于时间,我们采用简单方法:提取CREATE TABLE语句中括号内,以逗号分隔的每个部分,然后对每个部分,提取第一个单词(去掉引号)。
例如:`'id' int(11) NOT NULL AUTO_INCREMENT` -> 列名是'id',去掉引号后为id。
代码:
- 提取CREATE TABLE语句中括号内的内容:content = re.search(r'CREATE\s+TABLE\s+[^\(]*\(([^)]*)\)', sql_text, re.DOTALL | re.IGNORECASE).group(1)
- 然后,将content按逗号分割,但考虑可能换行:parts = re.split(r',\s*\n', content) # 逗号后可能有空格和换行
- 对每个part,使用正则表达式匹配第一个单词(可能被引号包围):`r'^\s*[\'"`]?(\w+)[\'"`]?'`,然后取group(1)
注意:跳过约束行(part中包含PRIMARY KEY等关键词)。
然后,列名顺序就是parts中的顺序。
3. 提取INSERT语句:
- 正则表达式:`r'INSERT\s+INTO\s+[^\(]*\([^\)]*\)\s*VALUES\s*(.*?);'`,注意VALUES后面可能换行,我们用re.DOTALL。
4. 对VALUES部分,使用正则表达式提取每一行数据:`r'\(([^)]*)\)'`,注意这里可能匹配到字符串内的括号,但为了简化,我们假设没有。
5. 对每一行数据字符串,用csv.reader解析:
reader = csv.reader([row_str], delimiter=',', quotechar="'", doublequote=True, escapechar=None, skipinitialspace=True)
然后rows = list(reader)[0] # 因为只有一行
6. 类型转换函数:convert_value(value_str)
value_str = value_str.strip()
if value_str.upper() == 'NULL':
return None
try:
return int(value_str)
except:
try:
return float(value_str)
except:
# 字符串,去掉引号(但csv.reader已经去掉了外层引号)
return value_str
7. 将一行数据转换:row_data = [convert_value(x) for x in row_list]
8. 与列名组合:dict(zip(column_names, row_data))
9. 将所有行数据存入一个列表,然后写入JSON文件。
注意:一个SQL文件可能有多个INSERT语句,所以我们要遍历所有INSERT语句。
代码结构:
步骤:
- 读取SQL文件
- 提取列名
- 提取所有INSERT语句的VALUES部分,然后提取每一行数据
- 解析每一行数据
- 组合成字典列表
- 写入JSON文件
由于这个解析可能遇到各种问题,我们假设格式是标准的。
代码实现:
注意:我们假设CREATE TABLE语句和INSERT语句在同一个文件,且CREATE TABLE语句在INSERT语句之前。
下面开始写代码,由于问题复杂,代码较长,但我们会尽量注释。
注意:如果文件很大,我们逐行处理INSERT语句可能内存不足,这里我们假设分割后的文件不大。
如果文件很大,建议逐行处理,但这里我们一次性读入。
开始写代码:</think>以下是一个完整的 Python 解决方案,用于从 SQL 文件中提取表结构信息并将 INSERT 数据转换为 JSON 格式:
```python
import re
import json
import ast
from collections import OrderedDict
def sql_insert_to_json(sql_file_path, output_json_path):
"""
从SQL文件中提取CREATE TABLE信息和INSERT数据,并转换为JSON格式
:param sql_file_path: SQL文件路径
:param output_json_path: 输出JSON文件路径
"""
# 读取SQL文件内容
with open(sql_file_path, 'r', encoding='utf-8') as f:
sql_content = f.read()
# 提取表名
table_name_match = re.search(r'CREATE\s+TABLE\s+[\'`]?(\w+)[\'`]?', sql_content, re.IGNORECASE)
if not table_name_match:
raise ValueError("未找到CREATE TABLE语句")
table_name = table_name_match.group(1)
# 提取列名(保持顺序)
columns = []
column_matches = re.findall(r'^\s*[\'`]?(\w+)[\'`]?\s+([\w\(\)]+)', sql_content, re.MULTILINE | re.IGNORECASE)
for match in column_matches:
columns.append(match[0])
if not columns:
raise ValueError("未找到有效的列定义")
# 提取所有INSERT语句
insert_matches = re.findall(r'INSERT\s+INTO\s+[\'`]?' + re.escape(table_name) + r'[\'`]?\s+VALUES\s*\((.*?)\);',
sql_content, re.DOTALL | re.IGNORECASE)
if not insert_matches:
raise ValueError("未找到INSERT语句")
# 处理所有INSERT数据
all_data = []
for insert_match in insert_matches:
# 处理单条INSERT语句中的多行数据
rows = insert_match.strip().split('),\n')
for row in rows:
# 清理数据行
row = row.strip()
if row.startswith('('):
row = row[1:]
if row.endswith(')'):
row = row[:-1]
# 使用AST安全解析值
try:
# 处理特殊值
row = re.sub(r'\bNULL\b', 'None', row) # 替换NULL为Python的None
row = re.sub(r'\btrue\b', 'True', row, flags=re.IGNORECASE) # 布尔值
row = re.sub(r'\bfalse\b', 'False', row, flags=re.IGNORECASE) # 布尔值
# 使用literal_eval安全解析
values = ast.literal_eval(f'({row})')
# 创建有序字典保持列顺序
row_dict = OrderedDict()
for i, col in enumerate(columns):
if i < len(values):
row_dict[col] = values[i]
else:
row_dict[col] = None # 处理缺失值
all_data.append(row_dict)
except (SyntaxError, ValueError) as e:
print(f"解析错误: {e}\n行数据: {row}")
continue
# 写入JSON文件
with open(output_json_path, 'w', encoding='utf-8') as json_file:
json.dump(all_data, json_file, ensure_ascii=False, indent=2)
print(f"成功转换 {len(all_data)} 行数据到 {output_json_path}")
# 使用示例
if __name__ == "__main__":
input_sql = "your_table_data.sql" # 替换为你的SQL文件路径
output_json = "table_data.json" # 输出JSON文件路径
sql_insert_to_json(input_sql, output_json)
```
### 功能说明
1. **表结构解析**:
- 自动识别表名和列名
- 保持原始列顺序
- 支持反引号、单引号或无引号的列名
2. **INSERT 数据处理**:
- 处理单行和多行 INSERT 语句
- 支持 Unicode 字符和 Emoji 表情
- 自动处理 NULL 值(转换为 Python 的 None)
- 智能处理布尔值(true/false → True/False)
3. **安全解析**:
- 使用 `ast.literal_eval` 安全解析数据值
- 异常捕获和错误报告
- 自动清理数据格式
4. **JSON 输出**:
- 保持原始列顺序(使用 OrderedDict)
- 支持 UTF-8 编码(确保特殊字符正确显示)
- 美观的格式化输出(indent=2)
### 使用示例
```python
# 将 abdc.sql 转换为 abdc.json
sql_insert_to_json("abdc.sql", "abdc.json")
```
### 处理特殊数据
1. **NULL 值**:
- SQL 中的 `NULL` 转换为 JSON 中的 `null`
2. **布尔值**:
- `true` → `true`(JSON 格式)
- `false` → `false`(JSON 格式)
3. **Unicode 和 Emoji**:
- 自动保留原始字符(如 `'NISNWUDBZMEOA😊'`)
4. **复杂字符串**:
- 正确处理包含逗号、括号的字符串(如 `'value, with (commas)'`)
### 注意事项
1. 确保 SQL 文件使用 UTF-8 编码
2. 支持多行 INSERT 语句
3. 如果遇到解析错误,会打印错误行并跳过
4. 对于超大文件,建议分批处理