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 и позволяет запросу выполняться как задумано.