web-dev-qa-db-ja.com

Gorm Golangオーム協会

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

このようなクエリを実行するにはどうすればよいですか? PreloadsRelatedを使用してみましたが成功しませんでした(おそらく間違った方法です)。期待どおりの結果が得られません。

28
Javier Cadiz

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つのクエリのみ)。

46
Javier Cadiz

クエリを最適化するには、同じ状況で「条件付き」を使用します

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

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)
    }
}
5
olif