在Golang中处理一对多或多对多SQL关系时,将行映射到结构的最佳方式(高效,推荐的"类似Go")是什么?
以下面的示例设置为例,我尝试详细介绍每种方法的优缺点,但是想知道社区的建议。
要求
- 适用于PostgreSQL(可以是通用的,但不包括MySQL / Oracle特定的功能)
- 效率-不会强求每种组合
-
没有ORM-理想情况下仅使用
database/sql 和jmoiron/sqlx
例
为了清楚起见,我已删除错误处理
楷模
1 2 3 4 5 6 7 8 9 | TYPE Tag struct { ID INT Name string } TYPE Item struct { ID INT Tags []Tag } |
数据库
1 2 3 4 5 6 7 8 9 | 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-选择所有项目,然后为每个项目选择标签
1 2 3 4 5 6 7 8 | 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 } |
优点
- 简单
- 容易明白
缺点
- 数据库查询数量与项目数量成正比时效率低下
方法2-构造SQL连接并手动遍历行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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结构扫描
尽管失败了,但我还是希望包括这种方法,因为我发现它是我当前的效率与开发简单性相结合的目标。我的希望是通过在每个struct字段上显式设置
1 2 | 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") |
不幸的是,由于出现
可能的方法4-PostgreSQL数组聚合器和
虽然我确定这将行不通,但我已经包含了这个未经测试的选项,以查看是否可以对其进行改进,使其可行。
1 2 | 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") |
有时间时,我将尝试在此处进行一些实验。
-
关于您的sqlx尝试:如何定义
Item 结构类型? -
@LeGEC-在上面我的问题的
Models 部分中进行了详细说明-在此简单示例中,只是一个ID int 和Tags []Tag 数组。 实际上,这要复杂得多。
postgres中的sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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:
1 2 3 4 | SELECT row_to_json(ROW) FROM ( SELECT * FROM item_tags ) ROW; |
并解组结构:
优点:
postgres管理数据关系。 使用sql函数添加/更新数据。
golang管理业务模型和逻辑。
这是简单的方法。
。
- 谢谢你! 我喜欢这个主意,对责任有明确的了解是很不错的。 同样,这是另一个需要维护的映射,虽然比较冗长但得到了好评。
我可以建议我以前使用过的另一种方法。
在这种情况下,您可以在查询中为标签制作一个json,然后将其返回。
优点:您对数据库有1个调用,该调用将聚合数据,而您要做的就是将json解析为一个数组。
缺点:有点难看。 随时为我ash。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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 } |
- 感谢您的替代方法。 Ive授予了upvote,因为我认为这绝对是一个选择,尽管您是对的-这不是很漂亮-通过将其添加到具有20多个字段的结构中,可以提高兼容性,并保留另一个映射。 但是,我不会接受,因为我希望有一个更"优雅"的解决方案。
- 这真的很聪明! 丑陋可以解决,但这可以与常规group by和自定义json解组一起使用