无存储过程权限时使用pyodbc执行多段SQL报错的解决方法
问题根因
你碰到的两类报错和存储过程权限无关,不需要建存储过程就能运行,问题来自三个明确的点:
- SQL本身存在拼写错误:你写了三次变量声明关键字
declare,后两次拼成了decalre,直接导致@dedupedemails、@esc_seq两个变量未被成功声明,触发「无法识别标量变量」报错。 - 默认配置下SQL Server会为每个执行的语句返回「影响N行」的计数消息,pyodbc会把这类消息识别为空结果集,你直接调用
fetchall()时拿到的是前面SET、DROP、SELECT INTO这类非查询语句返回的空结果,就会抛出「No results. Previous SQL was not a query.」错误。 - Python代码存在笔误:
cursor = cnxn()写法错误,连接对象不能直接作为函数调用,生成游标的正确写法是cursor = cnxn.cursor()。
修复方案
不需要申请数据库写权限,也不需要改动核心查询逻辑,按以下步骤调整即可:
- 修正SQL里的拼写错误,在SQL最开头加上
SET NOCOUNT ON;,关闭执行过程中的行数计数返回,避免空结果集干扰pyodbc的结果识别。 - 修正Python侧连接、游标的写法,执行SQL后通过
nextset()轮询跳过所有前置的非查询结果段,定位到最后需要的SELECT结果集再拉取数据。 - (可选兼容性优化)如果使用微软官方ODBC Driver for SQL Server,可以在连接字符串里加上
MARS_Connection=Yes开启多活动结果集支持,多语句执行的稳定性更好。
修正后的可运行代码
import pyodbc # 替换为你的实际连接字符串,可按需添加MARS_Connection=Yes配置 # 连接字符串示例:cnxn_str = "DRIVER={ODBC Driver 18 for SQL Server};SERVER=你的服务器地址;DATABASE=目标库名;UID=账号;PWD=密码;MARS_Connection=Yes;Encrypt=yes;TrustServerCertificate=no" q = """ SET NOCOUNT ON; set ANSI_WARNINGS OFF; declare @html varchar(MAX) declare @dedupedemails varchar(MAX) -- 修正原拼写错误 declare @esc_seq int -- 修正原拼写错误 set @esc_seq = 5; if object_id('tempdb.dbo.##dedupemail', 'U') is not null drop table ##dedupemail; with sub1 as ( select p.ID, p.LNAME, p.FNAME, p.EMAIL from dbo.person p ), sub2 as ( select s1.*, case when s1.ID = 1 then 'Yes' else 'No' end as IS_ADMIN from sub1 s1 ) select distinct s2.* into ##dedupemail from sub2 s2 where s2.IS_ADMIN = 'Yes' set @html = 'abc'; select @dedupedemails = ltrim(stuff(( select '; ' + d.email from ##dedupemail d for xml path('')), 1,1,'')); select @dedupedemails as EMAIL_LIST, @html as EMAIL_BODY """ try: cnxn = pyodbc.connect(cnxn_str) cursor = cnxn.cursor() # 修正原游标生成的笔误 cursor.execute(q) # 轮询跳过所有前置非结果集,直到定位到最终查询结果 while cursor.description is None: cursor.nextset() result = cursor.fetchall() print(result) cnxn.close() except pyodbc.Error as e: print("Error caught: ", e)
补充注意事项
- 尽量不要使用
##开头的全局临时表,多用户同时运行脚本时会出现数据冲突,换成#开头的局部临时表即可,局部临时表会在连接断开后自动清理,没有并发问题。 - 后续如果要增加更多段SQL逻辑,只要保留
SET NOCOUNT ON配置和轮询nextset的逻辑,就不会再碰到同类报错,完全不需要依赖存储过程。
内容的提问来源于stack exchange,提问作者mmatick
相关产品推荐
相关产品推荐

