【问题标题】:How to do a rolling sum, each row need to include the sum of previous rows怎么做一个滚动求和,每一行需要包括前几行的总和
【发布时间】:2013-01-30 03:17:59
【问题描述】:

我有桌子 [访问]。当 order_number 为空时,我需要获取按 user_id 和 visit_duration_seconds 总和的行,例如,对于用户 [2875636],我将得到:61+151+33+13。每行应包括其前行的总和。
请在下面的预期结果中参考 RESULT 列

user_id   starttime           visit_duration_seconds  order_number
2875636   2013-01-16 18:03:50 61  
2875636   2013-01-16 18:08:18 151 
2875636   2013-01-16 18:15:43 33  
2875636   2013-01-16 18:16:37 13  
2875636   2013-01-16 18:18:01 2011                     10177888
2875636   2013-01-16 18:24:35 1172                     10177884
2875636   2013-01-16 18:32:03 4731    
2875636   2013-01-16 18:33:27 407 
2875636   2013-01-16 18:37:29 74  
2875636   2013-01-16 18:48:55 80  
2875636   2013-01-16 19:05:00 1955    
2875636   2013-01-16 19:14:12 326 
2875636   2013-01-16 19:23:39 972 
2875636   2013-01-16 19:33:05 5440    
2875636   2013-01-16 19:35:48 43  
2875636   2013-01-16 19:41:10 66  
2875636   2013-01-16 19:42:03 100 
2875636   2013-01-16 19:42:12 2414                     10177940
2875636   2013-01-16 19:49:05 432                  10177925
2875636   2013-01-16 19:50:19 183 
2875636   2013-01-16 19:52:46 2061    
2875636   2013-01-16 19:52:53 400 
2875636   2013-01-16 20:00:47 338 
2875636   2013-01-16 20:08:58 216 
2875636   2013-01-16 20:14:21 58  
2875636   2013-01-16 20:14:26 196 
2875636   2013-01-16 20:19:14 2189    
2875636   2013-01-16 20:21:29 424 
2875636   2013-01-16 20:24:42 999 
2875636   2013-01-16 21:01:39 1810    
2875636   2013-01-16 21:02:54 525 
2875636   2013-01-16 21:10:06 27  
2875636   2013-01-16 21:12:08 282 
2875636   2013-01-16 21:51:02 6   
2875636   2013-01-16 22:18:34 173 
2875636   2013-01-16 23:02:58 318 
2875636   2013-01-16 23:45:37 207 
3018868   2013-01-16 16:01:45 18  
3018868   2013-01-16 16:16:45 39  
3018868   2013-01-16 16:22:55 656 
3018868   2013-01-16 16:25:54 1852    
3018868   2013-01-16 16:29:23 688 
3018868   2013-01-16 16:47:26 2258                       10177846
3018868   2013-01-16 16:57:41 572 
3018868   2013-01-16 17:06:47 1431    
3018868   2013-01-16 17:18:32 29  
3018868   2013-01-16 17:21:57 45  
3018868   2013-01-16 17:29:23 16  
3018868   2013-01-16 17:36:47 490

预期结果

user_id starttime           visit_duration_seconds  order_number        RESULT
2875636 2013-01-16 18:03:50 61                                      61
2875636 2013-01-16 18:08:18 151                                     212
2875636 2013-01-16 18:15:43 33                                      245
2875636 2013-01-16 18:16:37 13                                      258
2875636 2013-01-16 18:18:01 2011                     10177888           0
2875636 2013-01-16 18:24:35 1172                     10177884           0
2875636 2013-01-16 18:32:03 4731                                        4731
2875636 2013-01-16 18:33:27 407                                     5138
2875636 2013-01-16 18:37:29 74                                      5212
2875636 2013-01-16 18:48:55 80                                      ...
2875636 2013-01-16 19:05:00 1955                                        ...
2875636 2013-01-16 19:14:12 326                                     ...
2875636 2013-01-16 19:23:39 972 
2875636 2013-01-16 19:33:05 5440    
2875636 2013-01-16 19:35:48 43  
2875636 2013-01-16 19:41:10 66  
2875636 2013-01-16 19:42:03 100 
2875636 2013-01-16 19:42:12 2414                     10177940
2875636 2013-01-16 19:49:05 432                  10177925
2875636 2013-01-16 19:50:19 183 
2875636 2013-01-16 19:52:46 2061    
2875636 2013-01-16 19:52:53 400 
2875636 2013-01-16 20:00:47 338 
2875636 2013-01-16 20:08:58 216 
2875636 2013-01-16 20:14:21 58  
2875636 2013-01-16 20:14:26 196 
2875636 2013-01-16 20:19:14 2189    
2875636 2013-01-16 20:21:29 424 
2875636 2013-01-16 20:24:42 999 
2875636 2013-01-16 21:01:39 1810    
2875636 2013-01-16 21:02:54 525 
2875636 2013-01-16 21:10:06 27  
2875636 2013-01-16 21:12:08 282 
2875636 2013-01-16 21:51:02 6   
2875636 2013-01-16 22:18:34 173 
2875636 2013-01-16 23:02:58 318 
2875636 2013-01-16 23:45:37 207 
3018868 2013-01-16 16:01:45 18  
3018868 2013-01-16 16:16:45 39  
3018868 2013-01-16 16:22:55 656 
3018868 2013-01-16 16:25:54 1852    
3018868 2013-01-16 16:29:23 688 
3018868 2013-01-16 16:47:26 2258                       10177846
3018868 2013-01-16 16:57:41 572 
3018868 2013-01-16 17:06:47 1431    
3018868 2013-01-16 17:18:32 29  
3018868 2013-01-16 17:21:57 45  
3018868 2013-01-16 17:29:23 16  
3018868 2013-01-16 17:36:47 490 

【问题讨论】:

  • 我用 SQLite 做了类似的工作,使用了内部连接。我建议你看看:stackoverflow.com/questions/3675079/sqlite-filtering-by-sum/…(忘记删除部分)
  • @MPelletier:半连接方法适用于合理大小的集合。对于大集合,这种方法有可能产生大量的行。另一种方法是使用相关子查询,但这对于大型集合也有明显的性能劣势。
  • @spencer7593 是的,这是真的。不知道数据的大小,我想我可以把它扔在那里。
  • @MPelletier:工具带中有几个工具很好。如果只有 MySQL 支持,分析函数将是另一个合适的答案。我无法通过大脑围绕连接标准进行包装,该标准仅包括返回最新的非 NULL order_number 的较早行。我通过相关子查询获得它,而不是通过连接操作。有可能,只是还没想好。)

标签: mysql sql


【解决方案1】:

您可以利用 MySQL 用户变量来模拟分析函数。 (还有其他一些方法,例如使用半连接或使用相关子查询。如果您觉得它们可能更合适,我也可以为这些方法提供解决方案。)

要模拟“运行总计”分析函数,请尝试以下操作:

SELECT t.user_id
     , t.starttime
     , t.order_number
     , IF(t.order_number IS NOT NULL,
         @tot_dur := 0,
         @tot_dur := @tot_dur + t.visit_duration_seconds) AS tot_dur
  FROM visit t
  JOIN (SELECT @tot_dur := 0) d
 ORDER BY t.user_id, t.start_time

这里的“技巧”是使用 IF 函数来测试order_number 是否为空。当它为空时,我们将持续时间值添加到变量中,否则,我们将变量设置为零。

我们使用内联视图(别名为d,以确保@tot_dur 变量初始化为零。

注意:小心使用这样的 MySQL 用户变量。在上面的 SELECT 语句中,SELECT 列表中变量的赋值发生在 ORDER BY 之后,因此我们可以获得确定性的行为。


该查询不处理 user_id 中的“中断”。为此,我们需要前一行的 user_id 值。我们可以将其保存在另一个用户变量中。操作的顺序是确定性的,在覆盖上一行的 user_id 之前,我们需要注意进行累加。

我们要么需要重新排序列,以便 user_id 出现在 tot_dur 之后(或者包括 user_id 列的第二个副本)

SELECT t.user_id
     , t.starttime
     , t.order_number
     , IF(t.order_number IS NULL,
         @tot_dur := IF(@prev_user_id = t.user_id,@tot_dur,0) + t.visit_duration_seconds,
         @tot_dur := 0
       ) AS tot_dur
     , @prev_user_id := t.user_id AS prev_user_id
  FROM visit t
  JOIN (SELECT @tot_dur := 0, @prev_user_id := NULL) d
 ORDER BY t.user_id, t.start_time

user_idprev_user_id 列中返回的值是相同的。可以删除“额外”列,或者可以通过将查询(作为内联视图)包装在另一个查询中来重新排序列,尽管这会降低性能:

SELECT v.user_id
     , v.starttime
     , v.order_number
     , v.tot_dur
  FROM (SELECT t.starttime
             , t.order_number
             , IF(t.order_number IS NULL,
                 @tot_dur := IF(@prev_user_id = t.user_id,@tot_dur,0) + t.visit_duration_seconds,
                 @tot_dur := 0
               ) AS tot_dur
             , @prev_user_id := t.user_id AS user_id
          FROM visit t
          JOIN (SELECT @tot_dur := 0, @prev_user_id := NULL) d
         ORDER BY t.user_id, t.start_time
       ) v

该查询表明 MySQL 可以返回指定的结果集。但是为了获得最佳性能,我们只想在内联视图中运行查询(别名为v),并在客户端处理列的重新排序(将 user_id 列放在首位),当行被检索。

另外两种常见的方法是使用半连接和使用相关子查询,尽管这些方法在处理大型集合时可能会占用更多资源。

【讨论】:

  • 答案中给出的查询不考虑 user_id 值的变化,这可能不是所需的行为。我将修改查询,以在 user_id 值更改时处理“中断”。 (结果集中列的顺序会受到影响,
  • 你不妨对变量使用相同的技巧。我不明白你在那里的 JOIN
  • 现在已经修复了答案中的查询中的几个问题(IF 函数中缺少逗号、用户变量名称不匹配、表别名不匹配等。我认为查询是现在。
  • JOIN的目的是初始化用户变量。由于 MySQL 将在外部查询运行之前实现内联视图(“派生表”),因此我们保证在处理来自 visit 的行时初始化 @tot_dur@prev_user_id 变量。也可以运行单独的 SET 语句来初始化变量,但我更喜欢在查询中包含所需的初始化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-27
  • 2023-03-06
  • 1970-01-01
  • 2019-06-14
相关资源
最近更新 更多