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

如何用sqlc、PostgreSQL和Golang构建嵌套数据结构?

使用sqlc将PostgreSQL查询结果映射到带嵌套切片的Go结构体

问题场景

我在Golang项目中使用sqlc和PostgreSQL,数据库表结构如下:

CREATE TABLE users (
  username VARCHAR(255) PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL
);

CREATE TABLE flats (
  id SERIAL PRIMARY KEY,
  name VARCHAR(255) NOT NULL
);

CREATE TABLE user_flats (
  id SERIAL PRIMARY KEY,
  username VARCHAR(255) NOT NULL REFERENCES users(username),
  flat_id INTEGER NOT NULL REFERENCES flats(id)
);

需要根据公寓ID获取包含关联用户数组的公寓详情,期望返回的Go结构体为:

type FlatDetails struct {
    FlatID   int32
    FlatName string
    Users    []User
}

但使用JOIN关联查询时,sqlc生成的结构体将用户字段拆分为独立字段,而非嵌套切片。手动映射在复杂关联或大数据量场景下效率低下,希望找到更高效、符合惯例的方法,让sqlc直接将结果映射到带嵌套切片的结构体。


解决方案

方法1:PostgreSQL JSON聚合 + sqlc自定义类型映射

通过PostgreSQL的json_agg和json_build_object函数,将关联用户数据聚合成JSON数组,再通过sqlc的类型映射直接转为Go切片:

修改查询SQL

-- name: GetFlatDetails :one
SELECT
  f.id AS flat_id,
  f.name AS flat_name,
  json_agg(json_build_object('username', u.username, 'email', u.email)) AS users
FROM flats f
JOIN user_flats uf ON f.id = uf.flat_id
JOIN users u ON uf.username = u.username
WHERE f.id = $1
GROUP BY f.id, f.name;

配置sqlc类型映射

在sqlc.yaml中添加自定义类型映射,将PostgreSQL的json类型映射为[]User:

sql:
  - schema: "schema.sql"
    queries: "queries.sql"
    engine: "postgresql"
    gen:
      go:
        package: "db"
        out: "db"
        overrides:
          - db_type: "json"
            go_type:
              type: "[]User"
              import:
                path: "." # 替换为User类型所在的实际包路径

这样sqlc生成的查询函数返回的结构体中,users字段会直接是[]User切片,无需手动转换。

方法2:分两次查询(符合sqlc设计惯例)

如果不想依赖JSON序列化,可以拆分为两次独立查询,逻辑清晰且避免序列化开销:

定义sqlc查询

-- name: GetFlatByID :one
SELECT id AS flat_id, name AS flat_name FROM flats WHERE id = $1;

-- name: GetUsersByFlatID :many
SELECT u.username, u.email FROM users u
JOIN user_flats uf ON u.username = uf.username
WHERE uf.flat_id = $1;

业务层组装数据

// 先查询公寓基本信息
flat, err := db.GetFlatByID(ctx, flatID)
if err != nil {
    return nil, err
}
// 再查询该公寓关联的所有用户
users, err := db.GetUsersByFlatID(ctx, flatID)
if err != nil {
    return nil, err
}
// 组装成目标结构体
flatDetails := FlatDetails{
    FlatID:   flat.FlatID,
    FlatName: flat.FlatName,
    Users:    users,
}

这种方式虽然多一次数据库请求,但逻辑直观,在大数据量场景下性能更稳定,也贴合sqlc“让SQL与Go类型直接对应”的设计理念。

方法3:利用sqlc v1.17+的Embed特性

如果使用sqlc 1.17及以上版本,可以通过embed标签实现嵌套结构体映射。需结合json_agg聚合查询,在sqlc生成的结构体中直接嵌入[]User切片,简化类型映射配置。


内容的提问来源于stack exchange,提问作者adel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:35:38