Gorm Golang orm协会
我在用围棋。 我有以下结构。关系很简单。一个城镇有多个地方,一个地方属于一个城镇Gorm Golang orm协会,orm,go,go-gorm,Orm,Go,Go Gorm,我在用围棋。 我有以下结构。关系很简单。一个城镇有多个地方,一个地方属于一个城镇 type Place struct { ID int Name string Town Town } type Town struct { ID int Name string } 现在我想查询所有的地方,并与他们的所有领域的信息对应的城镇。 这是我的代码: db, _ := gorm.Open("sqlite3", "./data.db")
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 table */
id name town_id
1 Place1 1
2 Place2 1
/* towns Table */
id name
1 Town1
2 Town2
我收到了这封信:
[{1地点1{0}}{2母马地点2{0}]
但是我希望能收到这样的东西(这两个地方属于同一个城镇):
[{1地点1{1城镇1}}{2母马地点2{1城镇1}}]
我怎样做这样的查询?我尝试使用
预加载
和相关
但没有成功(可能是错误的方法)。我无法获得预期的工作结果。您没有在Place结构中指定towns的外键。只需将TownId添加到Place结构中,它就可以工作了
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)
}
}
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}]
输出是预期的,但这种方法有一个基本缺陷,请注意,对于每个位置,都需要执行另一个db查询,这会产生n+1
问题。这可以解决问题,但随着名额的增加,很快就会失控
事实证明,good方法使用预加载相当简单
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}]
这种方法只会触发两个查询,一个用于所有地点,另一个用于所有有地点的城镇。这种方法可以很好地扩展地方和城镇的数量(在所有情况下只有两个查询)。为了优化查询,我在相同的情况下使用“in-condition”
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]
}
}
无需循环查找ID,只需
pull
ID即可
townIDs := []uint{}
DB.Model(&Place{}).Pluck("town_id", &placeIDs)
towns := []Town{}
DB.Where(townIDs).Find(&towns)
首先更改您的模型:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
第二,进行预加载:
数据库中有什么?另外,您是否尝试了
相关的
功能?@robbrit我改进了问题,以反映数据库样本数据。没有,但是我现在尝试了相关的
函数,但仍然没有成功获得预期的结果。太好了。在阅读了这个答案后,他现在正在工作。只需再补充一条说明/问题。这种方法似乎有一个n+1问题,因为对于每个地方,我们都会得到一个额外的查询。这是正确的行为吗?如果您不想自己指定一个连接,恐怕这是使用Gorm的唯一方法,是的,那么您将有一个n+1问题。Gorm确实支持将联接作为sql上的瘦包装器:但是您必须指定查询。我对自己实现联接没有任何问题,但是如果我这样做,我将需要创建另一个结构来处理查询结果。我只想重用现有的两个。我的假设正确吗?是我错了还是Gorm没有在DB中创建FK?Alessio,你解决了吗?从性能方面来说,急切加载是一种不好的做法吗?我的意思是,每次执行find函数时,您都在从Town表中检索所有记录。@alesio是的,它没有。如果希望在DB中使用外键,则需要在迁移期间显式编写类似于DB.Model(&Place{}).AddForeignKey(“town_id”、“towns(id)”、“RESTRICT”、“RESTRICT”)
的内容。是的,我知道你的问题已经问了将近5年了,但让它在这里为其他人:D@Alveonagorm v2中没有此类方法