【问题标题】:Gorm Golang orm associationsGorm Golang orm 关联
【发布时间】:2015-06-08 18:08:21
【问题描述】:

我将 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 table */
id  name    town_id
 1  Place1        1
 2  Place2        1

/* towns Table */
id name
 1 Town1
 2 Town2

收到这个:

[{1 Place1 {0 }} {2 Mares Place2 {0 }}]

但我期待收到这样的东西(两个地方属于同一个城镇):

[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]

我怎样才能做这样的查询?我尝试使用 PreloadsRelated 没有成功(可能是错误的方式)。我无法获得预期的结果。

【问题讨论】:

  • 数据库中有什么?另外,你试过Related 功能吗?
  • @robbrit 我已经改进了问题以反映数据库示例数据。不,但我现在尝试了Related 函数,但我仍然没有成功获得预期的结果。

标签: orm go go-gorm


【解决方案1】:

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

这种方法只会触发两个查询,一个针对所有地点,一个针对所有有地点的城镇。这种方法适用于地点和城镇的数量(在所有情况下只有两个查询)。

【讨论】:

  • 我错了还是 Gorm 没有在数据库中创建 FK?
  • Alessio,你解决了吗?
  • 就性能而言,急切加载是一种不好的做法吗?我的意思是,每次执行 find 函数时,您都会从 Town 表中检索所有记录。
  • @Alessio 是的,它没有。如果您想在 DB 中使用外键,则需要在迁移期间明确编写 db.Model(&Place{}).AddForeignKey("town_id", "towns(id)", "RESTRICT", "RESTRICT") 之类的内容。是的,我知道你的问题已经快 5 年了,但是让它在这里为其他人:D
  • @Alveona 在 gorm v2 中没有这种方法
【解决方案2】:

您没有在 Place 结构中指定城镇的外键。只需将 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)
    }
}

【讨论】:

  • 太棒了。阅读此答案后现在正在工作。只是一个额外的注释/问题。这种方法似乎有一个 n+1 问题,因为对于每个地方,我们都会得到一个额外的查询。这是正确的行为吗?
  • 如果您不想自己指定加入,恐怕这是使用 Gorm 的唯一方法,是的,那么您将遇到 n+1 问题。 Gorm 确实支持将连接作为 sql 的瘦包装器:github.com/jinzhu/gorm#joins,但您必须指定查询。
  • 我对自己实现连接没有任何问题,但如果我这样做,我将需要创建另一个结构来处理查询结果。我只想重用现有的两个。我的假设可以吗?
【解决方案3】:

为了优化查询,我在相同情况下使用“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]
    }
}

【讨论】:

    【解决方案4】:

    无需循环查找 id,只需 pluck id

    townIDs := []uint{}
    DB.Model(&Place{}).Pluck("town_id", &placeIDs)
    
    towns := []Town{}
    DB.Where(townIDs).Find(&towns)
    

    【讨论】:

      【解决方案5】:

      首先改变你的模型:

      type Place struct {
        ID          int
        Name        string
        Description string
        TownID      int
        Town        Town
      }
      

      其次,进行预加载: https://gorm.io/docs/preload.html

      【讨论】:

        【解决方案6】:

        Click For Full Docs

        总结:预加载一对一关系:有一个,属于

        热切预载:

        db.Preload("Orders").Preload("Profile").Find(&users)
        

        使用内连接加入预加载:

        db.Joins("Orders").Joins("Profile").Find(&users)
        

        预加载所有关联:

        db.Preload(clause.Associations).Find(&users)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-01-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多