SQLAlchemy+psycopg2下pandas.read_sql_query参数替换报dict错误
解决pandas read_sql_query传字典参数报错TypeError: dict is not a sequence的问题
包版本
- SQLAlchemy 2.0.40
- pandas 2.2.3
- psycopg2-binary 2.9.10
问题还原
尝试用pandas原生参数替换执行查询,简化后的SQL语句:
select * FROM public.bq_results br WHERE cast("eventDate" as date) between TO_DATE('%test_start_date', 'YYYYMMDD') AND TO_DATE('%test_end_date', 'YYYYMMDD') limit 10000
执行以下代码时触发错误:
df = pd.read_sql_query(query, self.__engine, params={"test_start_date": "20250101", "test_end_date": "20250131"})
错误信息:
TypeError: dict is not a sequence
数据库引擎初始化代码:
self.__engine = create_engine(f'postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}')
错误原因
psycopg2(PostgreSQL的Python驱动)不支持%test_start_date这种占位符格式,它只认位置占位符%s或者命名占位符%(param_name)s。当前的占位符写法会被驱动识别为普通字符串,同时因为参数传的是字典,驱动期望接收序列(比如列表、元组),所以抛出dict is not a sequence错误。
解决方案
方案1:使用psycopg2命名占位符修改SQL
修改SQL语句中的占位符为%(param_name)s格式,保持params传字典:
select * FROM public.bq_results br WHERE cast("eventDate" as date) between TO_DATE(%(test_start_date)s, 'YYYYMMDD') AND TO_DATE(%(test_end_date)s, 'YYYYMMDD') limit 10000
执行代码不变:
df = pd.read_sql_query(query, self.__engine, params={"test_start_date": "20250101", "test_end_date": "20250131"})
方案2:用SQLAlchemy文本查询配合命名参数(更规范)
用SQLAlchemy的text()方法包装查询语句,使用:param_name格式的占位符,SQLAlchemy会自动适配底层驱动的参数格式:
from sqlalchemy import text query = text(""" select * FROM public.bq_results br WHERE cast("eventDate" as date) between TO_DATE(:test_start_date, 'YYYYMMDD') AND TO_DATE(:test_end_date, 'YYYYMMDD') limit 10000 """) df = pd.read_sql_query(query, self.__engine, params={"test_start_date": "20250101", "test_end_date": "20250131"})
注意事项
- 不要直接用字符串拼接生成SQL,会存在SQL注入风险,参数化查询是更安全的方式。
- 不同数据库驱动的占位符格式不同:PostgreSQL(psycopg2)用
%s或%(name)s,MySQL用%s,SQLite用?,而SQLAlchemy的text()方法可以统一用:param_name格式,无需关心底层差异。
内容的提问来源于stack exchange,提问作者Christopher Singer
相关产品推荐
相关产品推荐

