Ормские ассоциации Горма Голанга

Я использую 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}}]

Как я могу выполнить такой запрос? Я пробовал использовать Preloads и Related без успеха (возможно, неправильно). Я не могу получить ожидаемый результат.

Ответы

Ответ 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}]

Вывод ожидается, но этот подход имеет фундаментальный недостаток, обратите внимание, что для каждого места необходимо выполнить другой запрос db, который вызывает проблему с проблемой 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}]

Этот подход вызовет только два запроса: один для всех мест и один для всех городов, где есть места. Этот подход хорошо оценивает количество мест и городов (всего два запроса во всех случаях).

Ответ 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)
    }
}

Ответ 3

Чтобы оптимизировать запрос, я использую "в состоянии" в той же ситуации

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

Не нужно зацикливаться на идентификаторах, просто pluck идентификаторы

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

towns := []Town{}
DB.Where(townIDs).Find(&towns)