Подтвердить что ты не робот

Rails-запрос через ассоциацию, ограниченную самой последней записью?

class User 
has_many :books

Мне нужен запрос, который возвращает:

Пользователи, у которых самая последняя книга: complete = > true. т.е. если у самой последней книги пользователя: complete = > false, я не хотят их в моем результате.

Что я до сих пор

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

который является многообещающим началом, но не дает мне результата, который мне нужен. Я попробовал добавить .order("created_on desc").limit(1)
до конца вышеприведенного запроса, но затем я получаю только один результат, когда ожидаю многих.

Спасибо!

4b9b3361

Ответ 1

Если вы не собираетесь использовать рубиновое решение @rubyprince, это на самом деле более сложный запрос БД, чем ActiveRecord может обрабатывать в нем простейшую форму, потому что для этого требуется подзапрос. Вот как бы я сделал это полностью с запросом:

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

Чтобы преобразовать это в 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

Затем вы можете получить список всех пользователей, у которых есть последняя заполненная книга, выполнив следующие действия:

User.last_book_completed

Ответ 2

Это добавляет немного накладных расходов, но экономит сложность и увеличивает скорость позже, когда это имеет значение.

Добавьте столбец "most_recent" в книги. Убедитесь, что вы добавили индекс.

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

Затем, когда вы сохраняете книгу, обновите 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

Теперь, для вашего запроса

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

Это позволяет вам записать его так, и результат будет связан с другими областями.

User.last_book_completed

Ответ 3

Я не могу подумать о способе сделать это в одном запросе, но вы можете сделать:

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

Ответ 4

Недавно я столкнулся с подобной проблемой, и вот как я ее решил:

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

Таким образом, мы используем только методы ActiveRecord (без дополнительного SQL) и можем повторно использовать его при рассмотрении любого подмножества книг для пользователей (сначала, последние, последние русские книги и т.д.). Вам понадобится последняя "uniq" причина, иначе каждый пользователь появится дважды.

Ответ 5

вы должны использовать области здесь - они сделают вашу жизнь очень простой (это пример rails3)

В вашей модели book.rb

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

Затем вы можете вызвать User.books.recent.completed, чтобы получить то, что вы хотите

Ответ 6

основан на Панорате ответа, но с левым соединением вместо внутреннего соединения и без группировки:

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
}