0

假设我有以下内容:

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

任何人都知道如何在一个查询中做到这一点?

4

7 回答 7

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联盟

在mysql中使用union和order by子句

Rails 3 ActiveRecord:联合

rails 3中2个活动记录关系对象的联合

于 2013-09-16T07:45:00.937 回答
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当前用户对电影的评论数量。

于 2013-09-18T01:19:33.817 回答
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 中的新功能。

于 2013-09-19T20:33:40.003 回答
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

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

结果看起来像:

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')
于 2013-09-16T05:08:19.577 回答
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')

使用“包含”是左外连接的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 的条件,我猜。让它更快地处理大量数据;)

于 2013-09-16T06:11:17.983 回答
0

注意:我不会“说”红宝石,我会写 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},并将电影列表分成两部分。如果频率上升,这个版本实际上可能会更快。

于 2013-09-22T18:42:19.480 回答
-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。

于 2013-09-12T18:52:06.530 回答