Ормские ассоциации Горма Голанга
Я использую 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)