ActiveRecordでの内部結合

ActiveRecordでの内部結合

複雑なER図でのActiveRecordの使い方を勉強したので、いくつかの代表例をこちらにまとめます
ER図は、以下になります(IE記法で[1対多]の表記が逆になってしまっています)

https://img.esa.io/uploads/production/attachments/2315/2020/06/25/53778/767019fe-45ce-4964-8a4f-b72307a40132.png

・内部結合したテーブルにwhereメソッドで条件を加える方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Film < ActiveRecord::Base
  has_many :inventories
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  belongs_to :inventory
end

上記のようなテーブル関係にある時に、[2005年に貸し出ししていて、まだ返って来ていない映画の一覧(Filmテーブルから取得するカラムは、film_idとtitle)]を取得する場合は、 取得する[Filmsのテーブル]と貸出状況が分かる[Rentalsテーブル]を使うことになるが、この両者のテーブルは互いに直接繋がっていないので、間にある[Inventoriesテーブル]を通して繋げる
イメージとしては、[Filmsテーブル]に[Inventoriesテーブル]をくっつけて、そこに[Rentalsテーブル]をくっつけることになる

以下のコードのように記述すると、[2005年に貸し出ししていて、まだ返って来ていない映画の一覧]を取得できる

Film.select(:film_id, :title).joins(inventories: :rentals).where(rental: { rental_date: Time.new(2005).beginning_of_year..Time.new(2005).end_of_year, return_date: nil })

+---------+------------------+
| film_id | title            |
+---------+------------------+
| 1       | ACADEMY DINOSAUR |
+---------+------------------+

# where(rental: { rental_date:  で内部結合したテーブルのうちrentalテーブルのrental_dateカラムという書き方ができる

・ネスト化した内部結合でテーブルをグループ化する方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Category < ActiveRecord::Base
  has_many :film_categories
  has_many :films, through: :film_categories
end
class Film < ActiveRecord::Base
  has_many :film_categories
  has_many :categories, through: :film_categories
  has_many :inventories
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  has_many :payments
  belongs_to :inventory
end
class Payment < ActiveRecord::Base
  belongs_to :rental
end

上記のようなテーブル関係にある時に、[カテゴリごとに売上を集計して上位5つ]を取得する場合は、 取得する[categoriesのテーブル]と売上が分かる[paymentsテーブル]を使うことになるが、この両者のテーブルは互いに直接繋がっていないので、間にある[filmsテーブル],[inventoriesテーブル],[rentalsテーブル]を通して繋げる

以下のコードのように記述すると、[カテゴリごとに売上を集計して上位5つ]を取得できる

Category.select("category.category_id, category.name, sum(payment.amount) as revenue").joins(films: { inventories: { rentals: :payments } }).group("category.category_id, category.name").limit(5).order(revenue: :desc)

+-------------+-----------+---------+
| category_id | name      | revenue |
+-------------+-----------+---------+
| 15          | Sports    | 5314.21 |
| 14          | Sci-Fi    | 4756.98 |
| 2           | Animation | 4656.3  |
| 7           | Drama     | 4587.39 |
| 5           | Comedy    | 4383.58 |
+-------------+-----------+---------+

# 内部結合をネスト化していたりして、記述が複雑だが以下の画像で説明しています

・複数の関連付けを内部結合する方法を以下に示します

テーブルのそれぞれの関係は、以下のようになります

class Film < ActiveRecord::Base
  has_many :film_actors
  has_many :actors, through: :film_actors
  has_many :inventories
end
class Actor < ActiveRecord::Base
  has_many :film_actors
  has_many :films, through: :film_actors
end
class Inventory < ActiveRecord::Base
  has_many :rentals
  belongs_to :film
end
class Rental < ActiveRecord::Base
  has_many :payments
  belongs_to :inventory
end
class Payment < ActiveRecord::Base
  belongs_to :rental
end

上記のようなテーブル関係にある時に、[JOE SWANKが出演している映画で、売れてるランキングトップ10]を取得する場合は、 取得する[filmsテーブル]と売上が分かる[paymentsテーブル]と俳優が分かる[actorsテーブル]を使うことになるが、[filmsテーブル]と[paymentsテーブル]、[filmsテーブル]と[paymentsテーブル]は互いに直接繋がっていないので、間にある[inventoriesテーブル],[rentalsテーブル],[paymentsテーブル]を通して繋げる

以下のコードのように記述すると、[JOE SWANKが出演している映画で、売れてるランキングトップ10]を取得できる

Film.select("film.film_id, film.title, sum(payment.amount) as revenue").joins(:actors, inventories: { rentals: :payments }).where(actor: { first_name: "JOE", last_name: "SWANK" }).limit(10).group(:film_id).order(revenue: :desc)

+---------+------------------------+---------+
| film_id | title                  | revenue |
+---------+------------------------+---------+
| 865     | SUNRISE LEAGUE         | 170.76  |
| 964     | WATERFRONT DELIVERANCE | 121.83  |
| 873     | SWEETHEARTS SUSPECTS   | 98.71   |
| 434     | HORROR REIGN           | 87.73   |
| 510     | LAWLESS VISION         | 86.84   |
| 889     | TIES HUNGER            | 80.89   |
| 74      | BIRCH ANTITRUST        | 74.89   |
| 514     | LEBOWSKI SOLDIERS      | 72.79   |
| 650     | PACIFIC AMISTAD        | 62.76   |
| 811     | SMILE EARRING          | 58.9    |
+---------+------------------------+---------+

# 内部結合をネスト化していたりして、記述が複雑だが以下の画像で説明しています

参考記事

【Rails】 joinsメソッドのテーブル結合からネストまでの解説書 | Pikawaka - ピカ1わかりやすいプログラミング用語サイト

Railsのjoinsで3つのテーブルをくっつける|えりりん|note

ActiveRecordにおけるGROUP BYの使い方 - Qiita