2014-08-30 37 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) 
}