
PostgreSQL 原生不支持 ? 作为参数占位符,尤其在 WITH 子句的 VALUES 表达式中会触发语法错误;需改用 $1, $2 等 PostgreSQL 标准占位符,或借助 sqlx 等库实现自动转换。
postgresql 原生不支持 `?` 作为参数占位符,尤其在 `with` 子句的 `values` 表达式中会触发语法错误;需改用 `$1`, `$2` 等 postgresql 标准占位符,或借助 sqlx 等库实现自动转换。
在 PostgreSQL 中,? 并非原生支持的参数占位符——它属于 SQLite 或 MySQL 的语法惯例。PostgreSQL 严格要求使用 位置参数(positional placeholders),即 $1, $2, $3 … 这种形式。当你在 WITH 子句中使用 VALUES(?, ?), (?, ?) 时,PostgreSQL 解析器会在遇到 ? 时直接报错 syntax error at or near ",",因为它根本无法识别该符号,更不会将其视为待绑定参数。
✅ 正确写法(适配 PostgreSQL)应改为:
WITH Tmp(name, enabled) AS (
VALUES($1, $2), ($3, $4)
)
UPDATE table_groups
SET enabled = (
SELECT enabled
FROM Tmp
WHERE table_groups.name = Tmp.name
)
WHERE table_groups.name IN (SELECT name FROM Tmp);
对应 Go 代码中需按顺序传入四个参数:
_, err := db.Exec(query,
"group_a", true,
"group_b", false,
)
if err != nil {
log.Fatal(err)
}
⚠️ 注意事项:
- 不可混用
?和$n:PostgreSQL 驱动(如lib/pq或pgx)仅解析$n形式,?会被当作字面量处理,导致 SQL 语法错误; -
WITH ... VALUES(...)中每个元组的字段数必须一致,且参数总数需与$n编号严格匹配(例如 2 元组 × 2 行 = 4 个参数 →$1–$4); - 若需动态生成多行
VALUES(如批量更新 N 条记录),建议预计算参数数量并构造对应$1,$2,...,$N占位符序列,避免硬编码。
? 替代方案:使用 sqlx.In + Rebind(适用于需兼容多种方言的场景)
若项目已依赖 sqlx,可通过 sqlx.In 生成 IN 子句,再调用 db.Rebind() 将 ? 自动转为 $1, $2 等 PostgreSQL 兼容格式:
// 示例:动态构建 name 列表(但注意:VALUES 多行仍需手动构造)
names := []string{"group_a", "group_b"}
enableds := []bool{true, false}
// ⚠️ 注意:sqlx.In 不直接支持 VALUES 多行展开,需自行拼接
// 更推荐:对 VALUES 场景,优先手写 $1,$2,... 并用 slice 传参
query := `WITH Tmp(name, enabled) AS (VALUES($1,$2),($3,$4))
UPDATE table_groups SET enabled = (SELECT enabled FROM Tmp WHERE table_groups.name = Tmp.name)
WHERE table_groups.name IN (SELECT name FROM Tmp)`
_, err := db.Exec(query, names[0], enableds[0], names[1], enableds[1])
? 总结:
PostgreSQL 的参数化查询必须遵循其原生协议——使用 $n 占位符。? 是常见误区,源于开发者跨数据库迁移习惯。在 WITH、VALUES、子查询等复杂结构中,务必显式声明位置参数,并确保 Go database/sql 驱动(如 pgx)正确绑定。坚持使用 $1, $2 不仅避免语法错误,还能提升查询计划复用率与安全性。









