如何查询所有父母的孩子的属性总和?

时间:2017-06-26 05:33:20

标签: ruby-on-rails

在Rails应用程序中,我有两个模型如下

class Trip 
  has_many :items
  # has a 'fuel' column
end
class Item
  belongs_to :trip
  # has a 'quantity' column 
end

对于仪表板视图,我需要聚合这些数据,以便它们可以在图表中显示。

以下方法生成正确的输出

def self.to_dashboard_chart
  data = [['Fuel', 'Quantity']]
  self.all.each do |trip|
    data << [trip.fuel, trip.items.sum(:quantity)] unless trip.fuel.nil?
  end
  data
end

但也非常低效,特别是当数据库变大时!

我正在尝试将其改编为单个查询。

def self.to_dashboard_chart_new
  self.joins(:items).
    where.not(fuel: nil).
    select("fuel, sum(items.quantity) as trip_quantity").
    group(:id).
    map{ |t| [t.fuel, t.trip_quantity] }.
    unshift(['Fuel','Quantity'])
end

然而,这并没有返回相同的回应

Trip.to_dashboard_chart == Trip.to_dashboard_chart_new 
=> false
Trip.to_dashboard_chart.length == Trip.to_dashboard_chart_new.length
=> false

我无法理解为什么这些会返回不同的结果。我在俯瞰什么?

修改

我认为我已经将问题跟踪到如何处理没有项目的旅行。

let!(:t1) { create :trip, fuel: 50 }
let!(:t2) { create :trip, fuel: 100 }
let!(:t3) { create :trip, fuel: 200 }
let!(:t4) { create :trip, fuel: nil }

let!(:i1)  { create :item, trip: t1, quantity: 10 }
let!(:i2)  { create :item, trip: t1, quantity: 20 }
let!(:i3)  { create :item, trip: t2, quantity: 50 } 

it "returns the correct response with old" do
  expect(Trip.to_dashboard_chart_old).to eq(
        [
          ["Fuel", "Quantity"],
          [50,30],
          [100,50],
          [200,0]
        ]
      )
end

it "returns the correct response with new" do
  expect(Trip.to_dashboard_chart_new).to eq(
        [
          ["Fuel", "Quantity"],
          [50,30],
          [100,50],
          [200,0]
        ]
      )
end

Trip.to_dashboard_chart_old次通过,而Trip.to_dashboard_chart_new则失败

expected: [["Fuel", "Quantity"], [50, 30], [100, 50], [200, 0]]
got: [["Fuel", "Quantity"], [50, 30], [100, 50]]

如何修改查询以确保为没有任何项目的旅行返回0的数量?

2 个答案:

答案 0 :(得分:2)

def self.to_dashboard_chart_my
  arr = [['Fuel','Quantity']]
  items = self.left_outer_joins(:items).
    where('trips.fuel is not null').
    select("fuel, COALESCE(sum(items.quantity),0) as trip_quantity").
    group(:id)

  for item in items do
     arr << [item.fuel, item.trip_quantity]
  end

  arr
end

Bechmarks IPS(每秒迭代次数)

Warming up --------------------------------------
            Your One   148.000  i/100ms
              My one   165.000  i/100ms
Calculating -------------------------------------
            Your One      1.461k (±10.3%) i/s -      7.252k in   5.052092s
              My one      1.676k (± 8.8%) i/s -      8.250k in   5.004467s

答案 1 :(得分:1)

好吧,经过多次头疼,我最终发现问题是查询是如何处理Trips而没有项目的。

joins更改为left_outer_joins可确保包含所有记录,并将COALESCE添加到SUM函数可确保返回0而不是{{} 1}}。

nil

性能要好得多!在具有50,000条记录的数据库中

def self.to_dashboard_chart_new
  self.left_outer_joins(:items).
    where.not(fuel: nil).
    select("fuel, COALESCE(sum(items.quantity),0) as trip_quantity").
    group(:id).
    map{ |t| [t.fuel, t.trip_quantity] }.
    unshift(['Fuel','Quantity'])
end

如果有人能提出更高效的查询,我很乐意将其标记为正确答案。