有效地将一对多多对多数据库映射到 Golang 中的结构
Efficiently mapping one-to-many many-to-many database to struct in Golang
问题
在 Golang 中处理一对多或多对多 SQL 关系时,将行映射到的最佳(高效,推荐,"Go-like")方式是什么一个结构?
采用下面的示例设置,我尝试详细说明一些方法以及每种方法的优缺点,但想知道社区推荐什么。
要求
- 与 Postgre 一起工作SQL(可以是通用的但不包括 MySQL/Oracle 特定功能)
- 效率 - 无需暴力破解每个组合
- 无 ORM - 理想情况下仅使用
database/sql
和 jmoiron/sqlx
例子
为了清楚起见,我删除了错误处理
型号
type Tag struct {
ID int
Name string
}
type Item struct {
ID int
Tags []Tag
}
数据库
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)
);
方法 1 - Select 所有项目,然后每个项目 select 个标签
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 = ", item.ID)
items[i].Tags = tags
}
优点
- 简单
- 易于理解
缺点
- 随着数据库查询数量与项目数量成比例增加,效率低下
方法 2 - 构造 SQL 手动连接并遍历行
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,
})
}
优点
- 可以在不消耗太多内存的情况下循环的单个数据库调用和游标
缺点
- 在结构上使用多个连接和许多属性进行复杂且更难开发
- 性能不太好;更多的内存使用和处理时间与更多的网络调用
方法 3 失败 - sqlx 结构扫描
尽管失败了,但我想包括这种方法,因为我发现它是我当前的目标,即提高效率并简化开发。我希望通过在每个结构字段上显式设置 db
标记 sqlx
可以进行一些高级结构扫描
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
不够先进,无法递归循环遍历行(没有批评 - 这是一个复杂的场景)
可能的方法 4 - PostgreSQL 数组聚合器和 GROUP BY
虽然我确信这 不会 起作用,但我已经包含了这个未经测试的选项,看看是否可以对其进行改进,因此它 可能 工作。
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")
当我有时间的时候,我会尝试 运行 在这里做一些实验。
我可以推荐另一种我以前用过的方法。
在这种情况下,您在查询中创建了一个 json 标签,并 return 它。
优点:您调用了 1 次数据库,它聚合了数据,您所要做的就是将 json 解析为一个数组。
缺点:有点丑。请随时 bash 我。
type jointItem struct {
Item
ParsedTags string
Tags []Tag `gorm:"-"`
}
var jointItems []*jointItem
db.Raw(`SELECT
items.*,
(SELECT CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT('id', id,
'name', name
)
),
']'
)) as parsed_tags
FROM items`).Scan(&jointItems)
for _, o := range jointItems {
var tempTags []Tag
if err := json.Unmarshall(o.ParsedTags, &tempTags) ; err != nil {
// do something
}
o.Tags = tempTags
}
编辑:代码可能表现得很奇怪,所以我发现在移动时使用临时标签数组比使用相同的结构更好。
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;
并解组到结构:
亲:
postgres 管理数据的关系。使用 sql 函数添加/更新数据。
golang 管理业务模型和逻辑。
这很简单。
.
您可以使用 https://github.com/jackskj/carta 中的 carta.Map()
它会自动跟踪有很多关系。
问题
在 Golang 中处理一对多或多对多 SQL 关系时,将行映射到的最佳(高效,推荐,"Go-like")方式是什么一个结构?
采用下面的示例设置,我尝试详细说明一些方法以及每种方法的优缺点,但想知道社区推荐什么。
要求
- 与 Postgre 一起工作SQL(可以是通用的但不包括 MySQL/Oracle 特定功能)
- 效率 - 无需暴力破解每个组合
- 无 ORM - 理想情况下仅使用
database/sql
和jmoiron/sqlx
例子
为了清楚起见,我删除了错误处理
型号
type Tag struct {
ID int
Name string
}
type Item struct {
ID int
Tags []Tag
}
数据库
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)
);
方法 1 - Select 所有项目,然后每个项目 select 个标签
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 = ", item.ID)
items[i].Tags = tags
}
优点
- 简单
- 易于理解
缺点
- 随着数据库查询数量与项目数量成比例增加,效率低下
方法 2 - 构造 SQL 手动连接并遍历行
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,
})
}
优点
- 可以在不消耗太多内存的情况下循环的单个数据库调用和游标
缺点
- 在结构上使用多个连接和许多属性进行复杂且更难开发
- 性能不太好;更多的内存使用和处理时间与更多的网络调用
方法 3 失败 - sqlx 结构扫描
尽管失败了,但我想包括这种方法,因为我发现它是我当前的目标,即提高效率并简化开发。我希望通过在每个结构字段上显式设置 db
标记 sqlx
可以进行一些高级结构扫描
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
不够先进,无法递归循环遍历行(没有批评 - 这是一个复杂的场景)
可能的方法 4 - PostgreSQL 数组聚合器和 GROUP BY
虽然我确信这 不会 起作用,但我已经包含了这个未经测试的选项,看看是否可以对其进行改进,因此它 可能 工作。
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")
当我有时间的时候,我会尝试 运行 在这里做一些实验。
我可以推荐另一种我以前用过的方法。
在这种情况下,您在查询中创建了一个 json 标签,并 return 它。
优点:您调用了 1 次数据库,它聚合了数据,您所要做的就是将 json 解析为一个数组。
缺点:有点丑。请随时 bash 我。
type jointItem struct {
Item
ParsedTags string
Tags []Tag `gorm:"-"`
}
var jointItems []*jointItem
db.Raw(`SELECT
items.*,
(SELECT CONCAT(
'[',
GROUP_CONCAT(
JSON_OBJECT('id', id,
'name', name
)
),
']'
)) as parsed_tags
FROM items`).Scan(&jointItems)
for _, o := range jointItems {
var tempTags []Tag
if err := json.Unmarshall(o.ParsedTags, &tempTags) ; err != nil {
// do something
}
o.Tags = tempTags
}
编辑:代码可能表现得很奇怪,所以我发现在移动时使用临时标签数组比使用相同的结构更好。
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;
并解组到结构:
亲:
postgres 管理数据的关系。使用 sql 函数添加/更新数据。
golang 管理业务模型和逻辑。
这很简单。
.
您可以使用 https://github.com/jackskj/carta 中的 carta.Map() 它会自动跟踪有很多关系。