如何用Python正则表达式提取文本中任意行数的SELECT SQL块?
如何用Python正则表达式提取文本中任意行数的SELECT SQL块?
嘿,这个问题我之前也碰到过!正则处理SQL确实容易踩坑,我来给你一步步解决~
首先得搞清楚你原来的正则为啥失效:你用的res = re.findall('\(?select.*(\n.*)*[;\)]', sql_que2)没返回结果,核心问题有两个:
- 默认情况下,正则里的
.不匹配换行符,所以.*只能匹配到第一行的末尾,后续的(\n.*)*虽然能匹配换行,但整个模式的匹配逻辑已经断了; - 贪婪匹配的
.*会尽可能多吃字符,就算你加了换行匹配,也可能把多个SELECT块当成一个整体,或者错过正确的结束符。
解决方案1:改进正则(适合简单SQL场景)
我们只需要调整两个关键点,就能让正则匹配任意行数的SELECT块:
- 加
re.DOTALL(或re.S)标志,让.匹配包括换行在内的所有字符; - 把贪婪的
.*改成非贪婪的.*?,避免过度匹配,确保匹配到最近的结束符(;或))。
直接上代码,用你的测试SQL来验证:
import re sql_que = """ select p.value_n, p.enabled from proper p; select p.value_n, p.enabled from proper p where p.property_name = 'PROP1'; (select p.value_n, p.enabled from proper p where p.property_name = 'PROP1' union all select p.value_n, p.enabled from proper p where p.property_name = 'PROP2' )""" # 改进后的正则,加了DOTALL和非贪婪匹配 pattern = re.compile(r'\(?select.*?[;\)]', re.DOTALL | re.IGNORECASE) res = pattern.findall(sql_que) # 打印每个匹配到的块 for i, block in enumerate(res, 1): print(f"第{i}个SELECT块:\n{block}\n")
正则各部分的说明:
\(?:匹配可选的开头左括号,处理像例子里带括号的子查询;select:匹配SELECT关键字,加re.IGNORECASE是为了兼容Select/SELECT这类大小写不敏感的情况;.*?:非贪婪匹配任意字符(因为有re.DOTALL,所以包含换行),直到遇到第一个结束符就停;[;\)]:匹配SQL块的结束标记——分号(独立SELECT的结束)或右括号(子查询的结束)。
运行这段代码,你会得到三个正确的匹配结果:单行的SELECT、3行的带WHERE的SELECT,以及整个带UNION ALL的括号子查询,完美覆盖你例子里的所有情况!
重要提醒:正则的局限
不过得说实话,正则只适合结构简单的SQL——如果你的SQL里有嵌套括号(比如SELECT ... WHERE id IN (SELECT ...))、字符串里包含;或(,或者有注释,正则就会彻底失效,比如它可能会把内部子查询的)当成整个块的结束符,导致匹配不完整。
解决方案2:用专业SQL解析库(推荐复杂场景)
如果要处理复杂SQL,别再纠结正则了,直接用专门的SQL解析工具,比如sqlparse(Python的第三方库,需要先pip install sqlparse安装)。它能真正理解SQL语法,完美提取所有SELECT块,包括嵌套子查询。
代码示例:
import sqlparse # 解析原始SQL文本 parsed_sql = sqlparse.parse(sql_que) # 遍历解析结果,提取所有SELECT语句 select_blocks = [] for stmt in parsed_sql: # 判断当前语句是不是SELECT类型 if stmt.get_type() == 'SELECT': # 格式化语句,让输出更规整 formatted_stmt = sqlparse.format(stmt.to_unicode(), reindent=True, keyword_case='upper') select_blocks.append(formatted_stmt) # 打印结果 for i, block in enumerate(select_blocks, 1): print(f"提取到的第{i}个SELECT块:\n{block}\n")
用sqlparse的好处是,不管SQL有多复杂,它都能正确识别每个SELECT的边界,不会出现正则的各种匹配错误,这才是处理SQL的正经路子~
最后总结
- 简单场景(无嵌套、无特殊字符):用带
re.DOTALL和非贪婪匹配的正则就能搞定; - 复杂场景:直接上
sqlparse这类专业库,省得自己正则调半天还踩坑。




