You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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记录同时满足:

  1. 是所属Trip的最小start_date
  2. 是所属Trip的最大end_date

这种情况仅在Trip只有一个TripDestination,或某条TripDestination同时是最早开始和最晚结束时才成立,绝大多数符合条件的Trip无法匹配该逻辑,因此返回空集合。

内容的提问来源于stack exchange,提问作者Jah Seng Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 23:10:24