Ruby on Rails:如何在原生SQL查询的Scope中添加Where子句?
解决Submission模型trending Scope的过滤与排序问题
正确实现代码
根据你的需求,结合Ahoy::Event的统计逻辑,以下是正确的trending Scope实现:
class Submission < ApplicationRecord belongs_to :contest scope :trending, ->(time_range, contest_id) { # 统计指定时间范围内每个投稿的浏览量 event_stats = Ahoy::Event .where(name: 'view_submission') # 替换为你实际的浏览事件名称 .where(time: time_range) .group("properties->>'submission_id'") # 假设投稿ID存储在event的properties中 .select("properties->>'submission_id' AS submission_id, COUNT(*) AS view_count") # 左连接统计结果,确保无浏览量的投稿也能被返回 joins("LEFT JOIN (#{event_stats.to_sql}) AS event_counts ON event_counts.submission_id = submissions.id::text") .where(contest_id: contest_id) # 在主表过滤指定竞赛的投稿 .select("submissions.*, COALESCE(event_counts.view_count, 0) AS view_count") # 用COALESCE将NULL转为0 .order("view_count DESC") # 按浏览量降序排序 } end
多态关联适配(如果使用)
如果Ahoy事件通过多态关联绑定Submission,直接用subject_id和subject_type更安全:
scope :trending, ->(time_range, contest_id) { event_stats = Ahoy::Event .where(name: 'view_submission') .where(time: time_range) .where(subject_type: 'Submission') .group(:subject_id) .select("subject_id AS submission_id, COUNT(*) AS view_count") joins("LEFT JOIN (#{event_stats.to_sql}) AS event_counts ON event_counts.submission_id = submissions.id") .where(contest_id: contest_id) .select("submissions.*, COALESCE(event_counts.view_count, 0) AS view_count") .order("view_count DESC") }
你之前的问题原因
结果不全:
- 之前默认用内连接(
INNER JOIN)会过滤掉无浏览记录的投稿,改用LEFT JOIN可保留所有符合竞赛条件的投稿,即使浏览量为0。 - 若把
where(contest_id: contest_id)加在子查询中,Ahoy::Event表本身无contest_id字段,属于无效过滤,必须在主表submissions上做过滤。
- 之前默认用内连接(
排序异常:
- 未显式指定按统计出的
view_count降序排序,或排序字段指向错误列。 - 用
COALESCE把NULL转为0,确保无浏览量的投稿能正确排在末尾。
- 未显式指定按统计出的
SQL语法错误:
- 直接在select语句中嵌入
WHERE(contest_id = #{contest_id})属于语法错误,同时存在SQL注入风险,绝对不能用字符串拼接处理参数,应使用ActiveRecord参数化查询或正确的子查询拼接方式。
- 直接在select语句中嵌入
调用方式
和你期望的一致:
@submissions = Submission.trending(30.days.ago..Time.zone.now, @contest.id)
内容的提问来源于stack exchange,提问作者yellowreign
相关产品推荐
相关产品推荐

