
DuckDB 不支持直接将 Python 列表作为 IN(?) 的参数,需借助 SELECT UNNEST(?) 将参数展开为行集,再用于 IN 子句,这是当前最可靠且兼容性最佳的参数化动态查询方案。
duckdb 不支持直接将 python 列表作为 `in(?)` 的参数,需借助 `select unnest(?)` 将参数展开为行集,再用于 `in` 子句,这是当前最可靠且兼容性最佳的参数化动态查询方案。
在 DuckDB 中,预处理语句(prepared statement)对数组类型的支持有限:当尝试使用 WHERE id IN(?) 并传入 Python 列表(如 [4, 5, 6])时,DuckDB 会尝试将整个列表映射为单个标量值,导致类型转换失败(如报错 Unimplemented type for cast (BIGINT -> INTEGER[])),因为 IN(?) 期望的是多个独立值,而非一个数组对象。
✅ 正确解法是利用 DuckDB 内置函数 UNNEST 将参数列表展开为结果集,并嵌入子查询中参与 IN 判断:
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, 5]
# ✅ 正确写法:通过 SELECT UNNEST(?) 展开列表
result = conn.execute(
"SELECT * FROM person WHERE id IN (SELECT UNNEST(?))",
[target_ids]
).fetchall()
print(result)
# 输出: [(2, 'Bob'), (4, 'Diana'), (5, 'Eve')]
? 关键要点:
- UNNEST(?) 接收一个 Python 列表(或元组),将其转为单列结果集,每项占一行;
- IN (SELECT UNNEST(?)) 实质上将列表“横向展开”为 IN 可识别的值集合,语义等价于 IN (2, 4, 5);
- 参数必须以单元素列表形式传递(即 [target_ids]),因为 DuckDB 预处理语句只接受扁平参数序列;
- 该方案完全参数化,可有效防止 SQL 注入,同时保持查询计划复用优势。
⚠️ 注意事项:
- UNNEST 要求输入为一维列表;嵌套列表(如 [[1,2], [3]])会引发错误;
- 若列表为空([]),SELECT UNNEST(?) 返回空结果集,整个 IN 条件恒为 FALSE,查询返回空结果——符合预期逻辑,无需额外判空;
- 当前版本(v1.0+)尚未支持 IN ? 直接展开语法(如 PostgreSQL 的 IN ANY(?)),因此 UNNEST 是官方推荐且稳定的方式。
未来 DuckDB 可能优化数组绑定机制,但在现阶段,IN (SELECT UNNEST(?)) 是实现安全、高效、动态 IN 查询的黄金标准。











