Rails 如果关联有限制子句,则 AssociationRelation 属性的总和不正确
Rails sum on AssociationRelation attribute is incorrect if association has limit clause
我有一种方法可以计算模型中多个浮点属性的统计数据(主要是总和)。
模特
class GroupPlayer < ActiveRecord::Base
belongs_to :group
has_many :scored_rounds
has_many :rounds, dependent: :destroy
end
class Round < ActiveRecord::Base
belongs_to :group_player
end
class ScoredRound < Round
# STI
end
提供最多 4 个浮动属性的统计信息的方法从其他方法调用,具体取决于我是获取一个玩家还是一组玩家的统计数据。 ScoredRound 上的初始过滤器被传递给方法 (sr)
def method_stats(method,sr,grp)
rounds = sr.where.not(method => nil)
number_rounds = rounds.count
won = rounds.sum(method).round(2)
if method == :quality
dues = grp.options[:dues] * number_rounds
else
dues = grp.options["#{method.to_s}_dues"] * number_rounds
end
balance = (won - dues).round(2)
perc = dues > 0 ? (won / dues).round(3) : 0.0
[self.full_name,number_rounds,won,dues,balance,perc]
end
如果玩家没有赢得那场比赛,我在 ScoredRounds 中总结的 4 个属性中的 3 个可能未设置(无),因此回合被过滤。
一切正常,直到我决定添加对使用回合数的限制。例如,如果我只想要传递给 method_stats
的查询中最后 25 轮的状态,我会调用:
def money_stats(grp,method,limit=100)
sr = self.scored_rounds.where.not(method => nil).order(:date).reverse_order.limit(limit)
method_stats(method,sr,grp)
end
同样,我刚刚将限制和顺序子句添加到查询中。适用于所有记录。
如果我在控制台中模拟过程而不使用上述方法(或使用它们!),我会得到一个错误的总和
gp = GroupPlayer.find(123)
GroupPlayer Load (2.1ms) SELECT "group_players".* FROM "group_players" WHERE "group_players"."id" = LIMIT [["id", 123], ["LIMIT", 1]]
=> valid group player
sr = gp.scored_rounds.where.not(:quality => nil)
ScoredRound Load (1.7ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> #<ActiveRecord::AssociationRelation [#<ScoredRound id: 5706, player_id: 123, group_player_id: 123, event_id: 12, type: "ScoredRound", date: "2016-11-04", team: 3, tee: "White", quota: 32, front: 15, back: 15, total: 30, created_at: "2016-11-04 14:18:27", updated_at: "2016-11-04 19:12:47", quality: 0.0, skins: nil, par3: nil, other: nil>,...]
sr.count
(1.5ms) SELECT COUNT(*) FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 44
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
# Now if I add the order and limit clause
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
ScoredRound Load (1.6ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) ORDER BY "rounds"."date" DESC LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> => #<ActiveRecord::AssociationRelation [...]
sr.count
(1.1ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT ) subquery_for_count [["group_player_id", 123], ["LIMIT", 25]]
=> 25
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
### This is the error, it return the sum off all records,
# not the limited???? if I use pluck and sum
sr.pluck(:quality)
=> [10.0, 11.3333333333333, 10.0, 34.0, 0.0, 7.33333333333333, 0.0, 0.0, 31.5, 0.0, 21.3333333333333, 0.0, 19.0, 0.0, 0.0, 7.5, 0.0, 20.0, 10.0, 28.0, 8.0, 9.5, 0.0, 3.0, 24.0]
sr.pluck(:quality).sum
=> 254.49999999999994
不知道我是在 AREL 中发现了错误还是我做错了什么。我用 Round 而不是 STI ScoredRound 尝试了它,结果相同。
有什么想法吗?
# File activerecord/lib/active_record/relation/calculations.rb, line 75
def sum(column_name = nil)
return super() if block_given?
calculate(:sum, column_name)
end
如果您调用 sr.sum(:quality)
,则 sum 将 quality 作为列名并计算给定列上的值的总和。
如果您注意到,使用和不使用 LIMIT
的 SUM
结果是相同的:
sr = gp.scored_rounds.where.not(:quality => nil)
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
那是因为 LIMIT
影响查询返回的行数而 SUM
returns 只影响一个,所以该函数应用于所有 44 条记录,而不是给定的 25 条记录至 LIMIT
。 sr.pluck(:quality).sum
不会发生这种情况,它仅适用于查询返回的 25 条记录。
Don't know if I found a bug in AREL or I'm doing something wrong
可悲的是,99.9% 的时间不是错误而是我们的错:(
我有一种方法可以计算模型中多个浮点属性的统计数据(主要是总和)。
模特
class GroupPlayer < ActiveRecord::Base
belongs_to :group
has_many :scored_rounds
has_many :rounds, dependent: :destroy
end
class Round < ActiveRecord::Base
belongs_to :group_player
end
class ScoredRound < Round
# STI
end
提供最多 4 个浮动属性的统计信息的方法从其他方法调用,具体取决于我是获取一个玩家还是一组玩家的统计数据。 ScoredRound 上的初始过滤器被传递给方法 (sr)
def method_stats(method,sr,grp)
rounds = sr.where.not(method => nil)
number_rounds = rounds.count
won = rounds.sum(method).round(2)
if method == :quality
dues = grp.options[:dues] * number_rounds
else
dues = grp.options["#{method.to_s}_dues"] * number_rounds
end
balance = (won - dues).round(2)
perc = dues > 0 ? (won / dues).round(3) : 0.0
[self.full_name,number_rounds,won,dues,balance,perc]
end
如果玩家没有赢得那场比赛,我在 ScoredRounds 中总结的 4 个属性中的 3 个可能未设置(无),因此回合被过滤。
一切正常,直到我决定添加对使用回合数的限制。例如,如果我只想要传递给 method_stats
的查询中最后 25 轮的状态,我会调用:
def money_stats(grp,method,limit=100)
sr = self.scored_rounds.where.not(method => nil).order(:date).reverse_order.limit(limit)
method_stats(method,sr,grp)
end
同样,我刚刚将限制和顺序子句添加到查询中。适用于所有记录。
如果我在控制台中模拟过程而不使用上述方法(或使用它们!),我会得到一个错误的总和
gp = GroupPlayer.find(123)
GroupPlayer Load (2.1ms) SELECT "group_players".* FROM "group_players" WHERE "group_players"."id" = LIMIT [["id", 123], ["LIMIT", 1]]
=> valid group player
sr = gp.scored_rounds.where.not(:quality => nil)
ScoredRound Load (1.7ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> #<ActiveRecord::AssociationRelation [#<ScoredRound id: 5706, player_id: 123, group_player_id: 123, event_id: 12, type: "ScoredRound", date: "2016-11-04", team: 3, tee: "White", quota: 32, front: 15, back: 15, total: 30, created_at: "2016-11-04 14:18:27", updated_at: "2016-11-04 19:12:47", quality: 0.0, skins: nil, par3: nil, other: nil>,...]
sr.count
(1.5ms) SELECT COUNT(*) FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 44
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
# Now if I add the order and limit clause
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
ScoredRound Load (1.6ms) SELECT "rounds".* FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) ORDER BY "rounds"."date" DESC LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> => #<ActiveRecord::AssociationRelation [...]
sr.count
(1.1ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT ) subquery_for_count [["group_player_id", 123], ["LIMIT", 25]]
=> 25
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
### This is the error, it return the sum off all records,
# not the limited???? if I use pluck and sum
sr.pluck(:quality)
=> [10.0, 11.3333333333333, 10.0, 34.0, 0.0, 7.33333333333333, 0.0, 0.0, 31.5, 0.0, 21.3333333333333, 0.0, 19.0, 0.0, 0.0, 7.5, 0.0, 20.0, 10.0, 28.0, 8.0, 9.5, 0.0, 3.0, 24.0]
sr.pluck(:quality).sum
=> 254.49999999999994
不知道我是在 AREL 中发现了错误还是我做错了什么。我用 Round 而不是 STI ScoredRound 尝试了它,结果相同。
有什么想法吗?
# File activerecord/lib/active_record/relation/calculations.rb, line 75
def sum(column_name = nil)
return super() if block_given?
calculate(:sum, column_name)
end
如果您调用 sr.sum(:quality)
,则 sum 将 quality 作为列名并计算给定列上的值的总和。
如果您注意到,使用和不使用 LIMIT
的 SUM
结果是相同的:
sr = gp.scored_rounds.where.not(:quality => nil)
sr.sum(:quality)
(1.0ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) [["group_player_id", 123]]
=> 354.166666666667
sr = gp.scored_rounds.where.not(:quality => nil).order(:date).reverse_order.limit(25)
sr.sum(:quality)
(1.8ms) SELECT SUM("rounds"."quality") FROM "rounds" WHERE "rounds"."type" IN ('ScoredRound') AND "rounds"."group_player_id" = AND ("rounds"."quality" IS NOT NULL) LIMIT [["group_player_id", 123], ["LIMIT", 25]]
=> 354.166666666667
那是因为 LIMIT
影响查询返回的行数而 SUM
returns 只影响一个,所以该函数应用于所有 44 条记录,而不是给定的 25 条记录至 LIMIT
。 sr.pluck(:quality).sum
不会发生这种情况,它仅适用于查询返回的 25 条记录。
Don't know if I found a bug in AREL or I'm doing something wrong
可悲的是,99.9% 的时间不是错误而是我们的错:(