PostgreSQL - GROUP BY每行10分钟

时间:2015-07-01 11:46:12

标签: postgresql time group-by minute

我有一个难以解决的问题,我想你可以提供帮助。 我有一个包含数百万条记录的表,根据注册表值每隔10分钟精确分组一次,例如:

  

记录“01 | 2011/01/03 19:18:00.300”它需要计算的时间   记录是19:18:00.300至19:28:00.299。通过这个过程,它会   小组记录01,02,03。

     

记录“04 | 2011/01/03 19:29:54.289”它需要计算的时间   记录是19:29:54.289至19:39:54.288。通过这个过程,它会   组仅记录04记录。

     

记录“05 | 2011/01/04 14:43:43.067”,他需要计算的时间   记录是14:43:43.067到14:43:53.066。通过这个过程,它会   小组记录05,06,07。

     

记录“08 | 2011/01/04 14:57:55.608;”它需要计算的时间   记录是14:57:55.608至15:07:55.607。通过这个过程,它会   小组记录08,09,10,11,12,13,14,15。

输入数据:

ID   TS
01   2011/01/03 19:18:00.300
02   2011/01/03 19:18:00.503
03   2011/01/03 19:20:26.335
04   2011/01/03 19:29:54.289
05   2011/01/04 14:43:43.067
06   2011/01/04 14:50:10.727
07   2011/01/04 14:52:26.827
08   2011/01/04 14:57:55.608
09   2011/01/04 14:57:55.718
10   2011/01/04 14:59:13.603
11   2011/01/04 15:00:34.260
12   2011/01/04 15:02:55.687
13   2011/01/04 15:04:51.917
14   2011/01/04 15:06:24.760
15   2011/01/04 15:07:15.378

输出数据:

ID  TS   Count
01   2011/01/03 19:18:00.300    3
02   2011/01/03 19:29:54.289    1
03   2011/01/04 14:43:43.067    3
04   2011/01/04 14:57:55.608    8

有没有人能解决这个问题? 已经,感谢你的关注。

2 个答案:

答案 0 :(得分:2)

  

我有一张包含数百万条记录的表格   10分钟

tl; dr:对于不耐烦的人,请参阅答案中的最后一个查询,这是真正的解决方案,其他人是如何到达那里的一步一步。另外,all queries + schemas are available at SQLFiddle,对于那些想玩的人来说。

在我看来,针对此类问题的最佳解决方案是将每个时间戳截断为10分钟的开头,例如,让我们尝试进行以下转换(original -> 10 minutes truncated):

13:10 -> 13:10
13:15 -> 13:10
13:18 -> 13:10
13:20 -> 13:20
...

如果有人想尝试以下查询,您可以创建架构:

CREATE TABLE your_table(tscol timestamptz);
INSERT INTO your_table VALUES
('2011/01/03 19:18:00.300'),
('2011/01/03 19:18:00.503'),
('2011/01/03 19:20:26.335'),
('2011/01/03 19:29:54.289'),
('2011/01/04 14:43:43.067'),
('2011/01/04 14:50:10.727'),
('2011/01/04 14:52:26.827'),
('2011/01/04 14:57:55.608'),
('2011/01/04 14:57:55.718'),
('2011/01/04 14:59:13.603'),
('2011/01/04 15:00:34.260'),
('2011/01/04 15:02:55.687'),
('2011/01/04 15:07:15.378');

因此,为了做到这一点,我们需要了解date_truncdate_part functions (the latter can be invoked by the standard EXTRACT)以及interval data type。让我们一步一步地构建解决方案,最后的想法就是拥有这样的东西(现在是伪代码):

SELECT truncate_the_time_by_10_minutes(tscol) AS trunc10, count(*)
FROM your_table
GROUP BY trunc10
ORDER BY trunc10;

现在,如果问题是“按分钟聚合”,那么我们可以简单地将时间戳截断为分钟,这简单意味着将秒和微秒归零,这正是date_trunc('minute', ...)所做的,所以:

SELECT date_trunc('minute', tscol) AS trunc_minute, count(*)
FROM your_table
GROUP BY trunc_minute
ORDER BY trunc_minute;

可行,但它不是你想要的,date_trun的下一个功能是'hour',它已经失去了我们需要的信息,因此我们需要'minute''hour'之间的内容。 SELECT tscol, date_trunc('minute', tscol) AS trunc_minute FROM your_table ORDER BY tscol; 。让我们看看上面的查询如何使用一些例子:

           tscol            |      trunc_minute      
----------------------------+------------------------
 2011-01-03 19:18:00.3-02   | 2011-01-03 19:18:00-02
 2011-01-03 19:18:00.503-02 | 2011-01-03 19:18:00-02
 2011-01-03 19:20:26.335-02 | 2011-01-03 19:20:00-02
 2011-01-03 19:29:54.289-02 | 2011-01-03 19:29:00-02
...

返回:

2011-01-03 19:18:00-02

如果您看到EXTRACT(MINUTE FROM tscol),现在我们只需要减去8分钟,我们可以这样做:

  1. 18 将返回 18 and 10
  2. 由于我们要截断10分钟,让我们采用18 % 10的模数,所以 8 给我们 8
  3. 现在,我们要减去timestamp[tz]分钟,但作为整数,并从interval减去,我们需要8 * interval '1 minute',因为整数表示分钟,我们可以这样做: 00:08:00 ,这将为我们提供 SELECT tscol, date_trunc('minute', tscol) AS trunc_minute, CAST(EXTRACT(MINUTE FROM tscol) AS integer) % 10 AS min_to_subtract, (CAST(EXTRACT(MINUTE FROM tscol) AS integer) % 10) * interval '1 minute' AS interval_to_subtract, date_trunc('minute', tscol) - (CAST(EXTRACT(MINUTE FROM tscol) AS integer) % 10) * interval '1 minute' AS solution FROM your_table ORDER BY tscol;
  4. 在上一个查询中获得上述3个步骤,我们(我将展示每个列以便更好地理解):

               tscol            |      trunc_minute      | min_to_subtract | interval_to_subtract |        solution        
    ----------------------------+------------------------+-----------------+----------------------+------------------------
     2011-01-03 19:18:00.3-02   | 2011-01-03 19:18:00-02 |               8 | 00:08:00             | 2011-01-03 19:10:00-02
     2011-01-03 19:18:00.503-02 | 2011-01-03 19:18:00-02 |               8 | 00:08:00             | 2011-01-03 19:10:00-02
     2011-01-03 19:20:26.335-02 | 2011-01-03 19:20:00-02 |               0 | 00:00:00             | 2011-01-03 19:20:00-02
     2011-01-03 19:29:54.289-02 | 2011-01-03 19:29:00-02 |               9 | 00:09:00             | 2011-01-03 19:20:00-02
    ...
    

    返回:

    SELECT
        date_trunc('minute', tscol) - (CAST(EXTRACT(MINUTE FROM tscol) AS integer) % 10) * interval '1 minute' AS trunc_10_minute,
        count(*)
    FROM your_table
    GROUP BY trunc_10_minute
    ORDER BY trunc_10_minute;
    

    现在,最后一列是我们想要的解决方案,时间戳被截断为10分钟组,现在我们可以简单地汇总并获得最终解决方案

        trunc_10_minute     | count 
    ------------------------+-------
     2011-01-03 19:10:00-02 |     2
     2011-01-03 19:20:00-02 |     2
     2011-01-04 14:40:00-02 |     1
     2011-01-04 14:50:00-02 |     5
     2011-01-04 15:00:00-02 |     5
    (5 rows)
    

    返回:

    insta.controler 'instaCtrl' ($scope, $http), ->
       $http.get('http://api.instagram.com/publicapi/oembed/?url=http://instagr.am/p/fA9uwTtkSN/')
           .success(data), ->
               #done !
           .error(e), ->
               #nah ! 
    

    这就是你给出的确切输出,但我相信这是你真正期望的,如果不是这只是一个小调整的问题。

答案 1 :(得分:0)

这可能有点次优,但它确实有效。递归查询检测间隔的开始和停止时间; count(*)标量子查询计算每个区间内的原始记录数。

WITH RECURSIVE rr AS (
        SELECT 1::integer AS num
                , MIN(tscol) AS starter
                , MIN(tscol) + '10 min'::INTERVAL AS stopper
        FROM your_table
        UNION ALL
        SELECT
                1+rr.num AS num
                , tscol AS starter
                , tscol + '10 min'::INTERVAL AS stopper
        FROM your_table yt
        JOIN rr ON yt.tscol > rr.stopper
                AND NOT EXISTS ( SELECT *
                  FROM your_table nx
                  WHERE nx.tscol > rr.stopper
                  AND nx.tscol < yt.tscol
                )
        )
SELECT num,starter,stopper
        , (SELECT COUNT(*) FROM your_table yt
                WHERE yt.tscol BETWEEN rr.starter AND rr.stopper
        ) AS cnt
FROM rr
        ;