2017-05-10 18 views
0

私はレールに3つのモデル、Author、Book、Pageがあります。ページは書籍に属し、本は著者に属するものである。レールの値が最大の関連モデルをRailsクエリ

class Author < ActiveRecord::Base 
    has_many :books 
end 

class Book < ActiveRecord::Base 
    belongs_to :author 
    has_many :pages 
end 

class Page < ActiveRecord::Base 
    belongs_to :book 
end 

ページモデルにはpage_numberという列があります。私はPostgresを使用しています。

私の質問は次のとおりです。著者には@authorがあると仮定すると、その著者の最後のページすべてをどのようにクエリしますか?言い換えれば、私はその著者によって書かれた各本の最後のページを望んでいます。私は働いていない、以下しようとしています:以下の2行が働く

Page.where(book_id: @author.books.pluck(:id)).select('MAX(page_number), *').group(:book_id) 

EDIT

を、私はより速く/クリーナー溶液を学ぶのが大好きだ:

all_pages = Page.where(book: @author.books) 
last_pages = all_pages.select{ |a| !all_pages.select{ |b| b.book_id == a.book_id}.any?{ |c| c.page_number > a.page_number } } 
+1

pluckではなく 'Page.where(book:@ author.books)'を使用してください。そうすれば、ActiveRecordは別のクエリでIDをフェッチするのではなく、クエリの一部としてスコープを使用できます。 – max

答えて

1

最も効率的な方法が活用されている可能性がありますpostgres' window functions

クエリこのように、activerecordの一般的な使用例には当てはまらないので、find_by_sqlを使用する必要がありますが、その価値は十分にあるかもしれません。

あなたのケースでは、書籍のIDを最初に取得するとよいでしょう。参加すると追加のサブクエリが役に立たないかもしれません。

書籍のIDのリストが@author.books.idsであるとします。私たちが望む次のものは、グループごとの最後のページを捨てることができるように、ブックでグループ化されたページのリストです。 1,2を問題の著者の書籍IDとする。

ポストグラムでウィンドウ関数とrank関数を使用して、ページがブックのパーティション(グループ)にランク付けされている結果セットを作成できます。これらのページのパーティションをページ番号でソートし、最大ページ番号(最後のページ)が各パーティションの先頭になるようにします。クエリは次のようになります。

select 
    *, 
    rank() over (
     partition by book_id order by page_number desc 
    ) as reverse_page_index 
from pages 
where book_id in (1,2) 

想像されたpages結果セットは次のようになります。

author 1, book 1, page 3, rank 1 
author 1, book 1, page 2, rank 2 
author 1, book 1, page 1, rank 3 
author 1, book 2, page 6, rank 1 
author 1, book 2, page 5, rank 2 
author 1, book 2, page 4, rank 3 
author 1, book 2, page 3, rank 4 
author 1, book 2, page 2, rank 5 
author 1, book 2, page 1, rank 6 

ページレコードは、ブックご​​とに区切られ、ページ番号の昇順でソートされ、パーティション間でランク付けされます。

我々はその後、唯一の最初のウィンドウの計算が行われ、我々はサブ選択ので、同じように使用することができます後に各書籍のために(最後のページ)をランク付けしたい場合:

我々は上記をフィルタリング
select * 
from 
(
    select 
     *, 
     rank() over (
      partition by book_id order by page_number desc 
     ) as reverse_page_index 
    from pages 
    where book_id in (1,2) 
) as pages 
where reverse_page_index = 1; 

想像された結果は、ランク(reverse_page_index)が1(すなわち最後のページ)であるページレコードだけに設定されます。

そして今、私たちの結果セットは次のようになります。

author 1, book 1, page 3, rank 1 
author 1, book 2, page 6, rank 1 

また、最後に変更または任意のあなたが必要とすることで、この結果セットを注文することができます。

この質問をfind_by_sqlで投げてください。使用するアクティブレコードオブジェクトがいくつかあります。

+0

この徹底的な答えをありがとうございました。私はそれを徹底的に調べます。 – laertiades

+0

サブクエリに名前を追加する必要がありました。その後、それは魅力のように働いた。ありがとう! – laertiades

+0

ああありがとう!更新しました。それを聞いてうれしい! – jbielick

関連する問題