一尘不染

Rails通过:created_at对表进行分组,返回:status列的计数,然后对:status列进行子分组,并包含每个唯一值的计数

sql

我有一个通知模型,我想知道每天创建多少个通知以及每种状态有多少个。status是通知模型的字符串键。

Notification.where(user: users).group('DATE(created_at)').count('created_at')

那给我:

{Mon, 05 Oct 2015=>2572,
 Tue, 06 Oct 2015=>555,
 Wed, 07 Oct 2015=>2124,
 Thu, 08 Oct 2015=>220,
 Fri, 09 Oct 2015=>36}

但是我想要这样的东西:

{Mon, 05 Oct 2015=>{total=>2572, error=>500, pending=>12},
 Tue, 06 Oct 2015=>{total=>555, sent=>50, pending=>12}}

或类似。活动记录是否有可能?也许group_by对我有好处。但它已被弃用。

目前,我已经尝试过:

Notification.where(user: users).group('DATE(created_at), status').count('created_at') 
# => {"error"=>2, "sent"=>42}

这不是我想要的结果。


阅读 140

收藏
2021-05-16

共1个答案

一尘不染

我用另一种方法添加了第二个答案,我相信它会更好,因为它高效并且可以转换为数据库视图。

每当我遇到很多对数据库的重复点击或无法很好地翻译的大型,复杂查询时,我都希望使用纯SQL,因为这样可以将其用作DB中的视图。我问这个问题是因为我的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中都是一个简单的查询,它将以对象数组的形式加载它。这些对象中的每一个将具有与每一列相对应的方法。如果该行的nil列为null,则Rails将作为该值传递。

在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:值为nil。这是因为这些对象并不代表数据库中的实际对象,而是代表查询所产生的表中的一行。但是现在您可以执行以下操作:

@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;

请注意,我故意不限制用户的使用,就像您在原始问题中一样,并在SELECT中添加了user_id。我们正在直接在数据库中工作,它应该可以轻松地从该视图生成带有每个日期的所有用户统计信息的表。对于您正在做的事情,这是一个非常强大的数据集。现在,您可以在Rails中设置虚拟模型,并轻松获取所有数据,而不会扭曲Rails查询。

添加一个虚拟模型 app / models / 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进行操作并显示它。

2021-05-16