Rails 4 根据 Payment 条件查询唯一 Agent
结论
用 joins 关联 payments,筛选出 amount > 0 的记录,再用 distinct 去掉一对多关联产生的重复 Agent:
@agents = Agent.joins(:payments)
.where("payments.amount > ?", 0)
.distinct
对应的 SQL 逻辑大致如下:
SELECT DISTINCT agents.*
FROM agents
INNER JOIN payments ON payments.agent_id = agents.id
WHERE payments.amount > 0;
为什么会出现重复
一个 Agent 可以有多笔 Payment。执行 Agent.joins(:payments) 后,每笔符合条件的 Payment 都会生成一条关联记录。
比如,同一个 Agent 有三笔 amount > 0 的 Payment,连接查询中就会出现三条对应记录。只使用 joins 和 where,无法保证返回的 Agent 不重复。
distinct 会直接在数据库查询层去重,因此最终返回的 Agent 都是唯一的。
使用 group 的写法
也可以按 Agent 的主键分组:
@agents = Agent.joins(:payments)
.where("payments.amount > ?", 0)
.group("agents.id")
在某些 MySQL 配置下,这种写法也能得到唯一的 Agent。不过,这里没有聚合计算,使用 distinct 更能直接表达去重意图。
如果 MySQL 启用了 ONLY_FULL_GROUP_BY,只按 agents.id 分组却同时查询 agents.*,还可能引发错误。因此,仅用于去重时更推荐 distinct。
修正模型关联
belongs_to 后面应该使用单数形式 :agent,因为每条 Payment 只属于一个 Agent:
class Agent < ActiveRecord::Base
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :agent
end
下面这种写法不符合该外键关系的常规命名:
belongs_to :agents
payments.agent_id 对应的关联名称应该是 agent。修正后,Agent.joins(:payments) 就能通过 has_many :payments 正常建立连接。
注意事项
如果 amount 字段允许为 NULL,条件仍然可以这样写:
.where("payments.amount > ?", 0)
在 SQL 中,NULL > 0 不成立,所以这些 Payment 不会出现在结果中。
只有在需要统计每个 Agent 符合条件的 Payment 数量时,才适合使用 group 和聚合函数。如果只是查询唯一的 Agent,优先使用 distinct。
备注:内容仅供参考。
