Ruby on rails 使用DATE#TRUNC(';week';)返回一条记录,如果该周';总和(:点)有0?

Ruby on rails 使用DATE#TRUNC(';week';)返回一条记录,如果该周';总和(:点)有0?,ruby-on-rails,ruby,postgresql,ruby-on-rails-5,Ruby On Rails,Ruby,Postgresql,Ruby On Rails 5,我使用以下Rails+Postgres查询来查询db,以获得按周分组的总点数: Log Table: id, user_id, points, created_at points_by_week = Log.where(user_id: user_id).group("DATE_TRUNC('year', created_at)", "DATE_TRUNC('week', created_at)").sum(:points) 这将返回如下结果: { [2017-01-01 00:00:00

我使用以下Rails+Postgres查询来查询db,以获得按周分组的总点数:

Log Table: id, user_id, points, created_at

points_by_week = Log.where(user_id: user_id).group("DATE_TRUNC('year', created_at)", "DATE_TRUNC('week', created_at)").sum(:points)
这将返回如下结果:

{ [2017-01-01 00:00:00 UTC, 2017-04-17 00:00:00 UTC]=>10, 
  [2017-01-01 00:00:00 UTC, 2017-05-15 00:00:00 UTC]=>110, 
  [2017-01-01 00:00:00 UTC, 2017-06-19 00:00:00 UTC]=>1185 }
问题是,如果每周存在1个或多个点,则仅返回记录

我该怎么办:

  • 如果一周包含0分,则返回一周结果。例如,对于上面的返回,2017-05-08的
    一周缺少,因为它有0分。我想让它回到上面。因此,当我将此结果绘制成图表时,本周显示为0
  • 指定要返回的周数?并确保如果一周包含0分,则按上面的#1输出

  • 谢谢

    首先,我强烈建议您开始学习并使用纯SQL来执行这些任务。活动记录(与任何其他ORM一样)仅适用于简单任务。迟早你会需要深入到Postgres日志中,找出你的RoR AR产生了什么样的SQL查询,为什么它们不是最优的,或者为什么这些查询现在给你你想要的

    现在关于你的问题本身。我会这样做,使用LEFT JOIN和CTE(请参阅):

    CTE是标准的SQL特性,它们确实值得学习和使用

    在数据库中处理数据时,首先考虑使用数据库(SQL)。 甚至有一种越来越流行的意识形态——“数据库优先”

    如果您有疑问,这里有一些与此主题相关的好文章:


    首先,我强烈建议您开始学习并使用普通SQL来执行这些任务。活动记录(与任何其他ORM一样)仅适用于简单任务。迟早你会需要深入到Postgres日志中,找出你的RoR AR产生了什么样的SQL查询,为什么它们不是最优的,或者为什么这些查询现在给你你想要的

    现在关于你的问题本身。我会这样做,使用LEFT JOIN和CTE(请参阅):

    CTE是标准的SQL特性,它们确实值得学习和使用

    在数据库中处理数据时,首先考虑使用数据库(SQL)。 甚至有一种越来越流行的意识形态——“数据库优先”

    如果您有疑问,这里有一些与此主题相关的好文章:


    请注意,我没有花时间重新创建您的结构

    对其他读者的警告

    Arel的旧版本不汇编CTE语句。(我对<6.0版本的方法有问题,但由于某些原因,查询的“with”部分没有生成)对于这些版本,使用子查询而不是CTE

    对于这种类型的功能,我要做的第一件事是创建一个服务对象来处理这种特定情况。(让我们从这里开始)

    现在是构建适当查询的重担(顺便说一句,感谢@Nick的贡献,因为我是根据他的建议以及@ukaszKamiński的评论构建这个查询的)

    由此产生的SQL如下所示

    UserPointsByWeek.new(@user).to_sql # Assuming user.id == 1
    #=> "WITH first_week AS (
            SELECT DATE_TRUNC('week', logs.created_at) AS week 
            FROM logs 
            WHERE logs.user_id = 1
        ), all_weeks AS (
            SELECT gs AS week 
            FROM generate_series(
                (SELECT MIN(first_week.week) FROM first_week), 
                now(), 
                interval '1 week') AS _(gs)
       ) 
       SELECT 
           all_weeks.week, 
           SUM(logs.points) AS weekly_points 
       FROM 
           all_weeks 
           LEFT OUTER JOIN logs ON all_weeks.week = DATE_TRUNC('week', logs.created_at) 
             AND logs.user_id = 1    
       GROUP BY 
           all_weeks.week"
    

    #weekly_points
    将返回一个
    数组
    ,该数组包含用户第一周和当前周之间每周的两个键
    :week
    :weekly_points
    。另请注意,
    用户也可以访问。

    请注意,我没有花时间重新创建您的结构

    对其他读者的警告

    Arel的旧版本不汇编CTE语句。(我对<6.0版本的方法有问题,但由于某些原因,查询的“with”部分没有生成)对于这些版本,使用子查询而不是CTE

    对于这种类型的功能,我要做的第一件事是创建一个服务对象来处理这种特定情况。(让我们从这里开始)

    现在是构建适当查询的重担(顺便说一句,感谢@Nick的贡献,因为我是根据他的建议以及@ukaszKamiński的评论构建这个查询的)

    由此产生的SQL如下所示

    UserPointsByWeek.new(@user).to_sql # Assuming user.id == 1
    #=> "WITH first_week AS (
            SELECT DATE_TRUNC('week', logs.created_at) AS week 
            FROM logs 
            WHERE logs.user_id = 1
        ), all_weeks AS (
            SELECT gs AS week 
            FROM generate_series(
                (SELECT MIN(first_week.week) FROM first_week), 
                now(), 
                interval '1 week') AS _(gs)
       ) 
       SELECT 
           all_weeks.week, 
           SUM(logs.points) AS weekly_points 
       FROM 
           all_weeks 
           LEFT OUTER JOIN logs ON all_weeks.week = DATE_TRUNC('week', logs.created_at) 
             AND logs.user_id = 1    
       GROUP BY 
           all_weeks.week"
    

    #weekly_points
    将返回一个
    数组
    ,该数组包含用户第一周和当前周之间每周的两个键
    :week
    :weekly_points
    。作为旁注,
    用户
    也仍然可以访问。

    我不同意这样的说法,即这足够复杂,需要使用SQL或Arel。你已经得到了95%的结果,剩下的应该是微不足道的

    方法1

    如果您只想从散列中获取值,请考虑在散列上定义一个默认值,例如:

    points_by_week =
      Hash.
      new(0).
      merge(
        Log.
        where(user_id: user_id).
        group("DATE_TRUNC('year', created_at)", "DATE_TRUNC('week', created_at)").
        sum(:points)
      )
    
    然后,无论何时打电话:

    points_by_week[x]
    
    。。。如果散列中没有x,则返回0

    如果不存在的键需要nil,则将
    nil
    而不是
    0
    传递给Hash.new

    方法2 或者,如果确实需要填充散列中的整个值集,则可以根据范围(我在这里使用了整数0到100)创建一个散列,其中包含所需的键,并为具有类似内容的值创建零/零:

    。。。或者

    Hash[*(0..5).to_a.zip(Array.new(6)).flatten]
     => {0=>nil, 1=>nil, 2=>nil, 3=>nil, 4=>nil, 5=>nil} 
    
    (也许有一种更方便的方法)和
    #reverse#u merge
    将其与当前哈希值合并

    e、 g

    可以使用以下内容生成星期一日期数组:

    mondays = (Date.parse("2017-04-17")..(Date.parse("2017-04-17")+20.weeks)).map(&:to_time).select(&:monday?)
    
    。。。并转换为[年份,日期]与

    year_monday_pairs = mondays.map{|d| [d.beginning_of_year, d] }
    
    。。。并转换为
    #reverse_merge

    Hash[*year_monday_pairs.zip(Array.new(year_monday_pairs.size, 0)).flatten(1)]
     => {[2017-01-01 00:00:00 +0000, 2017-04-17 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-04-24 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-01 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-08 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-15 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-22 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-29 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-05 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-12 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-19 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-26 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-03 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-10 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-17 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-24 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-31 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-07 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-14 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-21 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-28 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-09-04 00:00:00 +0100]=>0} 
    

    我不同意这样的说法,即这足够复杂,需要使用SQL或Arel。你已经得到了95%的结果,剩下的应该是微不足道的

    方法1

    如果您只想从散列中获取值,请考虑在散列上定义一个默认值,例如:

    points_by_week =
      Hash.
      new(0).
      merge(
        Log.
        where(user_id: user_id).
        group("DATE_TRUNC('year', created_at)", "DATE_TRUNC('week', created_at)").
        sum(:points)
      )
    
    然后,无论何时打电话:

    points_by_week[x]
    
    。。。如果散列中没有x,则返回0

    如果要求为零
    year_monday_pairs = mondays.map{|d| [d.beginning_of_year, d] }
    
    Hash[*year_monday_pairs.zip(Array.new(year_monday_pairs.size, 0)).flatten(1)]
     => {[2017-01-01 00:00:00 +0000, 2017-04-17 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-04-24 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-01 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-08 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-15 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-22 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-05-29 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-05 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-12 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-19 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-06-26 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-03 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-10 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-17 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-24 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-07-31 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-07 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-14 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-21 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-08-28 00:00:00 +0100]=>0, [2017-01-01 00:00:00 +0000, 2017-09-04 00:00:00 +0100]=>0}