我正在使用另一种方法添加第二个答案,我认为这种方法会更好,因为它很有效并且可以转换为数据库视图。
每当我在 DB 上遇到大量重复命中或无法很好翻译的大型复杂查询时,我希望使用纯 SQL,因为它可以用作 DB 中的视图。我问了这个question,因为我的 SQL 很差。我认为这可以适应您的需求,特别是如果“状态”字段是一组已知的可能值。这是我最初尝试的方式:
构造一个有效的 SQL 查询。您可以在 psql 中进行测试。
SELECT created_at, count(status) AS total,
sum(case when status = 'error' then 1 end) AS errors,
sum(case when status = 'pending' then 1 end) AS pending,
sum(case when status = 'sent' then 1 end) AS sent
FROM notifications
GROUP BY created_at;
这应该返回一个像这样的数据透视表:
| created_at |total|errors|pending|sent|
----------------------------------------------
| Mon, 05 Oct 2015 |2572 |500 |12 |null|
| Tue, 06 Oct 2015 |555 |null |12 |50 |
太好了,在 Rails 中,任何单个表都是一个简单的查询,可以将其加载为对象数组。这些对象中的每一个都有一个对应于每一列的方法。如果该行的列为空,Rails 将传递 nil 作为值。
在 Rails 中测试
@stats = Notification.where(user: users).find_by_sql("SELECT created_at, count(status)
AS total,
sum(case when status = 'error' then 1 end) AS errors,
sum(case when status = 'pending' then 1 end) AS pending,
sum(case when status = 'sent' then 1 end) AS sent
FROM notifications
GROUP BY created_at;")
这将返回一个 Notification 对象数组...
=> [#< Notification id: nil, created_at: "2014-02-07 22:36:30">
#< Notification id: nil, created_at: "2014-06-26 02:07:51">,
#< Notification id: nil, created_at: "2015-04-26 21:37:09">,
#< Notification id: nil, created_at: "2014-02-07 22:48:29">,
#< Notification id: nil, created_at: "2014-11-04 23:39:07">,
#< Notification id: nil, created_at: "2015-01-27 17:46:50">,...]
请注意,Notification id: 为零。这是因为这些对象并不代表数据库中的实际对象,而是您的查询生成的表中的一行。但是现在您可以执行以下操作:
@stats.each do |daily_stats|
puts daily_stats.attributes
end
#{"created_at" => "Mon, 05 Oct 2015", "total" = 2572, "errors" => 500, "pending" => 12, "sent" => nil}
#{"created_at" => "Tue, 06 Oct 2015", "total" = 555, "errors" => nil, "pending" => 12, "sent" => 50}
等等.. 您的@stats 变量很容易传递给一个视图,在该视图中它很容易打印为.html.erb 文件中的表格。您可以访问数组中任何 Notification 对象的属性,例如:
@stats[0].created_at
#=> "Mon, 05 Oct 2015"
@stats[1].pending
#=> 12
总体而言,您使用了一个查询来获取整个数据集。
将其存储为视图
登录到数据库上的 SQL 控制台并执行
CREATE VIEW daily_stats AS
SELECT user_id, created_at, count(status) AS total,
sum(case when status = 'error' then 1 end) AS errors,
sum(case when status = 'pending' then 1 end) AS pending,
sum(case when status = 'sent' then 1 end) AS sent
FROM notifications
GROUP BY user_id, created_at;
现在您可以使用
获得结果
Select * FROM daily_stats;
请注意,我故意没有像您在原始问题中那样对用户进行限制,并将 user_id 添加到 SELECT。我们直接在数据库中工作,它应该可以轻松地从该视图生成一个表,其中包含每个日期的所有用户统计信息。对于您正在做的事情,这是一个非常强大的数据集。现在,您可以在 Rails 中设置一个虚拟模型,并且无需扭曲的 Rails 查询即可轻松获取所有数据。
添加虚拟模型
应用程序/模型/daily_stat.rb:
class DailyStat < ActiveRecord::Base
belongs_to :user
#this is a model for a view in the DB called dash_views
#class name is singular and will automatically look for the table "daily_stats" which his snake_case and plural.
end
将对应关系添加到您的User 模型中:
class User < ActiveRecord::Base
has_many :daily_stats
end
现在您可以通过用户以一种非常正常的方式访问您的统计信息。
users = [2]
DailyStat.where(user: users)
=> AllStat Load (2.8ms) SELECT "all_stats".* FROM "all_stats" WHERE "all_stats"."category_id" = 2
=> [ #<AllStat user_id: 2, created_at: "2014-02-14 00:30:24", total: 300, errors: 23, pending: nil, sent: 3>,
#<AllStat user_id: 2, created_at: "2014-11-29 00:18:28", total: 2454, errors: 3, pending: 45, sent: 323>,
#<AllStat user_id: 2, created_at: "2014-02-07 22:46:59", total: 589, errors: 33, pending: 240, sent: 68>...]
在另一个方向:
user = User.first
user.daily_stats
#returns array of that users DailyStat objects.
关键是“在最底层解决问题”。解决一个数据库中的数据查询问题,然后用Rails来操作呈现。