
本文详解如何使用 Go 的 database/sql 正确查询多行数据并映射到结构体切片,解决布尔字段类型不匹配、单行/多行误用(QueryRow vs Query)及 JSON 序列化异常等常见问题。
本文详解如何使用 go 的 `database/sql` 正确查询多行数据并映射到结构体切片,解决布尔字段类型不匹配、单行/多行误用(`queryrow` vs `query`)及 json 序列化异常等常见问题。
在 Go 中从 PostgreSQL 查询结构化数据时,一个典型误区是:对多行结果错误使用 QueryRow() —— 它仅适用于预期返回单行的场景(如主键查询),而你的 SQL 条件 WHERE id_parent IS NULL 显然可能返回任意数量的按钮记录(3 行或更多),必须改用 db.Query() 配合 rows.Next() 迭代处理。
其次,b.Children 字段报错的根本原因在于 PostgreSQL 列类型与 Go 类型不兼容。若数据库中 has_children 列实际为 INTEGER(如存 0/1)、TEXT(如 'true'/'false')或 CHAR(1),直接扫描到 bool 类型会触发 sql.ErrNoRows 或类型转换错误。正确做法是先扫描到中间变量,再手动转换:
rows, err := connection.Query("SELECT id_printer, name, has_children FROM button WHERE id_parent IS NULL;")
if err != nil {
http.Error(w, "Database query failed", http.StatusInternalServerError)
log.Printf("DB query error: %v", err)
return
}
defer rows.Close()
var buttons []Button
for rows.Next() {
var id int
var name string
var hasChildren interface{} // 使用 interface{} 兼容多种数据库类型
if err := rows.Scan(&id, &name, &hasChildren); err != nil {
http.Error(w, "Failed to scan row", http.StatusInternalServerError)
log.Printf("Scan error: %v", err)
return
}
// 安全转换 has_children:支持 bool / int / string 等常见表示
children := false
switch v := hasChildren.(type) {
case bool:
children = v
case int64:
children = v != 0
case string:
children = strings.ToLower(v) == "true" || v == "1"
case nil:
children = false // NULL 视为 false
default:
log.Printf("Unexpected type for has_children: %T", v)
children = false
}
buttons = append(buttons, Button{
ID: id,
Name: name,
Children: children,
})
}
// 检查迭代过程中是否发生错误(如类型不匹配)
if err := rows.Err(); err != nil {
http.Error(w, "Error iterating rows", http.StatusInternalServerError)
log.Printf("Rows iteration error: %v", err)
return
}
// 封装为 Starter 结构体并返回 JSON
starter := Starter{Buttons: buttons}
response, err := json.MarshalIndent(starter, "", " ")
if err != nil {
http.Error(w, "JSON serialization failed", http.StatusInternalServerError)
log.Printf("JSON marshal error: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprint(w, string(response))
关键注意事项:
- ✅ 始终调用 rows.Close()(推荐用 defer)释放资源;
- ✅ 必须检查 rows.Err() —— rows.Next() 的最后一次调用可能因 I/O 错误失败,但循环已退出,该错误需显式捕获;
- ✅ 避免在 Scan 中直接使用结构体字段地址(如 &b.Children)处理不确定类型的列,优先通过中间变量转换;
- ✅ JSON 标签应使用反引号包裹且格式正确:json:"buttons" 而非 json:buttons(原结构体定义存在语法错误,需修正);
- ✅ 生产环境建议使用 sql.NullBool 处理可能为 NULL 的布尔列,或在 SQL 中用 COALESCE(has_children, false) 统一默认值。
通过以上改造,代码即可健壮支持任意数量的查询结果,并安全处理 PostgreSQL 到 Go 的类型映射,真正实现“动态”数据获取。










