简体   繁体   English

Rails查询通过仅限于最新记录的关联?

[英]Rails query through association limited to most recent record?

class User 
has_many :books

I need a query that returns: 我需要一个返回的查询:

Users whose most recent book has :complete => true. 最近一本书的用户:complete => true。 ie If a user's most recent book has :complete => false, I do not want them in my result . 即如果用户最近的书有:complete => false,我不希望它们出现在我的结果中

What I have so far 到目前为止我有什么

User.joins(:books).merge(Book.where(:complete => true))

which is a promising start but does not give me the result I need. 这是一个很有希望的开始,但没有给我我需要的结果。 I've tried adding an .order("created_on desc").limit(1) 我尝试添加.order("created_on desc").limit(1)
to the end of the above query but then I end up with only one result when I am expecting many. 到上面的查询结束,但是当我期待很多时,我最终只得到一个结果。

Thanks! 谢谢!

If you aren't going to go with @rubyprince's ruby solution, this is actually a more complex DB query than ActiveRecord can handle in it's simplest form because it requires a sub-query. 如果您不打算使用@ ruby​​prince的ruby解决方案,这实际上是一个比ActiveRecord可以处理的最复杂的数据库查询,因为它需要一个子查询。 Here's how I would do this entirely with a query: 以下是我将如何使用查询完成此操作:

SELECT   users.*
FROM     users
         INNER JOIN books on books.user_id = users.id
WHERE    books.created_on = ( SELECT  MAX(books.created_on)
                              FROM    books
                              WHERE   books.user_id = users.id)
         AND books.complete = true
GROUP BY users.id

To convert this into ActiveRecord I would do the following: 要将其转换为ActiveRecord,我将执行以下操作:

class User
  scope :last_book_completed, joins(:books)
    .where('books.created_on = (SELECT MAX(books.created_on) FROM books WHERE books.user_id = users.id)')
    .where('books.complete = true')
    .group('users.id')
end

You can then get a list of all users that have a last completed book by doing the following: 然后,您可以通过执行以下操作获取具有上次完成的书籍的所有用户的列表:

User.last_book_completed

This adds a little overhead, but saves complexity and increases speed later when it matters. 这增加了一点开销,但是在重要时节省了复杂性并提高了速度。

Add a "most_recent" column to books. 在书籍中添加“most_recent”列。 Make sure you add an index. 确保添加索引。

class AddMostRecentToBooks < ActiveRecord::Migration

  def self.change
    add_column :books, :most_recent, :boolean, :default => false, :null => false
  end
  add_index :books, :most_recent, where: :most_recent  # partial index

end

Then, when you save a book, update most_recent 然后,当您保存书籍时,请更新most_recent

class Book < ActiveRecord::Base

  on_save :mark_most_recent

  def mark_most_recent
    user.books.order(:created_at => :desc).offset(1).update_all(:most_recent => false)
    user.books.order(:created_at => :desc).limit(1).update_all(:most_recent => true)
  end

end

Now, for your query 现在,为您的查询

class User < ActiveRecord::Base

  # Could also include and preload most-recent book this way for lists if you wanted
  has_one :most_recent_book, -> { where(:most_recent => true) }, :class_name => 'Book'

  scope :last_book_completed, -> { joins(:books).where(:books => { :most_recent => true, :complete => true })
end

This allows you to write it like this and the result is a Relation to be used with other scopes. 这允许您像这样编写它,结果是一个与其他范围一起使用的Relation。

User.last_book_completed

我想不出一种方法可以在一个查询中执行它,但你可以这样做:

User.all.select { |user| user.books.order("created_at desc").limit(1).complete }

I recently came across a similar problem and here is how I solved it: 我最近遇到了类似的问题,这就是我如何解决它:

most_recent_book_ids = User.all.map {|user| user.books.last.id }
results = User.joins(:books).where('books.id in (?) AND books.complete == ?', most_recent_book_ids, true).uniq

This way we only use ActiveRecord methods (no extra SQL) and can reuse it when considering any subset of books for users (first, last, last n books, etc...). 这样我们只使用ActiveRecord方法(没有额外的SQL),并且可以在考虑用户的任何书籍子集时使用它(第一页,最后一页,最后一本书等等)。 You need the last 'uniq' cause otherwise each user would appear twice.. 您需要最后一个'uniq'原因,否则每个用户将出现两次..

based off Pan's answer but with a left join instead of an inner join, and no grouping: 基于Pan的答案,但是使用左连接而不是内部连接,并且没有分组:

scope :with_last_book_complete, -> {
  subquery = Book.select(:id)
                 .where("books.user_id = users.id")
                 .order(created_at: :desc).limit(1)

  joins(<<-SQL).where(books: { complete: true })
    LEFT JOIN books
      ON books.user_id = users.id
      AND books.id = (#{subquery.to_sql})
  SQL
}

you should be using scopes here - they will make your life much simple (this is a rails3 example) 你应该在这里使用范围 - 它们会让你的生活变得简单(这是一个rails3的例子)

In your book.rb model 在你的book.rb模型中

scope :completed, where("complete = ?", true)
scope :recent, order("created_on ASC")

Then you can call User.books.recent.completed to get what you want 然后你可以调用User.books.recent.completed来获得你想要的东西

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM