31

私は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

私はこれを受け取っています:

[{1 Place1 {0 }} {2 Mares Place2 {0 }}]

しかし、私はこのようなものを受け取ることを期待しています(両方の場所は同じ町に属しています):

[{1 Place1 {1 Town1}} {2 Mares Place2 {1 Town1}}]

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

4

6 に答える 6

52

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

この方法では、すべての場所に対して 1 つと、場所を持つすべての町に対して 1 つの 2 つのクエリのみがトリガーされます。このアプローチは、場所と町の量に関して適切にスケーリングされます (すべてのケースで 2 つのクエリのみ)。

于 2015-04-11T16:07:16.760 に答える
7

Place 構造体で町の外部キーを指定しません。Place 構造体に 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)
    }
}
于 2015-04-06T20:20:36.853 に答える
5

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

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]
    }
}
于 2016-04-17T18:48:21.610 に答える
0

pluckIDだけをループする必要はありません。

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

towns := []Town{}
DB.Where(townIDs).Find(&towns)
于 2019-09-22T02:54:40.713 に答える