我有一个结构Person
:
type Person struct {
Id int64
Name string
Colors []string
}
应该从person
表中获取数据:
id | name
---------
1 | Joe
2 | Moe
和person_color
表:
person_id | color
-----------------
1 | black
1 | blue
2 | green
通过SELECT p.id, p.name, pc.color FROM person AS p INNER JOIN person_color AS pc ON pc.person_id = p.id
我将两个表合并到:
id | name | color
-----------------
1 | Joe | black
1 | Joe | blue
2 | Moe | green
目前我唯一想到的是在迭代rows.Next()
时手动映射颜色(注意:只是虚拟代码):
ps := make([]People, 0)
rows, err := db.Query("SELECT ...")
for rows.Next() {
var p Person
err := rows.Scan(&p.Id, &p.Name, &p.Color[0])
exists := false
for _, ps := range ps {
if ps.Id == p {
exists = true
ps.Color = append(ps.Color, p.Color)
}
}
if !exists {
ps = append(ps, p)
}
}
虽然这会起作用,但这很烦人,因为映射到切片字段是一种常见的操作。
答案 0 :(得分:1)
我几乎肯定会从SQL方面接近这一点。在PostgreSQL中,你可以使用array_agg来获取数组类型,这使得适当的Scanner实现应该能够抵抗奇怪的数据值:
SELECT p.id, p.name, pc.color FROM
person AS p INNER JOIN
array_agg(person_color) AS pc
ON
pc.person_id = p.id
GROUP BY p.id;
这将返回:
id | name | array_agg
----+------+--------------
1 | Joe | {black,blue}
2 | Moe | {green}
由您决定创建像type pgarraystring []string
和implement Scanner这样的Go类型,尽管我可能很快会在{I}中为PostgreSQL添加一些类型{1}}包。
在MySQL或SQLite中,您将缺少数组类型,但您可以使用github.com/jmoiron/sqlx/types
[1]来获得类似的结果。在其他数据库中,应该有一个类似的concat聚合,它与文本表示一起使用。
走这条路有几个原因。您出于某种原因使用SQL数据库;它应该能够以所需的格式返回您想要的数据;除非它真的会成为一个问题并且你已经对它进行了测量,否则它就会成为一个数据存储区。它还减少了通过线路发回的数据量以及光标完成的提取次数,因此通常它应该表现得更好。
[1]很抱歉,我无法发布指向GROUP_CONCAT
的链接,因为我没有任何StackOverflow信誉,但您应该能够谷歌搜索。
答案 1 :(得分:0)
我希望代码看起来更像这样。
// Assuming the context is in a function that can return _error_.
ps := make(map[int64]Person)
rows, err := db.Query("SELECT ...")
for rows.Next() {
var id int64
var name string
var color string
err := rows.Scan(&id, &name, &color)
if err != nil {
return err
}
p, ok := ps[id]
if !ok {
p.Id = id
p.Name = name
}
p.Colors = append(p.Colors, color)
ps[id] = p
}
您现在拥有的代码可能很昂贵,因为原始代码遍历所有人,对于连接表中的每一行。您可以通过映射快速跳转到正确的条目,而不是进行手动扫描。
此外,原始代码无法将已修改的人员保存回切片(如果已存在)。请记住,您正在使用 Person ,而不是 * Person 。如果你使用* Person,上面的代码可以改进为:
// Assuming the context is in a function that can return _error_.
ps := make(map[int64]*Person)
rows, err := db.Query("SELECT ...")
for rows.Next() {
var id int64
var name string
var color string
err := rows.Scan(&id, &name, &color)
if err != nil {
return err
}
p, ok := ps[id]
if !ok {
p := &Person{id, name, []string{color}}
ps[id] = p
continue
}
p.Colors = append(p.Colors, color)
}