2026, Jan 14 03:02
Как использовать плейсхолдеры %(name)s в pandas с psycopg2
Как избежать TypeError в pandas.read_sql_query с SQLAlchemy и psycopg2: используйте плейсхолдеры %(name)s для PostgreSQL вместо '%name'. Пошаговое исправление.
При использовании pandas для выполнения параметризованных SQL-запросов к PostgreSQL через SQLAlchemy и psycopg2 незаметное несоответствие синтаксиса плейсхолдеров может сорвать весь запрос. В связке SQLAlchemy 2.0.40, pandas 2.2.3 и psycopg2-binary 2.9.10 попытка положиться на встроенную подстановку параметров в pandas может привести к TypeError, если плейсхолдеры не соответствуют ожидаемому стилю драйвера.
Воспроизведение проблемы
Запрос выглядит безобидно, но виноват формат плейсхолдеров. Выполнение завершается ошибкой TypeError: dict is not a sequence.
qry_bad = """
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
"""
db_engine = create_engine(f'postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}')
out_df = pd.read_sql_query(
qry_bad,
db_engine,
params={"test_start_date": "20250101", "test_end_date": "20250131"}
)
Что происходит на самом деле
Поведение определяется pandas.read_sql_query и, точнее, лежащим под ним драйвером DB‑API. Функция передает связывание параметров драйверу, а тот ожидает плейсхолдеры в своем, специфичном для драйвера, формате. Документация прямо на это указывает и приводит требуемый стиль для psycopg2.
params : list, tuple или mapping, необязательно, по умолчанию: None
Список параметров, передаваемых методу execute. Синтаксис передачи параметров зависит от драйвера БД. Проверьте документацию своего драйвера, чтобы узнать, какой из пяти стилей, описанных в paramstyle из PEP 249, поддерживается. Например, для psycopg2 используется %(name)s, поэтому используйте params={'name': 'value'}.
В данном случае плейсхолдеры записаны как '%test_start_date' и '%test_end_date', что не соответствует формату %(name)s в psycopg2. Именно это несоответствие и вызывает TypeError при выполнении.
Как исправить
В самом SQL используйте стиль параметров %(name)s, принятый в psycopg2, и продолжайте передавать словарь в params. Больше ничего не нужно.
qry_ok = """
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
"""
db_engine = create_engine(f'postgresql://{self.user}:{self.password}@{self.host}:{self.port}/{self.database}')
out_df = pd.read_sql_query(
qry_ok,
db_engine,
params={"test_start_date": "20250101", "test_end_date": "20250131"}
)
Если сомневаетесь, как переписать '%test_start_date', ответ такой: нужно %(test_start_date)s. То же самое относится и к конечной дате.
Почему это важно
read_sql_query не приводит стили плейсхолдеров к одному виду. От вас ждут «диалект» драйвера. Для psycopg2 это шаблон %(name)s. Другой стиль приводит к запутанным ошибкам и потере времени, даже если сам словарь params составлен верно.
Выводы
Подключая pandas к PostgreSQL через psycopg2, всегда используйте плейсхолдеры %(name)s в SQL и передавайте словарь в params. Такое соответствие стилю драйвера устраняет TypeError и позволяет запросу выполняться как задумано.