You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Python正则表达式提取文本中任意行数的SELECT SQL块?

如何用Python正则表达式提取文本中任意行数的SELECT SQL块?

嘿,这个问题我之前也碰到过!正则处理SQL确实容易踩坑,我来给你一步步解决~

首先得搞清楚你原来的正则为啥失效:你用的res = re.findall('\(?select.*(\n.*)*[;\)]', sql_que2)没返回结果,核心问题有两个:

  • 默认情况下,正则里的.不匹配换行符,所以.*只能匹配到第一行的末尾,后续的(\n.*)*虽然能匹配换行,但整个模式的匹配逻辑已经断了;
  • 贪婪匹配的.*会尽可能多吃字符,就算你加了换行匹配,也可能把多个SELECT块当成一个整体,或者错过正确的结束符。

解决方案1:改进正则(适合简单SQL场景)

我们只需要调整两个关键点,就能让正则匹配任意行数的SELECT块:

  1. re.DOTALL(或re.S)标志,让.匹配包括换行在内的所有字符;
  2. 把贪婪的.*改成非贪婪的.*?,避免过度匹配,确保匹配到最近的结束符(;))。

直接上代码,用你的测试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这类专业库,省得自己正则调半天还踩坑。

火山引擎 最新活动