
本文详解如何使用 `pandas.read_sql_query()` 正确调用 sql server 带参数的存储过程,重点解决参数绑定语法错误、类型不匹配及 `set nocount on` 的必要性问题,并提供可直接运行的完整示例。
在使用 SQLAlchemy 和 pandas 与 SQL Server 交互时,调用带参数的存储过程(Stored Procedure)是一个常见但易出错的操作。你遇到的 TypeError: unsupported operand type(s) for &: 'str' and 'int' 是因误用 & 运算符拼接字符串(Python 中应使用 + 或 f-string),而 Connection.execute() got an unexpected keyword argument 'bookId' 则源于混淆了 execute() 方法的参数传递方式——params 必须显式传给 read_sql_query(),而非 execute()。
正确做法是:使用命名绑定参数(:paramName)配合 text() 构造 SQL,并通过 params= 参数将值以字典形式传入 pd.read_sql_query()。同时,强烈建议在 EXEC 前添加 SET NOCOUNT ON; ——它能抑制 SQL Server 返回的“X 行受影响”消息,避免 pandas 因接收到多个结果集(如额外的消息集)而抛出 ValueError: SQL query returned no data 或解析失败。
以下是推荐的完整实现:
from sqlalchemy import create_engine, text
import pandas as pd
from sqlalchemy.engine import URL
# 1. 构建连接
connection_string = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=dev;DATABASE=Nemisis"
connection_url = URL.create("mssql+pyodbc", query={"odbc_connect": connection_string})
engine = create_engine(connection_url)
# 2. 定义参数与参数化查询(使用命名绑定)
book_id = 653
sql = text("""\
SET NOCOUNT ON;
EXEC dbo.LibraryBookData @BookId = :bookId;
""")
# 3. 执行并加载为 DataFrame —— params 必须传给 read_sql_query()
df = pd.read_sql_query(sql, engine, params={"bookId": book_id})
print(df.head())✅ 关键要点总结:
- ✅ 使用 :bookId(冒号前缀)作为 SQLAlchemy 命名参数占位符,不要拼接字符串(如 "@BookId = " + str(bookId)),否则易引发 SQL 注入与类型错误;
- ✅ params 参数必须传给 pd.read_sql_query(),而非 engine.connect().execute();
- ✅ SET NOCOUNT ON; 应置于 EXEC 之前,确保仅返回目标结果集;
- ✅ 参数名("bookId")需与 SQL 中的 :bookId 严格一致(大小写敏感);
- ✅ 若存储过程返回多个结果集,read_sql_query() 默认只取第一个;如需处理多结果,请改用 connection.execute() + cursor.fetchall() 手动解析。
遵循以上模式,即可稳定、安全、高效地从带参存储过程中获取结构化数据。










