如何将m:n关系映射到切片字段?

时间:2014-08-30 13:26:41

标签: sql go relational-database foreign-key-relationship

我有一个结构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)
    }
}

虽然这会起作用,但这很烦人,因为映射到切片字段是一种常见的操作。

有没有办法在sqlsqlx的所有切片字段中制作上述通用?

2 个答案:

答案 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 []stringimplement 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)
}