Rails:在订购连接表

时间:2017-10-07 03:16:13

标签: ruby-on-rails

我有一个Book has_many Reviews,我已经为Book添加了一个类方法,以显示最近审核过的'我的主页上有书。我试过这个:

def self.recently_reviewed
  Book.joins(:reviews).order('reviews.created_at DESC').limit(5)
end

会产生许多重复记录,因此我尝试使用distinct,如下所示:

Book.joins(:reviews).order('reviews.created_at DESC').distinct.limit(5)

我也在.order之前和之后尝试了它,

ActiveRecord::StatementInvalid: PG::InvalidColumnReference: ERROR:  for SELECT DISTINCT, ORDER BY expressions must appear in select list

我对如何解决这个问题感到困惑,我是否应该降到.select以获得更大的灵活性?

3 个答案:

答案 0 :(得分:1)

Book.joins(:reviews).order('reviews.created_at DESC').distinct

您尝试从书籍和评论的联接表中选择不同的预订,然后根据reviews.created_at时间订购此不同预订列表。 SQL就像这样:

SELECT DISTINCT "books"."id" FROM "books" INNE JOIN "reviews" ON "reviews"."book_id" = "books"."id" ORDER BY reviews.created_at

有一个很好的理由说明为什么不允许这样做。因为结果是不确定的。想象一下,你对一本书有100条评论。在联接表中,您将有100行本书以及所有不同的评论。当您选择不同的列表时,您最终会得到本书的一行。这可以是连接表中的任何一个。然后根据此评论的created_at订购。由于评论可以是100中的任何一个,因此订单每次都可能不同。

这非常好:

Book.joins(:reviews).order('books.id DESC').distinct

因为它为该书选择的100行中的哪一行无关紧要,books.id是相同的。

回到你的问题。似乎你正试图获得最新评论的5本书。我没有看到一个简单的方法,但这是我的解决方案:

res = Review.group("book_id").maximum("created_at") # {book_id => create_at}, each book with its most recent review time
arr = res.to_a.sort { |a,b| b[1]<=>a[1] } #array sorted by created_at in desc order
arr.map{ |n| n[0] }.take(5)  #top 5 books' ids with most recent reviews

答案 1 :(得分:0)

这实际上是一个比ActiveRecord更简单的DB查询,因为它需要一个子查询。以下是我将如何使用查询完成此操作:

SELECT   book.*
FROM     book
         INNER JOIN books on reviews.book_id = books.id
WHERE    reviews.created_on = ( SELECT  MAX(reviews.created_at)
                              FROM    reviews
                              WHERE   reviews.book_id = books.id)
GROUP BY books.id

要将其转换为ActiveRecord,我会执行以下操作:

class Book
  scope :recently_reviewed, joins(:reviews)
    .where('reviews.created_on = (SELECT MAX(books.created_at) FROM reviews WHERE reviews.book_id = books.id)')
    .group('books.id')
end

然后,您可以通过执行以下操作获取所有上次审核的图书清单:

Book.recently_reviewed

然后,您可以通过

获取n个书籍的列表
Book.recently_reviewed.limit(n)

答案 2 :(得分:0)

你试过这个吗?

def self.recently_reviewed
  Review.preload(:book).order(created_at: :desc).limit(5).map(&:book)
end