
DuckDB 不支持直接将 Python 列表作为 IN(?) 参数传入,需借助 SELECT UNNEST(?) 将参数展开为行集,再用于 IN 子查询。
duckdb 不支持直接将 python 列表作为 `in(?)` 参数传入,需借助 `select unnest(?)` 将参数展开为行集,再用于 `in` 子查询。
在 DuckDB 中,预编译语句(prepared statement)对参数类型的处理较为严格:? 占位符仅接受标量值(如整数、字符串),不支持直接传入列表或数组以匹配 IN(...) 语法。若强行传入 [[4,5,6]],DuckDB 会尝试将整个列表强制转换为 INTEGER[] 类型,但当前版本尚未实现 BIGINT → INTEGER[] 的隐式转换,因而抛出 ConversionException。
✅ 正确解决方案是利用 DuckDB 内置函数 UNNEST,将参数化列表展开为结果集,并嵌入子查询中:
import duckdb
# 示例数据(实际中可来自真实表)
conn = duckdb.connect()
conn.execute("CREATE TABLE person(id INTEGER, name VARCHAR)")
conn.execute("INSERT INTO person VALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie'), (4, 'Diana'), (5, 'Eve')")
# 动态 ID 列表
target_ids = [2, 4, 6]
# ✅ 正确写法:IN(SELECT UNNEST(?))
result = conn.execute(
"SELECT * FROM person WHERE id IN (SELECT UNNEST(?))",
[target_ids]
).fetchall()
print(result)
# [(2, 'Bob'), (4, 'Diana')]? 关键要点:
- UNNEST(?) 接收一个 Python 列表(如 [2, 4, 6]),并将其转为单列结果集(每项一行);
- IN (SELECT UNNEST(?)) 等价于逻辑上的 id IN (2, 4, 6),语义清晰且完全参数化,避免 SQL 注入风险;
- 参数必须以 单元素列表形式传递:[target_ids],而非 target_ids 或 [[...]];
- 支持任意长度列表(包括空列表),空列表时 UNNEST([]) 返回空结果集,IN (...) 自然不匹配任何行,行为安全。
⚠️ 注意事项:
- 当前 DuckDB(v1.0+)仍不支持 IN ? 直接展开语法(如某些数据库的 IN (?) + 数组绑定),因此 UNNEST 是标准、可靠且推荐的方式;
- 若需频繁执行此类查询,建议复用连接对象和预编译语句提升性能(DuckDB 自动缓存执行计划);
- UNNEST 也支持嵌套结构(如 UNNEST([{'a':1}, {'a':2}])),但在 IN 场景中仅需一维数值列表。
该方案兼顾安全性、可读性与兼容性,是 DuckDB 生态中处理动态 IN 查询的事实标准做法。

















