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 个语句批次的层次结构(即每个语句批次依赖于前一个批次)。

我试过的

如果您正在从文本文件(例如由 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)