
PostgreSQL 本身不支持 ? 作为参数占位符,尤其在 WITH 子句的 VALUES 表达式中使用时会报语法错误;Go 标准库 database/sql 仅适配驱动协议,需借助 sqlx 等扩展库实现 ? 到 $1, $2... 的自动转换。
postgresql 本身不支持 `?` 作为参数占位符,尤其在 `with` 子句的 `values` 表达式中使用时会报语法错误;go 标准库 `database/sql` 仅适配驱动协议,需借助 `sqlx` 等扩展库实现 `?` 到 `$1, $2...` 的自动转换。
PostgreSQL 原生只接受 $1, $2, … 这类位置参数(PostgreSQL-style placeholders),而 ? 是 MySQL 或 SQLite 的占位符风格,并非 SQL 标准,也不被 lib/pq 或 pgx 等 PostgreSQL 驱动直接解析。当你在 WITH ... VALUES(?, ?), (?, ?) 中使用 ?,PostgreSQL 服务端会将其视为非法符号,导致 syntax error at or near "," —— 因为它根本未进入参数绑定流程,而是在 SQL 解析阶段就失败了。
要正确实现动态批量更新,推荐使用 jmoiron/sqlx 库,它提供了 sqlx.In() 和 db.Rebind() 两个关键能力:
-
sqlx.In()将切片参数展开为?占位符序列(如IN (?)→IN (?, ?, ?)); -
db.Rebind()将?自动转换为当前数据库驱动所需的格式(对 PostgreSQL 即转为$1, $2, $3...)。
以下是适配你原始需求的完整示例:
import (
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // 或 github.com/jackc/pgx/v5/pgxpool
)
// 构造 name/enabled 对列表(支持任意长度)
params := []interface{}{"group_a", true, "group_b", false}
// 使用 sqlx.In 构建 VALUES 子句(注意:需手动构造占位符模式)
// 注意:sqlx.In 不直接支持多列 VALUES,因此推荐拆解为两层
names := []string{"group_a", "group_b"}
enableds := []bool{true, false}
// 方案一:用 IN + 子查询(更简洁、安全)
query := `
WITH Tmp(name, enabled) AS (
SELECT * FROM UNNEST(?::text[], ?::boolean[])
)
UPDATE table_groups
SET enabled = (SELECT enabled FROM Tmp WHERE table_groups.name = Tmp.name)
WHERE table_groups.name IN (SELECT name FROM Tmp)
`
// 转换为 PostgreSQL 占位符
query = db.Rebind(query)
_, err := db.Exec(query, pq.Array(names), pq.Array(enableds))
⚠️ 注意事项:
UNNEST(?::text[], ?::boolean[])是 PostgreSQL 推荐方式,比VALUES(...)更易参数化;- 必须导入
github.com/lib/pq并使用pq.Array()包装切片(若用pgx,则改用pgtype.TextArray等对应类型);- 切勿拼接字符串生成 SQL,否则将引入 SQL 注入风险;
- 若坚持用
VALUES,需动态生成$1,$2,...占位符(例如通过strings.Repeat("($%d, $%d),", n)),但可读性与安全性均不如UNNEST方案。
总结:? 在 PostgreSQL 中无效是设计使然,而非 Go 实现缺陷。拥抱 PostgreSQL 原生语法($n + UNNEST/ARRAY)并配合 sqlx.Rebind(),是兼顾安全性、可维护性与性能的最佳实践。









