はじめに
GORMを使って親と子テーブルのデータを両方取得するのに苦労したので、備忘録として残しておきます。
GORMは公式ドキュメントが丁寧に書かれているので、そちらも合わせてご覧いただくと良いかと思います。
結論
preload
を使って子テーブルのデータを取得しました。
preload
とは、Eager Loading(イーガーローディング)の一種で、メインのクエリと一緒に関連するデータを予め読み込む機能です。これにより、N+1問題を避けて子テーブルのデータを取得できます。
JOIN
とSELECT
を使うことでも子テーブルのデータを取得できたのですが、今回はより簡単に使えるpreload
を採用しました。
以下の構造体を例にPreload
の使い方を簡単に説明します。
// UserがOrderをhas manyで持つ
type User struct {
Name string
Email string
Orders []Order
}
// OrderがOrderDetailsをhas manyで持つ
type Order struct {
UserID uint
Total float64
State string
OrderDetails []OrderDetails
}
type OrderDetails struct {
ID uint
OrderID uint
Price float64
Quantity int
}
UserとそのOrdersを取得する
例えば、全てのUserとそれに紐づくOrdersを取得したい場合、以下のように記述します。
var users []User
db.Preload("Orders").Find(&users)
クエリは以下のようになります。
SELECT * FROM `users`;
SELECT * FROM `orders` WHERE `user_id` IN (ユーザーのIDリスト);
Ordersとそれに紐づくOrderDetailsを取得する
さらに、それぞれのOrderに紐づくOrderDetailsも取得したい場合は、以下のように記述します。
db.Preload("Orders.OrderDetails").Find(&users)
クエリは以下のようになります。
SELECT * FROM `users`;
SELECT * FROM `orders` WHERE `user_id` IN (ユーザーのIDリスト);
SELECT * FROM `order_details` WHERE `order_id` IN (注文のIDリスト);
Preloadするデータに条件指定を入れる
仮にOrder
テーブルからstate
がshipped
であるレコードのみを取得し、それに紐づくOrderDetails
も同時に取得したい場合は、以下のように記述します。
var users []User
db.Preload("Orders", "state = ?", "shipped").
Preload("Orders.OrderDetails").Find(&users)
クエリは以下のようになります。
SELECT * FROM `users`;
SELECT * FROM `orders` WHERE `state` = 'shipped' AND `user_id` IN (ユーザーのIDリスト);
SELECT * FROM `order_details` WHERE `order_id` IN (注文のIDリスト);
終わりに
上記のようにpreload
を使うことで、簡単に子テーブルのデータを取得することができました。簡単に使えるのは大きなメリットなのですが、SELCT
と併用できずテーブルのすべてのカラムを持ってしまうためデータが重くなるというデメリットもあります。必要とするデータに応じてJOIN``SELECT
と柔軟に使い分けるのがいいと思います。
機会があれば、JOIN``SELECT
を使ったデータの取得方法についても書いてみます。