ActiveRecordでの内部結合
ActiveRecordでの内部結合
複雑なER図でのActiveRecordの使い方を勉強したので、いくつかの代表例をこちらにまとめます
ER図は、以下になります(IE記法で[1対多]の表記が逆になってしまっています)
・内部結合したテーブルに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わかりやすいプログラミング用語サイト