Go语言中基于user_id分组SQL查询结果并映射到嵌套结构体的实现方法
解决方法:通过Map分组整合用户与产品数据
你的问题核心是需要将SQL返回的扁平行数据,按照user_id聚合为嵌套结构,直接用StructScan到目标结构体只会得到扁平的重复用户记录,我们可以借助一个map来跟踪已处理的用户,实现分组整合。
具体步骤:
- 定义临时结构体接收单行数据:SQL返回的每行是单个用户-产品对,用一个和查询结果字段完全匹配的临时结构体承载每行数据,避免直接扫描嵌套结构体时的字段不匹配问题。
- 使用Map跟踪用户:用
user_id作为map的key,对应值为*UserProduct指针,快速判断用户是否已被处理,避免重复创建实例。 - 逐行处理并聚合产品:遍历查询结果时,检查map中是否存在该用户:
- 不存在则创建新的
UserProduct实例,初始化用户信息并添加当前产品; - 存在则直接将当前产品追加到该用户的
Products切片中。
- 不存在则创建新的
- 将Map转换为切片返回:最后把map中的所有
UserProduct实例提取到切片中,得到需要的嵌套结构。
修改后的完整代码:
import ( "context" "log" "github.com/jmoiron/sqlx" ) type Product struct { // 调整为单数命名,更符合Go规范 ProductID int `json:"product_id"` ProductName string `json:"product_name"` } type UserProduct struct { UserID int `json:"user_id"` UserName string `json:"user_name"` Products []Product `json:"product"` } // 临时结构体,完全匹配SQL查询的字段映射 type userProductRow struct { UserID int `db:"user_id"` UserName string `db:"name"` ProductID int `db:"product_id"` ProductName string `db:"product_name"` } func GetUserProducts(ctx context.Context, db *sqlx.DB) ([]UserProduct, error) { // 用map跟踪已处理的用户,key为user_id userMap := make(map[int]*UserProduct) // 补全原SQL中缺失的FROM users子句 query := `SELECT users.id as user_id, users.name, products.name as product_name, products.id as product_id FROM users JOIN user_products ON users.id = user_products.user_id JOIN products ON products.id = user_products.product_id` rows, err := db.QueryxContext(ctx, query) if err != nil { return nil, err } defer rows.Close() // 确保查询结束后释放资源 for rows.Next() { var row userProductRow if err := rows.StructScan(&row); err != nil { log.Printf("扫描行数据失败: %v", err) continue } // 检查用户是否已在map中存在 user, exists := userMap[row.UserID] if !exists { // 新建用户实例并初始化 user = &UserProduct{ UserID: row.UserID, UserName: row.UserName, Products: []Product{}, } userMap[row.UserID] = user } // 将当前产品添加到用户的产品列表 user.Products = append(user.Products, Product{ ProductID: row.ProductID, ProductName: row.ProductName, }) } // 检查遍历过程中是否出现错误 if err := rows.Err(); err != nil { return nil, err } // 将map中的值转换为切片返回 var userProducts []UserProduct for _, u := range userMap { userProducts = append(userProducts, *u) } return userProducts, nil }
关键细节说明:
- 原SQL语句漏写了
FROM users,已在代码中补全,否则会执行失败; - 添加
defer rows.Close()确保数据库连接资源被正确释放; - 最后将map转切片时,顺序可能和SQL返回的用户顺序不一致,如果需要保持顺序,可以额外维护一个记录user_id顺序的切片;
- 完善了错误处理逻辑,覆盖查询、扫描及遍历结束后的错误检查。
这样修改后,返回的[]UserProduct序列就会按照user_id分组,每个用户的多个产品都被整合到Products切片中,最终序列化后的JSON格式完全符合你的期望。
内容的提问来源于stack exchange,提问作者Tammam
相关产品推荐
相关产品推荐

