J'utilise Go avec le GORM ORM . J'ai les structures suivantes. La relation est simple. Une ville a plusieurs lieux et un lieu appartient à une ville.
type Place struct {
ID int
Name string
Town Town
}
type Town struct {
ID int
Name string
}
Maintenant, je veux interroger tous les endroits et obtenir avec tous leurs champs les informations de la ville correspondante. Voici mon code:
db, _ := gorm.Open("sqlite3", "./data.db")
defer db.Close()
places := []Place{}
db.Find(&places)
fmt.Println(places)
Mon exemple de base de données contient ces données:
/* places table */
id name town_id
1 Place1 1
2 Place2 1
/* towns Table */
id name
1 Town1
2 Town2
je reçois ceci:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
Mais je m'attends à ce que reçoive quelque chose comme ça (les deux endroits appartiennent à la même ville):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
Comment puis-je faire une telle requête? J'ai essayé d'utiliser Preloads
et Related
sans succès (probablement dans le mauvais sens). Je n'arrive pas à obtenir le résultat escompté.
TownID
doit être spécifié comme clé étrangère. La structure Place
se présente comme suit:
type Place struct {
ID int
Name string
Description string
TownID int
Town Town
}
Maintenant, il existe différentes approches pour gérer cela. Par exemple:
places := []Place{}
db.Find(&places)
for i, _ := range places {
db.Model(places[i]).Related(&places[i].Town)
}
Cela produira certainement le résultat attendu, mais notez la sortie du journal et les requêtes déclenchées.
[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}]
La sortie est attendue mais cette approche a un défaut fondamental, notez que pour chaque endroit, il est nécessaire de faire une autre requête db qui produit un n + 1
problème de problème. Cela pourrait résoudre le problème, mais deviendra rapidement incontrôlable à mesure que le nombre de places augmente.
Il s'avère que l'approche bonne est assez simple en utilisant des précharges.
db.Preload("Town").Find(&places)
Voilà, le journal des requêtes produit est:
[22.24ms] SELECT * FROM "places"
[0.92ms] SELECT * FROM "towns" WHERE ("id" in ('1'))
[{1 Place1 {1 Town1} 1} {2 Place2 {1 Town1} 1}]
Cette approche ne déclenchera que deux requêtes, une pour tous les lieux et une pour toutes les villes qui ont des lieux. Cette approche évolue bien en fonction du nombre de lieux et de villes (seulement deux requêtes dans tous les cas).
Pour optimiser la requête, j'utilise "en condition" dans la même 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]
}
}
Vous ne spécifiez pas la clé étrangère des villes dans votre structure Place. Ajoutez simplement TownId à votre structure Place et cela devrait fonctionner.
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)
}
}