PyODBC:如何在事务中复制批次分隔符 (GO) 的行为?
PyODBC: how to replicate behavior of batch separator (GO) in a transaction?
背景
我维护一个 Python 应用程序,它自动将 SQL 架构迁移(adding/removing 表和列、调整数据等)应用于我们的数据库 (SQL2016 ).每次迁移都通过 PyODBC 在事务中执行,以便在出现问题时可以回滚。有时迁移需要一个或多个 batch statements (GO
) to execute correctly. Since GO
is ,我一直在 GO
上拆分每个 SQL 迁移并在同一事务中分别执行每个 SQL 片段。
import pyodbc
import re
conn_args = {
'driver': '{ODBC Driver 17 for SQL Server}',
'hostname': 'MyServer',
'port': 1298,
'server': r'MyServer\MyInstance',
'database': 'MyDatabase',
'user': 'MyUser',
'password': '********',
'autocommit': False,
}
connection = pyodbc.connect(**conn_args)
cursor = connection.cursor()
sql = '''
ALTER TABLE MyTable ADD NewForeignKeyID INT NULL FOREIGN KEY REFERENCES MyParentTable(ID)
GO
UPDATE MyTable
SET NewForeignKeyID = 1
'''
sql_fragments = re.split(r'^\s*GO;?\s*$', sql, flags=re.IGNORECASE|re.MULTILINE)
for sql_frag in sql_fragments:
cursor.execute(sql_frag)
# Wait for the command to complete. This is necessary for some database system commands
# (backup, restore, etc). Probably not necessary for schema migrations, but included
# for completeness.
while cursor.nextset():
pass
connection.commit()
问题
SQL 语句批处理未按预期执行。在SSMS中执行上面的schema迁移时,成功了。在Python中执行时,第一批(添加外键)执行得很好,但第二批(设置外键值)失败,因为它不知道新的外键。
('42S22', "[42S22] [FreeTDS][SQL Server]Invalid column name 'NewForeignKeyID'. (207) (SQLExecDirectW)")
目标
在 PyODBC 的单个事务中执行 SQL 个语句批次的层次结构(即每个语句批次依赖于前一个批次)。
我试过的
正在搜索 PyODBC documentation 以获取有关 PyODBC 如何支持或不支持批处理语句/GO
命令的信息。未找到参考文献。
正在搜索 Whosebug 和 Google 了解如何在 PyODBC 中批处理语句。
在 SQL 片段执行之间引入一个小的睡眠,以防出现某种竞争条件。似乎不太可能成为解决方案,并且没有改变行为。
我考虑过将每批语句分离到一个单独的事务中,该事务在执行下一批之前提交,但这将 reduce/eliminate 我们自动回滚模式的能力迁移失败。
编辑:我刚刚发现 this question, which is pretty much exactly what I want to do. However, upon testing (in SSMS) the answer that recommends using EXEC
我发现 第二个 EXEC
命令(设置值)失败,因为它不知道new foreign key. 我测试不好,居然成功了。此解决方案可能有效但并不理想,因为 EXEC
与参数不兼容。此外,如果跨片段使用变量,这将不起作用。
BEGIN TRAN
EXEC('ALTER TABLE MyTable ADD NewForeignKeyID INT NULL FOREIGN KEY REFERENCES MyParentTable(ID)')
EXEC('UPDATE MyTable SET NewForeignKeyID = 1')
ROLLBACK TRAN
Invalid column name 'FK_TestID'.
如果您正在从文本文件(例如由 SSMS 中的脚本对象生成的文件)中读取 SQL 语句,那么您可以只使用 Python 的 subprocess
模块来运行 以该文件作为输入的 sqlcmd
实用程序 (-i
)。最简单的形式看起来像
server = "localhost"
port = 49242
uid = "scott"
pwd = "tiger^5HHH"
database = "myDb"
script_file = r"C:\__tmp\batch_test.sql"
"""contents of the above file:
DROP TABLE IF EXISTS so69020084;
CREATE TABLE so69020084 (src varchar(10), var_value varchar(10));
INSERT INTO so69020084 (src, var_value) VALUES ('1st batch', 'foo');
GO
INSERT INTO so69020084 (src, var_value) VALUES ('2nd batch', 'bar');
GO
"""
import subprocess
cmd = [
"sqlcmd",
"-S", f"{server},{port}",
"-U", uid,
"-P", pwd,
"-d", database,
"-i", script_file,
]
subprocess.run(cmd)
背景
我维护一个 Python 应用程序,它自动将 SQL 架构迁移(adding/removing 表和列、调整数据等)应用于我们的数据库 (SQL2016 ).每次迁移都通过 PyODBC 在事务中执行,以便在出现问题时可以回滚。有时迁移需要一个或多个 batch statements (GO
) to execute correctly. Since GO
is GO
上拆分每个 SQL 迁移并在同一事务中分别执行每个 SQL 片段。
import pyodbc
import re
conn_args = {
'driver': '{ODBC Driver 17 for SQL Server}',
'hostname': 'MyServer',
'port': 1298,
'server': r'MyServer\MyInstance',
'database': 'MyDatabase',
'user': 'MyUser',
'password': '********',
'autocommit': False,
}
connection = pyodbc.connect(**conn_args)
cursor = connection.cursor()
sql = '''
ALTER TABLE MyTable ADD NewForeignKeyID INT NULL FOREIGN KEY REFERENCES MyParentTable(ID)
GO
UPDATE MyTable
SET NewForeignKeyID = 1
'''
sql_fragments = re.split(r'^\s*GO;?\s*$', sql, flags=re.IGNORECASE|re.MULTILINE)
for sql_frag in sql_fragments:
cursor.execute(sql_frag)
# Wait for the command to complete. This is necessary for some database system commands
# (backup, restore, etc). Probably not necessary for schema migrations, but included
# for completeness.
while cursor.nextset():
pass
connection.commit()
问题
SQL 语句批处理未按预期执行。在SSMS中执行上面的schema迁移时,成功了。在Python中执行时,第一批(添加外键)执行得很好,但第二批(设置外键值)失败,因为它不知道新的外键。
('42S22', "[42S22] [FreeTDS][SQL Server]Invalid column name 'NewForeignKeyID'. (207) (SQLExecDirectW)")
目标
在 PyODBC 的单个事务中执行 SQL 个语句批次的层次结构(即每个语句批次依赖于前一个批次)。
我试过的
正在搜索 PyODBC documentation 以获取有关 PyODBC 如何支持或不支持批处理语句/
GO
命令的信息。未找到参考文献。正在搜索 Whosebug 和 Google 了解如何在 PyODBC 中批处理语句。
在 SQL 片段执行之间引入一个小的睡眠,以防出现某种竞争条件。似乎不太可能成为解决方案,并且没有改变行为。
我考虑过将每批语句分离到一个单独的事务中,该事务在执行下一批之前提交,但这将 reduce/eliminate 我们自动回滚模式的能力迁移失败。
编辑:我刚刚发现 this question, which is pretty much exactly what I want to do. However, upon testing (in SSMS) the answer that recommends using
EXEC
我发现第二个我测试不好,居然成功了。此解决方案可能有效但并不理想,因为EXEC
命令(设置值)失败,因为它不知道new foreign key.EXEC
与参数不兼容。此外,如果跨片段使用变量,这将不起作用。BEGIN TRAN EXEC('ALTER TABLE MyTable ADD NewForeignKeyID INT NULL FOREIGN KEY REFERENCES MyParentTable(ID)') EXEC('UPDATE MyTable SET NewForeignKeyID = 1') ROLLBACK TRAN Invalid column name 'FK_TestID'.
如果您正在从文本文件(例如由 SSMS 中的脚本对象生成的文件)中读取 SQL 语句,那么您可以只使用 Python 的 subprocess
模块来运行 以该文件作为输入的 sqlcmd
实用程序 (-i
)。最简单的形式看起来像
server = "localhost"
port = 49242
uid = "scott"
pwd = "tiger^5HHH"
database = "myDb"
script_file = r"C:\__tmp\batch_test.sql"
"""contents of the above file:
DROP TABLE IF EXISTS so69020084;
CREATE TABLE so69020084 (src varchar(10), var_value varchar(10));
INSERT INTO so69020084 (src, var_value) VALUES ('1st batch', 'foo');
GO
INSERT INTO so69020084 (src, var_value) VALUES ('2nd batch', 'bar');
GO
"""
import subprocess
cmd = [
"sqlcmd",
"-S", f"{server},{port}",
"-U", uid,
"-P", pwd,
"-d", database,
"-i", script_file,
]
subprocess.run(cmd)