Gorm Golang orm associations

huangapple go评论75阅读模式
英文:

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}}]

我该如何进行这样的查询?我尝试使用PreloadsRelated,但没有成功(可能是方法不对)。我无法得到期望的结果。

英文:

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)
英文:

Click For Full Docs

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)

huangapple
  • 本文由 发表于 2015年4月4日 00:13:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/29435783.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定