如何按月份筛选Rails模型记录?
哈哈,我之前也踩过一模一样的坑!你写的Order.where(created_at.strftime("%B"): "April")之所以无效,是因为你把Ruby层面的方法直接放到了ActiveRecord的数据库查询里——strftime是Ruby处理日期的方法,而ActiveRecord的where方法需要生成数据库能识别的SQL语句,数据库可不懂Ruby的strftime哦~
下面给你几个靠谱的替代方案,按需选择:
方案1:用数据库的月份函数直接查询(按月份名称)
不同数据库的函数略有不同,针对你的需求(匹配月份名称"April"):
如果你用PostgreSQL:
Order.where("TO_CHAR(created_at, 'FMMonth') = ?", "April")
FMMonth会去掉月份名称前后的空格,确保匹配准确。
如果你用MySQL:
Order.where("MONTHNAME(created_at) = ?", "April")
如果你用SQLite:
Order.where("strftime('%B', created_at) = ?", "April")
这里SQLite的strftime('%B')和Ruby的strftime("%B")效果类似,但这是数据库层面的函数,能被SQL识别。
方案2:按月份数字查询(更高效,推荐)
相比匹配字符串名称,直接用月份数字(April是第4个月)查询更高效,还能避免大小写、本地化的问题:
# 查询所有4月份的订单 Order.where("EXTRACT(MONTH FROM created_at) = ?", 4)
这个写法在PostgreSQL、MySQL、SQLite中都能正常工作(不同数据库对EXTRACT的支持基本一致)。
方案3:用时间范围查询(性能最优,强烈推荐)
如果你的created_at字段加了索引,用时间范围查询能充分利用索引,查询速度最快:
# 获取当年4月的起始和结束时间 start_of_april = Date.new(Date.today.year, 4, 1).beginning_of_day end_of_april = Date.new(Date.today.year, 4, 30).end_of_day # 查询该时间范围内的所有订单 Order.where(created_at: start_of_april..end_of_april)
如果需要查询往年的4月,只需要把Date.today.year换成对应的年份即可,比如2023。
为什么原来的方法不行?
再啰嗦一句:created_at.strftime("%B")是要在Ruby实例上调用的方法——你得先把数据库里的记录捞到Ruby内存中,才能对每个Order对象的created_at调用strftime。但where是让数据库直接筛选数据,不会先把所有记录加载到Ruby里,所以这种写法完全不兼容。
内容的提问来源于stack exchange,提问作者Umesh Malhotra

