RoR使用where与joins时如何按关联模型条件过滤查询结果
问题原因
你写的查询无法生效核心是两个问题:
- 你把Ruby层面对象的调用方法(
.last、.date.year)直接写进了SQL条件片段,数据库无法识别Ruby的对象方法逻辑,这类写法只能在ActiveRecord把记录查询出来实例化成模型对象后才能调用。 - 普通的
joins(:milestones)会把项目关联的所有里程碑都做连接匹配,默认会返回只要存在任意一个里程碑在2021年的项目,无法匹配「最后一个里程碑」的筛选要求。
正确实现方案
你的核心需求本质是:筛选出「所属里程碑中日期最大的那条记录的年份为2021」的项目,用SQL聚合函数就能实现,不需要把数据拉到Ruby层面处理。
通用兼容写法(适配绝大多数数据库)
@projects_2021 = Project.joins(:milestones) .group("projects.id") .having("EXTRACT(YEAR FROM MAX(milestones.date)) = ?", 2021)
代码逻辑说明:
group("projects.id")按项目ID分组,每个项目最终只返回一条结果MAX(milestones.date)取每个项目下所有里程碑的最大日期,也就是你要的「最后一个里程碑」的日期EXTRACT(YEAR FROM 日期字段)是SQL标准的日期年份提取函数,如果是MySQL环境可以替换成更简洁的YEAR(MAX(milestones.date)) = ?,SQLite环境替换为strftime('%Y', MAX(milestones.date)) = ?即可- 用参数绑定
?传值2021,避免SQL注入风险
更易维护的写法(推荐)
如果后续还有其他针对最新里程碑的查询需求,可以直接在Project模型里加一个指向最新里程碑的关联:
class Project < ApplicationRecord has_many :milestones # 新增:每个项目的最新里程碑 has_one :latest_milestone, -> { order(date: :desc) }, class_name: "Milestone" end
加完关联之后查询代码会更简洁,语义也更清晰:
@projects_2021 = Project.joins(:latest_milestone) .where("EXTRACT(YEAR FROM milestones.date) = ?", 2021)
注意事项
- 以上
joins写法会自动过滤掉没有任何里程碑的项目,如果你需要保留这类项目,把joins换成left_joins即可,不过无里程碑的项目本身也不可能满足「最后一个里程碑在2021年」的条件,常规场景用joins就够。 - 不要用先查所有项目再循环取最后一个里程碑判断年份的写法,数据量稍大就会出现N+1查询问题,性能极差。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

