用于解析SQL语句的正则表达式

6 投票
4 回答
5311 浏览
提问于 2025-04-15 11:12

我有一个IronPython脚本,它会对SQL Server数据库执行一系列SQL语句。这些语句是很长的字符串,实际上包含多个语句,用“GO”这个关键词分隔开。在SQL管理工作室和其他一些工具中运行时,这个方法是有效的,但在ADO中就不行了。因此,我使用2.5版本的“re”模块把这些字符串拆分成这样:

splitter = re.compile(r'\bGO\b', re.IGNORECASE)
for script in splitter.split(scriptBlob):
    if(script):
        [... execute the query ...]

不过,这种方法在少数情况下会出问题,比如当注释或字符串中出现“go”这个词时。我该怎么解决这个问题呢?也就是说,如何正确地把这个字符串解析成两个脚本:

-- this is a great database script!  go team go!
INSERT INTO myTable(stringColumn) VALUES ('go away!')
/*
  here are some comments that go with this script.
*/
GO
INSERT INTO myTable(stringColumn) VALUES ('this is the next script')

补充:

我进一步搜索,找到了这份SQL文档:http://msdn.microsoft.com/en-us/library/ms188037(SQL.90).aspx

结果发现,GO必须单独放在一行上,正如一些答案所建议的那样。不过,它后面可以跟一个“count”整数,这样可以执行这个语句批处理那么多次(有人真的用过这个吗?)而且它可以在同一行后面加上单行注释(但不能加多行注释,我测试过这个)。所以,理想的正则表达式可能看起来像这样:

"(?m)^\s*GO\s*\d*\s*$"

不过,这个正则表达式没有考虑到:

  • 可能在末尾有一个单行注释("--"后面跟着除了换行符以外的任何字符)。
  • 整行可能在一个更大的多行注释中。

我不太关心捕获“count”参数并使用它。现在我有了一些技术文档,离按照规范写这个正则表达式就近在咫尺,再也不用担心这个问题了。

4 个回答

5

如果“GO”总是单独占一行,你可以这样使用分割:

#!/usr/bin/python

import re

sql = """-- this is a great database script!  go team go!
INSERT INTO myTable(stringColumn) VALUES ('go away!')
/*
  here are some comments that go with this script.
*/
GO 5 --this is a test
INSERT INTO myTable(stringColumn) VALUES ('this is the next script')"""

statements = re.split("(?m)^\s*GO\s*(?:[0-9]+)?\s*(?:--.*)?$", sql)

for statement in statements:
    print "the statement is\n%s\n" % (statement)
  • (?m) 这个设置可以让你在多行中进行匹配,也就是说 ^$ 会匹配每一行的开始和结束,而不是整个字符串的开始和结束。
  • ^ 匹配一行的开头
  • \s* 匹配零个或多个空白字符(比如空格、制表符等)
  • GO 匹配字面意思的“GO”
  • \s* 继续匹配空白字符
  • (?:[0-9]+)? 匹配一个可选的整数(可以有前导零)
  • \s* 继续匹配空白字符
  • (?:--.*)? 匹配一个可选的行尾注释
  • $ 匹配一行的结尾

这个分割方法会处理掉“GO”这一行,所以你不需要担心它。这样你就会得到一系列的语句。

不过,这种修改后的分割方法有个问题:如果“GO”后面有数字,它不会把这个数字返回。如果这个数字很重要,我建议你考虑使用某种解析器。

5

因为你可以在注释里再写注释、嵌套注释、在查询里写注释等等,所以用正则表达式来处理这些情况根本不靠谱。

想象一下下面这个脚本:

INSERT INTO table (name) VALUES (
-- GO NOW GO
'GO to GO /* GO */ GO' +
/* some comment 'go go go'
-- */ 'GO GO' /*
GO */
)

还有更复杂的情况:

INSERT INTO table (go) values ('xxx') GO

唯一的办法就是构建一个有状态的解析器。这个解析器一次读取一个字符,并且有一个标志位,当它在注释或引号包围的字符串里时,这个标志位会被设置为“在里面”,当结束时再重置,这样代码就可以在这些情况下忽略“GO”的出现。

8

“GO”是不是总是单独一行?你可以直接用“^GO$”来分割。

撰写回答