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

解决获取无超时TimeSlot的MissionReferral时SQL子查询别名错误

问题解决

1. 语法错误原因

PostgreSQL 严格要求 FROM 子句中的子查询必须指定别名,虽然部分数据库编辑器可能宽松处理,但 Rails 在执行 count 这类操作时会触发严格语法检查,导致你看到的 PG::SyntaxError。

2. 逻辑修正

你的原查询只覆盖了存在 TimeSlot的 MissionReferral,但需求是获取不存在任何晚于指定日期的 TimeSlot的实例,这包括两类:

  • 没有任何 TimeSlot 的 MissionReferral
  • 所有 TimeSlot 的 started_at 都早于指定日期的 MissionReferral

3. 修正后的 Scope

方式一:修正原 SQL 并补全逻辑

scope :with_no_timeslots_after, lambda { |date|
  target_date = date.to_date
  from(
    <<~SQL, target_date
      (
        SELECT tr.*
        FROM task_referrals tr
        LEFT JOIN (
          SELECT booking_id, booking_type, max(started_at) as started_at
          FROM time_slots AS ts
          GROUP by booking_id, booking_type
        ) as latest_time_slot
        ON tr.id = latest_time_slot.booking_id
        AND latest_time_slot.booking_type = 'TaskReferral'
        WHERE latest_time_slot.started_at < ? OR latest_time_slot.started_at IS NULL
      ) AS mission_referrals
    SQL
  ).where(type: 'MissionReferral')
}
  • 给外层子查询添加别名 mission_referrals,解决语法错误
  • 改用 LEFT JOIN 包含无 TimeSlot 的记录
  • 使用参数化查询(? 占位符)避免 SQL 注入风险
  • 添加 where(type: 'MissionReferral') 确保只返回 MissionReferral 类型的记录(父表 task_referrals 可能包含其他子类)

方式二:用 ActiveRecord 查询方法更简洁安全

如果不想写原生 SQL,也可以用 Rails 风格的链式查询实现相同逻辑:

scope :with_no_timeslots_after, lambda { |date|
  target_date = date.to_date
  left_joins(:time_slots)
    .select('task_referrals.*, MAX(time_slots.started_at) as latest_started_at')
    .group('task_referrals.id')
    .having('MAX(time_slots.started_at) < ? OR MAX(time_slots.started_at) IS NULL', target_date)
    .where(type: 'MissionReferral')
}

这种方式自动处理 SQL 注入和表别名问题,更易维护。

验证

执行 MissionReferral.with_no_timeslots_after(1.day.from_now).count 即可正常运行,同时返回符合需求的所有实例。

内容的提问来源于stack exchange,提问作者DavidM

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 02:03:22