首页 > 解决方案 > 给定两个链接的 HABTM 和 has_many 关系,优化 ActiveRecord 查询以排除某些项目

问题描述

我需要通过过滤器仅显示 ActiveAdmin 中的一个表中的某些条目,我将其实现为选择(下拉)并且其代码在 Rails 范围内。我很难在 Rails 范围内找到以下条件的纯 SQL 替代品。

我有以下型号:

# app/models/score.rb

class Score < ApplicationRecord
  has_and_belongs_to_many :export_orders, join_table: :scores_export_orders
end

# app/models/export_order.rb

class ExportOrder < ApplicationRecord
  has_and_belongs_to_many :scores, join_table: :scores_export_orders
  belongs_to :shop
end

# app/models/shop.rb

class Shop < ApplicationRecord
  has_many :export_orders
end

过滤器允许您选择商店,因此相关代码需要在范围内返回 a Score::ActiveRecord_Relation

条件是:返回所有未导出到选定店铺的分数,即所有没有出口订单的分数shop等于选定店铺

例如,想象以下场景:

[608, []]
[607, []]
[606, []]
[593, [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 8, 8, 8, nil, nil, nil, nil, 8, 8]]
[586, []]
[585, []]
[372, [nil, nil, 2, nil, nil, nil, 8, 8, 8, nil]]
[157, [nil, 2, nil, nil, nil, 1]]

左边是分数 ID,右边是他们出口订单的商店 ID 数组。需要明确的是,以上是 的结果Score.all.map { |s| [s.id, s.export_orders.map{|o|o.shop_id}] }

例如,结果Score.not_exported_to(2).ids应该是

[608, 607, 606, 593, 586, 585]

因为分数的出口订单(带有这些 id)都没有2在他们的商店 id 中。


现在,我真的想不出用 SQL 的方式来写这个。到目前为止,我在我的范围内拥有的是:

# app/models/score.rb

  scope :not_exported_to, -> (input) {
    shop = Shop.find(input)
    export_orders = shop.export_orders
    already_exported_score_ids = export_orders.map { |o| o.scores.ids } .flatten.uniq
    where.not(id: already_exported_score_ids)
  }

这给出了正确的结果,但显然效率很低,因为它使用map依赖关联 ( Score),它在分数表上执行选择,重复次数等于export_orders.count。想象一下它在一家商店的 1000 多个出口订单上运行!

到目前为止我尝试了什么:

joins("INNER JOIN scores_export_orders ON scores.id = scores_export_orders.score_id INNER JOIN export_orders ON export_orders.id = scores_export_orders.export_order_id").distinct.where("export_orders.shop_id <> #{input}")

但这会返回错误的结果。

我在 SQL 方面不是那么专家,恐怕我不得不使用某种我不太熟悉的嵌套查询。

标签: ruby-on-railspostgresqlactiverecordquery-optimizationactiveadmin

解决方案


推荐阅读