Rails запрос через ассоциацию ограничен самой последней записью? - PullRequest
30 голосов
/ 09 марта 2011
class User 
has_many :books

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

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

Что у меня до сих пор

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

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

Спасибо!

Ответы [ 6 ]

44 голосов
/ 10 марта 2011

Если вы не собираетесь использовать решение ruby ​​@ 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
14 голосов
/ 08 сентября 2015

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

Добавить столбец «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

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

User.last_book_completed
3 голосов
/ 09 марта 2011

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

User.all.select { |user| user.books.order("created_at desc").limit(1).complete }
2 голосов
/ 08 ноября 2013

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

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) и можем использовать его повторно при рассмотрении любого поднабора книг для пользователей (первая, последняя, ​​последняя n книг и т. Д.). Вам нужен последний 'uniq', иначе каждый пользователь появится дважды ..

0 голосов
/ 21 сентября 2017

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

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
}
0 голосов
/ 09 марта 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...