的 TL; DR?请参见编辑2
我有一个小Rails应用程序,有一些人可以玩的不同类型的游戏:它基于体育,所以他们可以每周挑选每个游戏的获胜者(模型PickEm
,属性{{ 1}} boolean与correct
用于未完成的游戏),并预测特定团队游戏的结果(模型nil
,属性Guess
带整数,score
表示未完成的游戏) 。每nil
User
has_many
和PickEms
。我正在尝试显示排名(Guesses
/总计 - 总计全部为非correct
,nil
/总数可能。)
我发现我可以收集用户及其相关记录,但在尝试显示排名时,我发现每个用户都在触发另一个查询 - 随着用户群的增加而缓慢且不可持续。那是因为score
是@user.pick_em_score
而pick_ems.where(correct: true).size
是@user.guess_Score
。所以我调用guesses.where.not(score: nil).sum(:score)
并运行该查询。我觉得应该有办法同时获得每个user.pick_em_score
以及这些特定的计数,而不是缓冲一大堆不必要的额外内容。
我需要什么:
User
记录User
(通过计算User.pick_em_score
条记录计算)correct
计算User.pick_ems
NOT NULL
(由User.guesses_score
计算)guesses.sum(:score)
计算User.guesses
我在Rails的ActiveRecord帮助程序中找到的大部分内容,尤其是与计算相关的内容,仅用于检索 计算。看起来我可能需要直接钻研NOT NULL
等等。但我无法让它发挥作用。有人能指出我正确的方向吗?
澄清:我知道我可以将此信息写入select()
模型,但这个限制性过于严格:下一季,我需要在User
添加新列 年度的结果等。此外,这是第三级回调更新相关模型 - User
模型已更新相关的Match
和PickEms
保存。 我正在寻找最简单的ActiveRecord查询或查询,以便能够使用此信息,如标题所示。理想情况下,一个查询返回上述信息,但如果需要的话,没关系。
我曾经在PHP中使用MySQL直接工作,但是那些技能已经生锈了(在原始的MySQL中,我想,我会有几个子选择语句来帮助拉动这些计数)而且我也喜欢能够使用Rails的ActiveRecord助手等,并尽可能避免构建原始SQL。
我似乎已将其归结为启动工作的一个调用,但我正在编写大量SQL。它也很脆弱,IMO,并试图用它运行失败了。看起来我只是将Rails中的百万个奇异Guesses
查询直接推送到SQL中,但这可能仍然是一个进步。
SELECT
问题似乎是:有没有办法使用Rails而不是原始SQL来链接我们在那里看到的User.unscoped.select('users.*',
'(SELECT COUNT(*) FROM pick_ems WHERE pick_ems.user_id = users.id AND pick_ems.correct) AS correct_pick_ems',
'(SELECT COUNT(*) FROM pick_ems WHERE pick_ems.user_id = users.id AND pick_ems.correct IS NOT NULL) AS total_pick_ems',
'(SELECT SUM(guesses.score) FROM guesses WHERE guesses.user_id = users.id AND guesses.score IS NOT NULL) AS guesses_score',
'(SELECT COUNT(*) FROM guesses WHERE guesses.user_id = users.id AND guesses.score IS NOT NULL) AS guesses_count' )
这些子查询?或者只是......一般来说,更好的方法来构建它?
此外,我正在为users.id
运行另一组SELECT
,这将取决于WHERE
和total_pick_ems
guesses_count
,但是我不能使用那些别名列,我必须再次调用> 0
。
答案 0 :(得分:1)
欢迎来到AR。它真的只适用于简单的CRUD,比如查询。一旦你真的想要在愤怒中查询你的数据,它就没有能力去做你想要的查询而不需要求助于批量的SQL字符串,而且往往放弃了链接的能力。
正是为什么我转向Sequel,因为它具有使用更全面的SQL功能集组成查询的功能,包括连接条件,窗口函数,递归公用表表达式和高级预先加载。与AR和Arel相比,作者非常敏感,文档也很出色。
我不希望你会喜欢这个答案,但是当你开始看到铁轨附带的自以为是的组件之外的时候会到来,我不得不说这些组件并不是最好的。 Sequel也加倍了我的应用程序,因为我可以通过AR得到它,不仅仅是开发人员的幸福,它意味着更少的服务器运行。是的,这将是一个学习曲线,但IMO更好地学习有背后的工具。
答案 1 :(得分:1)
加入可能会奏效。如下所示
User.unscoped.joins(:guesses).joins(:pick_ems).
where("guesses.score IS NOT NULL").
select("users.*,
sum(guesses.score) as guesses_score,
count(guesses.id) as guesses_count,
count(case when pick_ems.correct = True then 1 else null end)
as correct_pick_ems,
count(case when pick_ems.correct != null then 1 else null end)
as total_pick_ems,
").
group("users.id")
如果您需要一次有限数量的用户使用此信息,那么使用类方法(例如
)进行查询或预先加载(User.includes(:guesses, :pick_ems)
)
def correct_pick_ems
pick_ems.count(&:correct)
end
会起作用。
但是,如果您在大多数时间内需要所有用户使用此信息,那么users表中的缓存计数器将更加优化。
答案 2 :(得分:0)
你需要的是某种自定义(智能)counter_cache只能在某些条件下计数(例如是正确的)
您可以使用conditional after_save
& after_destroy
触发器构建您自己的自定义counter_cache,如下所示:
class PickEm
belongs_to :user
after_save :increment_finished_counter_cache, if: Proc.new { |pick_em| pick_em.correct }
after_destroy :decrement_finished_counter_cache, if: Proc.new { |pick_em| pick_em.correct }
private
def increment_finished_counter_cache
self.user.update_column(:finished_games_counter, self.user.finished_games_counter + 1) #update_column should not trigger any validations or callbacks
end
def decrement_finished_counter_cache
self.user.update_column(:finished_games_counter, self.user.finished_games_counter - 1) #update_column should not trigger any validations or callbacks
end
end
未经过测试的代码(仅用于表明想法)
有些人说最好避免将自定义计数器命名为rails命名(foo_counter_cache)
答案 3 :(得分:0)
你应该对它进行基准测试,但我的预感是,将所有数据添加到单个SELECT
并不比将其分解为单独的SELECT
更快(I&I #39;实际上有后者更快的情况)。通过分解,您还可以使用更多的ActiveRecord和更少的原始SQL,例如:
user_ids_to_pick_em_score = User.joins(:pick_ems).where(pick_ems: {correct: true}).group(:user_id).count
user_ids_to_pick_ems_count = User.joins(:pick_ems).where.not(pick_ems: {correct: nil}).group(:user_id).count
user_ids_to_guesses_score = Hash[User.select("users.id, SUM(guesses.score) AS total_score").joins(:guesses).group(:user_id).map{|u| [u.id, u.total_score]}]
user_ids_to_guesses_count = User.joins(:guesses).where.not(guesses: {score: nil}).group(:user_id).count
修改:要显示它们,您可以这样做:
<%- User.select(:id, :name).find_each do |u| -%>
Name: <%= u.name %>
Picks Correct: <%= user_ids_to_pick_em_score[u.id] %>/<%= user_ids_to_pick_ems_count[u.id] %>
Total Score: <%= user_ids_to_guesses_score[u.id] %>/<%= user_ids_to_guesses_count[u.id] %>
<%- end -%>