Rails中使用includes关联where条件无法获取预期用户及订单的原因咨询
问题原因分析与解决方案
先把你的模型和查询代码整理成规范格式,方便我们梳理问题:
你的模型代码
user.rb
class User < ApplicationRecord has_many :orders, inverse_of: :user, dependent: :restrict_with_exception end
order.rb
class Order < ApplicationRecord belongs_to :user, inverse_of: :orders end
你的查询代码
User.where(id: selected_salesmen.pluck(:id)) .includes(:orders) .where("order.booked_at > ? AND order.booked_at < ?", booked_at_gteq, booked_at_lteq)
为什么查询不符合预期?
核心问题有两个:
SQL表名拼写错误
Rails默认数据库表名是模型名的复数形式,你的关联表应该是orders而非单数order。用单数表名会导致SQL无法匹配正确字段,要么报错,要么返回空结果。includes+ 字符串where的逻辑偏差includes原本是用来预加载关联数据避免N+1查询的,但一旦你用字符串形式的条件过滤关联表字段,会触发两个问题:- Rails会把查询转换成类似
joins的内连接逻辑,只会返回存在符合条件订单的用户,直接过滤掉没有匹配订单的用户; - 这个字符串条件只会作用于主查询的筛选,不会过滤预加载到用户对象里的
orders集合——也就是说,即使拿到了用户,它的orders还是会包含所有关联订单,而非你指定时间段内的。
- Rails会把查询转换成类似
正确的写法
根据你的需求(获取指定用户,且预加载的订单是指定时间段内的),推荐两种写法:
写法一:哈希形式的where条件
Rails会自动处理关联表名,同时把条件正确应用到主查询和预加载的关联数据上:
User.where(id: selected_salesmen.pluck(:id)) .includes(:orders) .where(orders: { booked_at: booked_at_gteq..booked_at_lteq })
这里用范围语法..替代了两个单独的比较符,更符合Rails的写法习惯。
写法二:使用merge方法
如果已经有现成的Order查询条件,用merge合并逻辑更清晰:
order_filter = Order.where(booked_at: booked_at_gteq..booked_at_lteq) User.where(id: selected_salesmen.pluck(:id)) .includes(:orders) .merge(order_filter)
特殊需求:保留无符合条件订单的用户
如果你需要返回所有指定用户(即使他们没有匹配时间段的订单,只是预加载的orders为空),可以用preload结合块语法:
User.where(id: selected_salesmen.pluck(:id)) .preload(:orders) do |orders| orders.where(booked_at: booked_at_gteq..booked_at_lteq) end
这种写法会分开执行两个查询:先查所有指定用户,再单独查询每个用户符合条件的订单,不会过滤掉无匹配订单的用户。
内容的提问来源于stack exchange,提问作者Umesh Malhotra
相关产品推荐
相关产品推荐

