假设我有以下内容:
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
任何人都知道如何在一个查询中执行此操作?
答案 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)
答案 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。