LoginSignup
3
2

テーブル結合したほうが良い理由(Active Record)

Posted at

学習環境

Ruby on Rails 6.0.3
PostgreSQL 16.1

課題内容と要件

Ruby on RailsでActiveRecordを使ったテーブル結合の勉強をしている時のこと…
以下のような要件があったと想定した問題がありました。

【要件】注文されていないすべての料理を返すこと
 ※left_outer_joinsを使うこと

ER図は以下のようなイメージ
image.png

答えとして考えたこと

Railsガイドをパラパラと見ながら、
なるほど、”注文されていない”だから、注文されたデータ(order_foodテーブルのorder_id)を見て、where.notを使うのかなと思ったのでした。
ただし、要件にはleft_outer_joinsを使うこととある。

答えとそれぞれの処理

left_outer_joinsを使った答え

@foods = Food.left_outer_joins(:order_foods).where(order_foods: {food_id: nil })

where.notでもよくない?と思ったやつ

@foods = Food.where.not(id: OrderFood.all.pluck(:food_id))

left_outer_joinsを使った場合のRailsコンソールを使って確認した処理

[12] pry(main)> @foods = Food.left_outer_joins(:order_foods).where(order_foods: {food_id: nil })
Food Load (1.4ms)  SELECT "foods".* FROM "foods" LEFT OUTER JOIN "order_foods" ON "order_foods"."food_id" = "foods"."id" WHERE "order_foods"."food_id" IS NULL
=> [#<ChineseFood:0x000055c0fcdfad78 id: 497, shop_id: 87, name: "アメリカビーバーの洋菓子", created_at: Fri, 09 Feb 2024 03:27:24 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:24 UTC +00:00, price: 1090, type: "ChineseFood">,
#<JapaneseFood:0x000055c0fcdfac10 id: 209, shop_id: 40, name: "ホンドフクロウのカフェ", created_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, price: 1590, type: "JapaneseFood">,
#<ItalianFood:0x000055c0fcdfaad0 id: 437, shop_id: 74, name: "マンドリルのふぐ", created_at: Fri, 09 Feb 2024 03:27:23 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:23 UTC +00:00, price: 1690, type: "ItalianFood">,
#<ChineseFood:0x000055c0fcdfa968 id: 518, shop_id: 90, name: "シロサイのおでん", created_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, price: 490, type: "ChineseFood">,
#<ChineseFood:0x000055c0fcdfa850 id: 549, shop_id: 94, name: "オウサマペンギンのカフェ", created_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, price: 590, type: "ChineseFood">,
#<ItalianFood:0x000055c0fcdfa6e8 id: 268, shop_id: 49, name: "ショウジョウトキのうどん", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 1490, type: "ItalianFood">,
#<ChineseFood:0x000055c0fcdfa5a8 id: 397, shop_id: 67, name: "フンボルトペンギンのファミレス", created_at: Fri, 09 Feb 2024 03:27:22 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:22 UTC +00:00, price: 890, type: "ChineseFood">,
#<JapaneseFood:0x000055c0fcdfa468 id: 359, shop_id: 63, name: "アカカンガルーのハンバーグ", created_at: Fri, 09 Feb 2024 03:27:21 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:21 UTC +00:00, price: 1690, type: "JapaneseFood">,
#<ItalianFood:0x000055c0fcdfa300 id: 292, shop_id: 53, name: "モモイロペリカンのステーキ", created_at: Fri, 09 Feb 2024 03:27:20 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:20 UTC +00:00, price: 1190, type: "ItalianFood">,
#<ItalianFood:0x000055c0fcdfa1c0 id: 335, shop_id: 58, name: "マンドリルのそば", created_at: Fri, 09 Feb 2024 03:27:20 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:20 UTC +00:00, price: 990, type: "ItalianFood">,
#<ItalianFood:0x000055c0fcdfa0a8 id: 555, shop_id: 95, name: "アフリカタテガミヤマアラシのステーキ", created_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, price: 1590, type: "ItalianFood">,
#<ItalianFood:0x000055c0fcdf9f40 id: 541, shop_id: 93, name: "コモンリスザルのたこ焼き", created_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:25 UTC +00:00, price: 590, type: "ItalianFood">,
//省略
[13] pry(main)> @foods.size
=> 126

where.notを使った場合のRailsコンソールを使って確認した処理

[9] pry(main)> @foods = Food.where.not(id: OrderFood.all.pluck(:food_id))
(0.7ms)  SELECT "order_foods"."food_id" FROM "order_foods"
Food Load (4.0ms)  SELECT "foods".* FROM "foods" WHERE "foods"."id" NOT IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, //省略)
[["id", 1], ["id", 1], ["id", 1], ["id", 1], ["id", 1], ["id", 1] //省略]
=> [#<JapaneseFood:0x000055c0fc2e11d0 id: 209, shop_id: 40, name: "ホンドフクロウのカフェ", created_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, price: 1590, type: "JapaneseFood">,
#<JapaneseFood:0x000055c0fcbcbc50 id: 216, shop_id: 41, name: "アルダブラゾウガメのしゃぶしゃぶ", created_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, price: 1790, type: "JapaneseFood">,
#<ChineseFood:0x000055c0fcbaa398 id: 220, shop_id: 41, name: "マントヒヒのカレー", created_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:18 UTC +00:00, price: 1990, type: "ChineseFood">,
#<ChineseFood:0x000055c0fcdca9c0 id: 240, shop_id: 47, name: "アルパカの串揚げ", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 590, type: "ChineseFood">,
#<ItalianFood:0x000055c0fcdbbc90 id: 244, shop_id: 47, name: "ミーアキャットのうどん", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 1790, type: "ItalianFood">,
#<ItalianFood:0x00007fcdf80276c8 id: 247, shop_id: 47, name: "ジェフロイクモザルのかに", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 1290, type: "ItalianFood">,
#<ItalianFood:0x00007fcdf8027498 id: 249, shop_id: 47, name: "ライオンの和菓子", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 1290, type: "ItalianFood">,
#<ItalianFood:0x00007fcdf8026b60 id: 258, shop_id: 48, name: "シシオザルのすき焼き", created_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, updated_at: Fri, 09 Feb 2024 03:27:19 UTC +00:00, price: 490, type: "ItalianFood">,
//省略
[8] pry(main)> @foods.size
=> 126

結果として”注文されていないすべての料理を返すこと”についてはどちらも正解だが、
処理の時間、SQLも2回動かしている点でleft_outer_joinsを使うのが正解なのだとわかりました。

それぞれ何をしているかを文章にして考えてみる

left_outer_joinsを使った場合、

1.foodsテーブルとorider_foodsテーブルを左外部結合をする。

2.order_foodsテーブルにあるfood_idカラムの値がnilだった場合は返す。

where.notを使った場合、

1.order_foodsテーブルを全ての中から、food_idカラムのレコードの配列を取得する。

2.foodsテーブルのidカラムと比べて一致しないものを返す。

一見同じに見えるが、SQL文としては前者は1つで済むが後者は2つになってしまう。
また、一致しないものを返す処理というのは全ての情報を取得する必要もあるため、DBの処理としては重たくなってしまうため、テーブル結合をした方が処理も軽い。

まとめ

コードだけ見るとwhere.notのほうが短いので良のではと思ってしまいましたが、SQL文はむしろ複雑になっており、処理の時間もかかっていたことがわかりました。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2