用于解析SQL语句的正则表达式
我有一个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 个回答
如果“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”后面有数字,它不会把这个数字返回。如果这个数字很重要,我建议你考虑使用某种解析器。
因为你可以在注释里再写注释、嵌套注释、在查询里写注释等等,所以用正则表达式来处理这些情况根本不靠谱。
想象一下下面这个脚本:
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”的出现。
“GO”是不是总是单独一行?你可以直接用“^GO$”来分割。