Question
在Golang中处理一对多或多对多SQL关系时,将行映射到 struct 的最佳(高效的、推荐的、"类似Go的")方式是什么?
以下面的示例设置为例,我试图详细介绍一些方法,以及每种方法的优缺点,但不知道社区会推荐什么.
Requirements
- 与PostgreSQL配合使用(可以是通用的,但不包括MySQL/Oracle特定的功能)
- 效率-没有 brute 地强迫每一种组合
- 无ORM-理想情况下仅使用
database/sql
和jmoiron/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 在这里做一些实验.