You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 01:02:27