【问题标题】:Rails query for associated model with max column valueRails 查询具有最大列值的关联模型
【发布时间】:2017-05-11 04:38:00
【问题描述】:

我在 rails 中有 3 个模型:作者、书籍和页面。页面属于书籍,书籍属于作者:

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 模型有一个名为 page_number 的列。我正在使用 Postgres。

我的问题是:假设有作者@author,如何查询该作者的所有最后一页?换句话说,我想要那个作者写的每本书的最后一页。我正在尝试以下不起作用:

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

编辑

以下 2 行有效,但我很想了解更快/更清洁的解决方案:

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 } }

【问题讨论】:

  • 使用 Page.where(book: @author.books) 而不是采摘。这样,ActiveRecord 可以将范围用作查询的一部分,而不是在单独的查询中获取 id。

标签: sql ruby-on-rails postgresql activerecord


【解决方案1】:

最有效的方法可能是利用postgres' window functions

这样的查询不适合 activerecord 常见用例,因此您可能必须使用 find_by_sql,但它可能非常值得。

在您的情况下,首先获取图书 ID 可能是一个不错的选择,因为加入或额外的子查询可能没有优势——您的选择。

假设您有一个来自 @author.books.ids 的图书 ID 列表。我们想要的下一件事是“按”书“分组”的页面列表,这样我们就可以为每个组提取最后一页。让 1,2 成为相关作者的图书 ID。

我们可以在 postgres 中使用窗口函数和 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,您将可以使用一些活动记录对象。

【讨论】:

  • 非常感谢您的详尽回答。我会调查的。
  • 我必须为子查询添加一个名称。在那之后,它就像一个魅力。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多