Question

在Golang中处理一对多或多对多SQL关系时,将行映射到 struct 的最佳(高效的、推荐的、"类似Go的")方式是什么?

以下面的示例设置为例,我试图详细介绍一些方法,以及每种方法的优缺点,但不知道社区会推荐什么.

Requirements

  • 与PostgreSQL配合使用(可以是通用的,但不包括MySQL/Oracle特定的功能)
  • 效率-没有 brute 地强迫每一种组合
  • 无ORM-理想情况下仅使用database/sqljmoiron/sqlx

Example

For sake of clarity I have removed error handling

Models

type Tag struct {
  ID int
  Name string
}

type Item struct {
  ID int
  Tags []Tag
}

Database

CREATE TABLE item (
  id                      INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
);

CREATE TABLE tag (
  id                      INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
  name                    VARCHAR(160),
  item_id                 INT REFERENCES item(id)
);

Approach 1 - Select all Items, then select tags per item

var items []Item
sqlxdb.Select(&items, "SELECT * FROM item")

for i, item := range items {
  var tags []Tag
  sqlxdb.Select(&tags, "SELECT * FROM tag WHERE item_id = $1", item.ID)
  items[i].Tags = tags
}

Pros个个

  • 易于理解的
  • 通俗易懂

Cons

  • 效率低下,数据库查询的数量与项目数量成正比

Approach 2 - Construct SQL join and loop through rows manually

var itemTags = make(map[int][]Tag)

var items = []Item{}
rows, _ := sqlxdb.Queryx("SELECT i.id, t.id, t.name FROM item AS i JOIN tag AS t ON t.item_id = i.id")
for rows.Next() {
  var (
    itemID  int
    tagID   int
    tagName string
  )
  rows.Scan(&itemID, &tagID, &tagName)
  if tags, ok := itemTags[itemID]; ok {
    itemTags[itemID] = append(tags, Tag{ID: tagID, Name: tagName,})
  } else {
    itemTags[itemID] = []Tag{Tag{ID: tagID, Name: tagName,}}
  }
}
for itemID, tags := range itemTags {
  items = append(Item{
    ID: itemID,
    Tags: tags,
  })
}

Pros个个

  • 可以循环访问的单个数据库调用和游标,而不会占用太多内存

Cons

  • 使用多个连接和 struct 上的许多属性,开发起来复杂且困难
  • 表现不太好;更多的内存使用和处理时间,而不是更多的网络调用

Failed approach 3 - sqlx struct scanning

尽管失败了,我还是想包括这种方法,因为我发现它是我当前的目标,即效率与开发简单性相结合.我希望通过在每个 struct 字段sqlx上显式设置db标记,可以执行一些高级 struct 扫描

var items []Item
sqlxdb.Select(&items, "SELECT i.id AS item_id, t.id AS tag_id, t.name AS tag_name FROM item AS i JOIN tag AS t ON t.item_id = i.id")

不幸的是,这个错误显示为missing destination name tag_id in *[]Item,这让我相信StructScan还不够高级,不能递归地遍历各行(没有批评-这是一个复杂的场景)

Possible approach 4 - PostgreSQL array aggregators and 100

虽然我确信这将not工作,我已经包括了这个未经测试的选项,看看它是否可以改进,所以它may工作.

var items = []Item{}
sqlxdb.Select(&items, "SELECT i.id as item_id, array_agg(t.*) as tags FROM item AS i JOIN tag AS t ON t.item_id = i.id GROUP BY i.id")

当我有时间的时候,我会try 在这里做一些实验.

推荐答案

Postgres中的SQL:

create schema temp;
set search_path = temp;
create table item
(
  id INT generated by default as identity primary key
);

create table tag
(
  id      INT generated by default as identity primary key,
  name    VARCHAR(160),
  item_id INT references item (id)
);

create view item_tags as
select id,
  (
          select
            array_to_json(array_agg(row_to_json(taglist.*))) as array_to_json
          from (
                select tag.name, tag.id
                 from tag
                         where item_id = item.id
               ) taglist ) as tags
from item ;


-- golang query this maybe 
select  row_to_json(row)
from (
    select * from item_tags
) row;

然后Golang查询此SQL:

select  row_to_json(row)
from (
    select * from item_tags
) row;

并解封以进行 struct 化:

赞成的意见:

  1. 博士后管理数据之间的关系.使用sql函数添加/更新数据.

  2. golang管理商业模式和逻辑.

这条路很简单.

.

Go相关问答推荐

获取k8s群集作用域运算符的命名空间

运行add. inf,这样我们就可以在app.conf中使用. inf参数了?

Term~T中的类型不能是类型参数,但可以是引用类型参数的切片

困扰围棋官方巡回赛的S建议所有方法都使用同一类型的接收器

GoFR HTTP服务初始化中Open遥测传输和超时配置的说明

为什么没有正确生成这些元组?

如何使用 AWS sdk 在 Go 中正确解组 PartiQL 查询的结果?

如何将Golang测试用例的测试覆盖率值与特定阈值进行比较

使用Cookie身份验证的Gorilla Golang Websocket优化

Golang 网络应用程序安全性:您是否应该判断输入是否为有效的 utf-8?

使用自定义处理程序 nats golang 保留订阅方法

Dockerfile 问题 - 为什么找不到二进制 dlv - 没有这样的文件或目录

Go 泛型:自引用接口约束

为什么时间很短.睡眠时间比基准测试中要求的(约 300 ns)长?

如何从 tinygo webassembly 目标返回对象

Go:用于 XML 解码的嵌套 struct 中的提升字段

将 Simple Go Web 应用程序部署到 Elastic Beanstalk

如何扩充 ResponseWriter 的 Header() 返回的 map

如何访问Go 1.18泛型 struct (structs)中的共享字段

Beego - 我需要context.Context而不是 Beego 上下文