GORM ORM でGoを使用しています。次の構造体があります。関係は簡単です。 1つの町には複数の場所があり、1つの場所は1つの町に属します。
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
私はreceiveこれ:
[{1 Place1 {0 }} {2 Mares Place2 {0 }}]
しかし、私はexpectingのようなものを受け取ります(両方の場所は同じ町に属します):
[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]
このようなクエリを実行するにはどうすればよいですか? Preloads
とRelated
を使用してみましたが成功しませんでした(おそらく間違った方法です)。期待どおりの結果が得られません。
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クエリを実行する必要があることに注意してください。これで問題を解決できますが、場所の量が増えるとすぐに制御できなくなります。
goodのアプローチは、プリロードを使用するとかなり簡単であることがわかります。
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つのクエリのみがトリガーされます。1つはすべての場所、もう1つは場所があるすべての町です。このアプローチは、場所と町の量に関して適切にスケーリングされます(すべての場合で2つのクエリのみ)。
クエリを最適化するには、同じ状況で「条件付き」を使用します
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]
}
}
Place構造体では、町の外部キーを指定しません。 PlaceIdを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 //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)
}
}