ActiveRecord:如何基于has_many子模型的MIN/MAX创建查询范围?
查询符合日期条件的Trip记录
模型关联关系
class Trip < ApplicationRecord has_many :trip_destinations, dependent: :destroy end class TripDestination < ApplicationRecord belongs_to :trip validates :start_date, presence: true validates :end_date, presence: true end
需求
需要查询所有满足以下条件的Trip记录:
- 关联的
TripDestination中,最早的start_date早于今天 - 关联的
TripDestination中,最晚的end_date晚于今天
已调试问题
原查询代码如下:
today = Date.today Trip .joins(:trip_destinations) .where(" trip_destinations.start_date = ( SELECT MIN(trip_destinations.start_date) FROM trip_destinations WHERE trips.id = trip_destinations.trip_id ) AND trip_destinations.start_date <= ?", today ) .where(" trip_destinations.end_date = ( SELECT MAX(trip_destinations.end_date) FROM trip_destinations WHERE trips.id = trip_destinations.trip_id ) AND trip_destinations.end_date > ?", today ) .group("trips.id")
生成的SQL:
SELECT 1 AS one FROM "trips" INNER JOIN "trip_destinations" ON "trip_destinations"."trip_id" = "trips"."id" WHERE "trips"."published" = $1 AND ( trip_destinations.start_date = ( SELECT MIN(trip_destinations.start_date) FROM trip_destinations WHERE trips.id = trip_destinations.trip_id ) AND trip_destinations.start_date <= '2022-11-08') AND ( trip_destinations.end_date = ( SELECT MAX(trip_destinations.end_date) FROM trip_destinations WHERE trips.id = trip_destinations.trip_id ) AND trip_destinations.end_date > '2022-11-08') GROUP BY "trips"."id" LIMIT $2 [["published", true], ["LIMIT", 1]]
问题:该查询返回空集合,但手动确认存在符合条件的Trip;拆分两个WHERE子句单独执行均能返回目标Trip,组合后无结果(即使去掉日期条件也为空)。
解决方案
方法一:使用GROUP BY + HAVING聚合筛选
这是最直接高效的方式,通过分组计算每个Trip的最小/最大日期,再筛选符合条件的记录:
today = Date.today Trip.joins(:trip_destinations) .group('trips.id') .having('MIN(trip_destinations.start_date) <= ? AND MAX(trip_destinations.end_date) > ?', today, today)
对应的核心SQL逻辑:
SELECT trips.* FROM trips INNER JOIN trip_destinations ON trip_destinations.trip_id = trips.id GROUP BY trips.id HAVING MIN(trip_destinations.start_date) <= '2022-11-08' AND MAX(trip_destinations.end_date) > '2022-11-08'
方法二:子查询筛选符合条件的Trip ID
先从TripDestination中筛选出符合条件的trip_id集合,再查询对应的Trip:
today = Date.today Trip.where(id: TripDestination.select(:trip_id) .group(:trip_id) .having('MIN(start_date) <= ? AND MAX(end_date) > ?', today, today))
原查询错误原因
原查询要求同一条TripDestination记录同时满足:
- 是所属Trip的最小start_date
- 是所属Trip的最大end_date
这种情况仅在Trip只有一个TripDestination,或某条TripDestination同时是最早开始和最晚结束时才成立,绝大多数符合条件的Trip无法匹配该逻辑,因此返回空集合。
内容的提问来源于stack exchange,提问作者Jah Seng Lee
相关产品推荐
相关产品推荐

