如何用ActiveRecord查询覆盖当前时间的门店营业时间记录?
Hey,我来帮你搞定这个ActiveRecord查询的问题!你肯定是想避免把所有记录拉到内存里用Ruby的cover?过滤对吧?毕竟数据量大的时候那效率简直没法看。下面分两种常见的表结构场景给你解决方案:
start和end是DateTime类型(包含完整日期+时间) 如果你的opening_hours表中,start和end字段存储的是完整的日期时间(比如2024-05-20 09:00:00这种格式),那直接用ActiveRecord的范围查询就能轻松实现需求,两种写法任你选:
- 直观SQL风格写法:
OpeningHour.where("start <= ? AND end >= ?", Time.now, Time.now)
- Rails风格链式查询写法:
OpeningHour.where(start: ..Time.now).where(end: Time.now..)
这两种写法都会生成类似下面的SQL语句,让数据库直接完成过滤,完全不会加载多余数据:
SELECT "opening_hours".* FROM "opening_hours" WHERE "opening_hours"."start" <= '2024-05-20 14:30:00' AND "opening_hours"."end" >= '2024-05-20 14:30:00'
day是Date类型,start/end是Time类型(仅存时分秒) 如果你的表结构是用day字段单独存储适用日期,start和end只存当天的营业时间(比如start是09:00:00,end是18:00:00),那需要先匹配今天的日期,再比较时间部分:
- 字符串格式比较(适配大多数数据库):
today = Date.today current_time_str = Time.now.strftime("%H:%M:%S") OpeningHour.where(day: today).where("start <= ? AND end >= ?", current_time_str, current_time_str)
- 秒数比较(更精准,避免字符串格式潜在问题):
today = Date.today current_seconds = Time.now.seconds_since_midnight OpeningHour.where(day: today).where( "EXTRACT(EPOCH FROM start) <= ? AND EXTRACT(EPOCH FROM end) >= ?", current_seconds, current_seconds )
这里EXTRACT(EPOCH FROM start)会把时间字段转换为当天的秒数(比如09:00:00对应32400秒),和当前时间的秒数直接比较,结果更准确。
如果你想直接找出当前正在营业的门店(关联Store模型),记得加上distinct避免返回重复的门店记录:
# 对应场景1的写法 Store.joins(:opening_hours).where(opening_hours: { start: ..Time.now, end: Time.now.. }).distinct # 对应场景2的写法 Store.joins(:opening_hours) .where(opening_hours: { day: Date.today }) .where("opening_hours.start <= ? AND opening_hours.end >= ?", Time.now.strftime("%H:%M:%S"), Time.now.strftime("%H:%M:%S")) .distinct
(start..end).cover? Time.now? 要是你写OpeningHour.all.select { |oh| (oh.start..oh.end).cover? Time.now },这会先把所有OpeningHour记录加载到Ruby内存中,再逐一过滤。数据量小的时候可能没感觉,但门店数量多了之后,这个操作会变得非常缓慢,而且完全没法利用数据库给start和end字段创建的索引,绝对是性能杀手。所以一定要让数据库来做过滤,也就是用ActiveRecord的where方法生成对应的SQL查询。
内容的提问来源于stack exchange,提问作者simonlehmann

