英文:
Gorm Golang orm associations
问题
我正在使用Go语言和GORM ORM。我有以下的结构体。关系很简单,一个城镇有多个地点,一个地点属于一个城镇。
type Place struct {
ID int
Name string
Town Town
}
type Town struct {
ID int
Name string
}
现在我想查询所有的地点,并且获取它们对应的城镇的所有字段信息。这是我的代码:
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
places := []Place{}
db.Find(&places)
fmt.Println(places)
我的示例数据库中有以下数据:
/* places表 */
id name town_id
1 Place1 1
2 Place2 1
/* towns表 */
id name
1 Town1
2 Town2
我得到的结果是:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
但我期望得到的结果是(两个地点都属于同一个城镇):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
我该如何进行这样的查询?我尝试使用Preloads
和Related
,但没有成功(可能是方法不对)。我无法得到期望的结果。
英文:
I'm using Go with the GORM ORM.
I have the following structs. The relation is simple. One Town has multiple Places and one Place belongs to one Town.
type Place struct {
ID int
Name string
Town Town
}
type Town struct {
ID int
Name string
}
Now i want to query all places and get along with all their fields the info of the corresponding town.
This is my code:
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
places := []Place{}
db.Find(&places)
fmt.Println(places)
My sample database has this data:
/* places table */
id name town_id
1 Place1 1
2 Place2 1
/* towns Table */
id name
1 Town1
2 Town2
i'm receiving this:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
But i'm expecting to receive something like this (both places belongs to the same town):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
How can i do such query ? I tried using Preloads
and Related
without success (probably the wrong way). I can't get working the expected result.
答案1
得分: 53
TownID
必须作为外键指定。Place
结构如下所示:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
现在有不同的方法来处理这个问题。例如:
places := []Place{}
db.Find(&places)
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
这样做肯定会产生预期的结果,但请注意日志输出和触发的查询。
[4.76ms] SELECT * FROM "places"
[1.00ms] SELECT * FROM "towns" WHERE ("id" = '1')
[0.73ms] SELECT * FROM "towns" WHERE ("id" = '1')
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
输出是预期的,但这种方法有一个根本性的缺陷,注意到对于每个地点都需要进行另一个数据库查询,这会产生一个n + 1
问题。这可以解决问题,但随着地点数量的增长,情况会很快失控。
事实证明,好的方法非常简单,只需使用预加载。
db.Preload("Town").Find(&places)
就是这样,产生的查询日志如下:
[22.24ms] SELECT * FROM "places"
[0.92ms] SELECT * FROM "towns" WHERE ("id" in ('1'))
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
这种方法只会触发两个查询,一个是获取所有地点的查询,另一个是获取所有具有地点的城镇的查询。这种方法在地点和城镇的数量方面具有良好的可扩展性(在所有情况下只有两个查询)。
英文:
TownID
must be specified as the foreign key. The Place
struct gets like this:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
Now there are different approach to handle this. For example:
places := []Place{}
db.Find(&places)
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
This will certainly produce the expected result, but notice the log output and the queries triggered.
[4.76ms] SELECT * FROM "places"
[1.00ms] SELECT * FROM "towns" WHERE ("id" = '1')
[0.73ms] SELECT * FROM "towns" WHERE ("id" = '1')
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
The output is the expected but this approach has a fundamental flaw, notice that for every place there is the need to do another db query which produce a n + 1
problem issue. This could solve the problem but will quickly gets out of control as the amount of places grow.
It turns out that the good approach is fairly simple using preloads.
db.Preload("Town").Find(&places)
That's it, the query log produced is:
[22.24ms] SELECT * FROM "places"
[0.92ms] SELECT * FROM "towns" WHERE ("id" in ('1'))
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
This approach will only trigger two queries, one for all places, and one for all towns that has places. This approach scales well regarding of the amount of places and towns (only two queries in all cases).
答案2
得分: 7
你在Place结构体中没有指定towns的外键。只需在Place结构体中添加TownId字段即可解决。
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type Place struct {
Id int
Name string
Town Town
TownId int // 外键
}
type Town struct {
Id int
Name string
}
func main() {
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
db.CreateTable(&Place{})
db.CreateTable(&Town{})
t := Town{
Name: "TestTown",
}
p1 := Place{
Name: "Test",
TownId: 1,
}
p2 := Place{
Name: "Test2",
TownId: 1,
}
err := db.Save(&t).Error
err = db.Save(&p1).Error
err = db.Save(&p2).Error
if err != nil {
panic(err)
}
places := []Place{}
err = db.Find(&places).Error
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
if err != nil {
panic(err)
} else {
fmt.Println(places)
}
}
英文:
You do not specify the foreign key of towns in your Place struct. Simply add TownId to your Place struct and it should work.
package main
import (
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type Place struct {
Id int
Name string
Town Town
TownId int //Foregin key
}
type Town struct {
Id int
Name string
}
func main() {
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
db.CreateTable(&Place{})
db.CreateTable(&Town{})
t := Town{
Name: "TestTown",
}
p1 := Place{
Name: "Test",
TownId: 1,
}
p2 := Place{
Name: "Test2",
TownId: 1,
}
err := db.Save(&t).Error
err = db.Save(&p1).Error
err = db.Save(&p2).Error
if err != nil {
panic(err)
}
places := []Place{}
err = db.Find(&places).Error
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
if err != nil {
panic(err)
} else {
fmt.Println(places)
}
}
答案3
得分: 5
为了优化查询,我在相同的情况下使用了“in条件”。
places := []Place{}
DB.Find(&places)
keys := []uint{}
for _, value := range places {
keys = append(keys, value.TownID)
}
rows := []Town{}
DB.Where(keys).Find(&rows)
related := map[uint]Town{}
for _, value := range rows {
related[value.ID] = value
}
for key, value := range places {
if _, ok := related[value.TownID]; ok {
res[key].Town = related[value.TownID]
}
}
请注意,这只是代码的翻译部分,不包括任何其他内容。
英文:
To optimize query I use "in condition" in the same situation
places := []Place{}
DB.Find(&places)
keys := []uint{}
for _, value := range places {
keys = append(keys, value.TownID)
}
rows := []Town{}
DB.Where(keys).Find(&rows)
related := map[uint]Town{}
for _, value := range rows {
related[value.ID] = value
}
for key, value := range places {
if _, ok := related[value.TownID]; ok {
res[key].Town = related[value.TownID]
}
}
答案4
得分: 1
首先更改你的模型:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
其次,进行预加载:
https://gorm.io/docs/preload.html
英文:
First change your model:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
And second, make preloading:
https://gorm.io/docs/preload.html
答案5
得分: 1
摘要:预加载一对一关系:拥有一个,属于
急切预加载:
db.Preload("Orders").Preload("Profile").Find(&users)
使用内连接进行关联预加载:
db.Joins("Orders").Joins("Profile").Find(&users)
预加载所有关联:
db.Preload(clause.Associations).Find(&users)
英文:
Summary: preloading one-to-one relation: has one, belongs to
eager preload:
db.Preload("Orders").Preload("Profile").Find(&users)
join preload using inner join:
db.Joins("Orders").Joins("Profile").Find(&users)
preload all associations:
db.Preload(clause.Associations).Find(&users)
答案6
得分: 0
不需要循环遍历 ids,只需使用 pluck
方法获取 ids。
townIDs := []uint{}
DB.Model(&Place{}).Pluck("town_id", &placeIDs)
towns := []Town{}
DB.Where(townIDs).Find(&towns)
英文:
No need to loop for ids, just pluck
the ids
townIDs := []uint{}
DB.Model(&Place{}).Pluck("town_id", &placeIDs)
towns := []Town{}
DB.Where(townIDs).Find(&towns)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论