2014-08-30 39 views
0

我有一个结构Person如何映射m:n与切片场的关系?

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的所有切片字段上做出上述通用?

+1

另一种通用的方法是拆分查询。查询名称并使用空的“颜色”创建“人物”对象。然后查询所有名称的所有颜色,并将其添加到人员。这种方法扩展到多个外键,或称为“切片字段”,因为你称它们为: – Andomar 2014-08-30 14:06:32

+0

@Andomar嗯我认为使用另一个查询不会解决这个问题,因为你仍然需要以某种方式减少返回的行。目前我认为我们能做的最好的是一个'Peoples'类型,它有一个'Reduce()'方法,将所有'People'与相同的'People.Id'合并。但仍然怀疑对此有如此少的反应 - 对我来说似乎并不奇特:) – bodokaiser 2014-08-30 14:47:39

+0

是的,这是一个[非常常见的问题](http://en.wikipedia.org/wiki/Object-relational_impedance_mismatch) 。作为一名老程序员,我的建议是不要花太多时间将数据从表格移动到对象。这个问题本身并不值得一提。 – Andomar 2014-08-30 15:35:26

回答

1

我几乎肯定会从SQL方面接近这一点。在PostgreSQL可以使用array_agg得到数组类型回来,这给予适当的扫描仪实现应该是怪异的数据值耐:

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} 

它是由你来创建一个Go类型,如type pgarraystring []stringimplement Scanner,尽管有可能我会在github.com/jmoiron/sqlx/types包中尽快为PostgreSQL添加一些这些类型。

在MySQL或SQLite中,您将缺乏数组类型,但您可以使用GROUP_CONCAT [1]来实现类似的结果。在其他数据库中,应该有一个类似的concat集合,它可以与文本表示一起使用。

走这条路线有几个原因。出于某种原因,您正在使用SQL数据库;它应该能够以所需的格式向您返回所需的数据;除非它真的会成为一个问题,并且你已经测量了它,重新回到它,这是它作为数据存储的优势。它还减少了通过线路发回的数据量以及由光标完成的提取次数,所以通常它应该表现得更好。

[1]对不起,我无法发布到GROUP_CONCAT的链接,因为我没有任何StackOverflow声望,但是您应该可以对其进行Google搜索。

+0

对于MySQL'SELECT p.id,p.name,GROUP_CONCAT(pc.color)AS color FROM person AS p INNER JOIN person_color AS pc ON pc.person_id = p.id GROUP BY p.id' works(important is the' GROUP BY p.id')。扫描仪只需要一个简单的类型开关,使用'strings.Split(“黑色,蓝色”,“,”)'来使用。 – bodokaiser 2014-08-30 20:29:05

+0

真的非常感谢这个答案。它使得在SQL中处理数据数组非常简单,并且可能在Go世界之外非常有用:) – bodokaiser 2014-08-30 20:29:54

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,上面的代码可以被细化为:

// 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) 
}