在created_at之前按关联顺序排序

时间:2013-09-12 18:43:44

标签: mysql ruby-on-rails activerecord

假设我有以下内容:

class Movie < ActiveRecord::Base
  has_many :reviews
end

我想列出用户在审核之前未先审核的新制作的电影。

所以这就像合并这两个查询一样:

@reviewed_movies   = Movie.joins(:reviews)
                          .where("reviews.user_id != ?", user.id)
                          .order("created_at DESC")

@unreviewed_movies = Movie.joins(:reviews)
                          .where("reviews.user_id = ?", user.id)
                          .order("created_at DESC")

@movies   = @reviewed_movies.all + @unreviewed_movies.all

任何人都知道如何在一个查询中执行此操作?

7 个答案:

答案 0 :(得分:3)

不熟悉Ruby语法,但您可以在下面执行查询,例如用户ID为“3”

(SELECT * FROM `movies` m  LEFT JOIN  reviews r ON (m.id =r.movie_id)
WHERE user_id =3 ORDER BY created_at DESC)
UNION ALL
(SELECT * FROM `movies` 
LEFT JOIN  reviews r ON (m.id =r.movie_id)
WHERE user_id !=3 ORDER BY created_at DESC)

或直接使用用户条件

加入您的表格
(SELECT * FROM `movies` m  
LEFT JOIN  reviews r ON (m.id =r.movie_id AND user_id =3)
ORDER BY created_at DESC)
UNION ALL
(SELECT * FROM `movies` 
LEFT JOIN  reviews r ON (m.id =r.movie_id AND user_id !=3 )
ORDER BY created_at DESC)

Mysql Union

Using union and order by clause in mysql

Rails 3 ActiveRecord: UNION

Union of 2 active record relation object in rails 3

答案 1 :(得分:1)

@movies   = Movie.joins(:reviews)
                 .select("movies.*, reviews.id, SUM(reviews.user_id = #{user.id}) AS counter ")
                 .group("movies.id ")
                 .order("counter ASC, movies.created_at DESC")

这将创建counter,这是电影对当前用户的评论数。

答案 2 :(得分:0)

SQL类似于:

SELECT movies.*, reviews.*
  FROM movies
  LEFT OUTER
  JOIN reviews
    ON reviews.movie_id = movies.id
   AND reviews.user_id  = :user_id
 ORDER BY IF(reviews.id IS NULL, 0, 1) ASC
         ,movies.created_at DESC

http://sqlfiddle.com/#!2/97117/1

的SQLFiddle

结果看起来像:

ID  NAME       CREATED_AT                       MOVIE_ID  USER_ID  REVIEW
1   movie 1    September, 16 2013 12:29:26+0000   (null)   (null)  (null)
3   movie 3    September, 16 2013 10:29:26+0000   (null)   (null)  (null)
2   movie 2    September, 16 2013 11:29:26+0000        2        1  movie 2 review

不知道Ruby,但我认为你正在寻找这样的东西:

@movies = Movie.joins('LEFT OUTER JOIN reviews ON reviews.movie_id = movies.id AND reviews.user_id = ?', user.id)
               .order('IF(reviews.movie_id IS NULL, 0, 1) ASC, created_at DESC')

答案 3 :(得分:0)

一个小小的更新:

@movies = Movie.includes(:reviews).where('reviews.movie_id = movies.id AND reviews.user_id = ?', user.id).order('reviews.movie_id ASC, created_at DESC')

使用“includes”是左外连接的rails活动记录方式:)

显然它也有一些副作用,因此可以使用eagerload。(rails 3.x +)

@movies = Movie.eager_load(:reviews).where('reviews.movie_id = movies.id AND reviews.user_id = ?', user.id).order('reviews.movie_id ASC, created_at DESC')

如果不需要审核ID的条件,我猜。使大量数据更快地触摸;)

答案 4 :(得分:0)

如果您的reviews表上有movie_id,这可能在rails 4中有效。它应该使用子选择执行单个查询。

movies = Movie.where.not(id: Review.select("movie_id").where(user_id: user.id)).order("counter ASC, movies.created_at DESC")

自动子选择是rails 4中的新选择。

答案 5 :(得分:0)

注意:我不会说“ruby”,我会写SQL。

首先,我试图在一个查询中使用它:

-- add a boolean column computed from the join
SELECT m.*, (r.id != null) as reviewed
FROM movies m LEFT JOIN reviews r ON r.movieID = m.id AND r.userID = @userID
ORDER BY reviewed ASC, created_at DESC

接下来的评论:如果您的列表变大,您可能希望避免在每个请求上对未编制索引的列进行排序。你可以有一个查询来获取行,然后让你的ruby代码对它进行排序(你仍然需要合并两个列表,但只有一个SQL查询):

-- don't sort on "reviewed"
SELECT m.*, (r.id != null) as reviewed
FROM movies m LEFT JOIN reviews r ON r.movieID = m.id AND r.userID = @userID
ORDER BY created_at DESC

最后,另一句话:你也可以尝试运行两个更尊重SQL缓存的查询

-- extract movies : this query is the same for all users. Once cached : all users will get the cached version
SELECT * FROM movies ORDER BY created_at DESC

-- extract the user's reviewed movies. With the right index (userID, movieID), this will only hit the index
SELECT movieID FROM reviews WHERE reviews.userID = @userID

然后在ruby中,您应该从第二个查询构建映射reviewed[moviedID] -> {true, false},并将电影列表分成两部分。如果频繁发生,这个版本实际上可能会更快。

答案 6 :(得分:-1)

您可以使用外部联接 - http://www.w3schools.com/sql/sql_join_left.asp

@movies_1 = Movie.joins('LEFT JOIN reviews ON reviews.movie_id = movies.id').where('reviews.movie_id IS NULL').where("reviews.user_id != ?", user.id).order("created_at")
@movies_2 = Movie.joins('LEFT JOIN reviews ON reviews.movie_id = movies.id').where(where('reviews.movie_id IS NOT NULL')).where("reviews.user_id = ?", user.id).order("created_at")

或者您可以使用counter_cache http://guides.rubyonrails.org/association_basics.html并检查哪些电影的reviews_count为0。