解决获取无超时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
相关产品推荐
相关产品推荐

