2025, Nov 06 18:02
Исправляем ORA-01036 в executemany() oracledb: порядок привязок и столбцов
Как устранить ORA-01036 в executemany() oracledb: правильные плейсхолдеры, порядок привязок и столбцов, пример кода на Python для пакетных обновлений.
Исправляем ORA-01036 в executemany(): как связываемые переменные и порядок столбцов могут сломать пакетные обновления
Введение
Пакетные обновления через executemany() в Python обычно несложны, но малейшая ошибка со связываемыми переменными может привести к ORA-01036: illegal variable name/number. Парадокс в том, что тот же апдейт может проходить, если выполнять его по одной строке через execute(). Ниже — краткое объяснение, почему так происходит, и как правильно сопоставить плейсхолдеры SQL с вашими данными в oracledb (Python 3.9, oracledb 1.4.1).
Демонстрация проблемы
Проблемный сценарий использует пакетное выполнение с позиционными привязками. В тексте SQL один из плейсхолдеров заключён в кавычки, а порядок значений в кортежах не совпадает с порядком привязок.
df_pairs = src_df[["CONTRACT_NUM", "DOCID"]].copy()
cur = dbh.cursor()
batch_vals = [tuple(x) for x in df_pairs.values]
print(batch_vals)
stmt = "UPDATE USERBOX_MIDDLE_OFFICE.rvp_doc_partner_to_archive SET DOCID = :2 WHERE CONTRACT_NUM = ':1'"
cur.executemany(stmt, batch_vals)
rows_changed = cur.rowcount
dbh.commit()
cur.close()DatabaseError: ORA-01036: illegal variable name/number
Почему это падает
Корневая причина — некорректное использование связываемых переменных. Плейсхолдер ':1' в SQL взят в кавычки, из‑за чего он превращается в строковый литерал, а не в привязку. В результате Oracle не может подставить в него значение. Есть и тонкость: цифры в именах плейсхолдеров не связаны с индексами кортежа; если вы не используете именованные привязки, порядок в SQL определяет позиционное связывание. В ошибочном запросе DOCID присваивается через :2, а в WHERE используется ':1', при том что входные кортежи имеют вид (CONTRACT_NUM, DOCID). Несоответствие порядка значений и позиционных плейсхолдеров даёт путаницу и приводит к ошибке. Для сравнения, поштучный путь с execute() срабатывает, потому что значения вставляются напрямую в текст SQL (со всеми кавычками для строк), и привязки там не участвуют.
Решение и исправленный код
Уберите кавычки у плейсхолдеров и приведите порядок данных к порядку привязок в SQL. Поскольку в запросе мы присваиваем DOCID и фильтруем по CONTRACT_NUM, первое значение в каждом кортеже должно соответствовать DOCID, а второе — CONTRACT_NUM.
# Переупорядочьте столбцы в соответствии с позициями привязок в SQL
pairs_reordered = src_df[["DOCID", "CONTRACT_NUM"]].copy()
cur2 = dbh.cursor()
bulk_params = [tuple(v) for v in pairs_reordered.values]
print(bulk_params)
sql_fixed = """
UPDATE USERBOX_MIDDLE_OFFICE.rvp_doc_partner_to_archive
SET DOCID = :1
WHERE CONTRACT_NUM = :2"""
cur2.executemany(sql_fixed, bulk_params)
affected_total = cur2.rowcount
dbh.commit()
cur2.close()Так первое место привязки соответствует DOCID, а второе — CONTRACT_NUM. Строковые значения не нужно оборачивать в апострофы — драйвер сделает это сам. Важно лишь не брать в кавычки сам плейсхолдер и передавать значения в кортежах в том порядке, который указан в запросе.
Почему это важно
Понимание механики связываемых переменных критично для надёжных пакетных операций. Разница между позиционным связыванием и фактическим порядком ваших входных данных напрямую определяет, как executemany() подставляет значения в SQL. Осознание того, что числа в плейсхолдерах не связаны с индексами кортежей, убережёт от ложных предположений. И знание о том, что плейсхолдеры нельзя брать в кавычки, помогает избежать скрытых багов и ошибок во время выполнения.
Что запомнить
Если executemany() выдаёт ORA-01036, а поштучный execute() вроде бы работает, проверьте два момента. Во‑первых, плейсхолдеры не должны быть в кавычках. Во‑вторых, порядок столбцов в ваших данных должен соответствовать порядку позиционных привязок в SQL, либо используйте явные именованные привязки. С этими поправками пакетные обновления в Python 3.9 и oracledb 1.4.1 работают предсказуемо и стабильно.
Статья основана на вопросе на StackOverflow от Oleg Parunev и ответе от MT0.