3

我有一个难以解决的问题,我想你可以帮忙。我有一个包含数百万条记录的表,其中每 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

有没有人可以解决这个问题?已经,感谢您的关注。

4

2 回答 2

7

我有一张包含数百万条记录的表,其中每 10 分钟精确分组一次

tl; dr:对于不耐烦的人,请参阅答案中的最后一个查询,这是真正的解决方案,其他人则逐步了解如何到达那里。此外,所有查询 + 模式都可以在 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_trunc函数date_part(后者可以被标准调用EXTRACTinterval数据类型。让我们一步一步构建解决方案,最终的想法是有这样的东西(现在是伪代码):

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是 with 'hour',它已经失去了我们需要的信息,所以我们需要介于'minute'and之间的东西'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,现在我们只需要减去 8 分钟,我们可以:

  1. EXTRACT(MINUTE FROM tscol)将返回18
  2. 因为我们想要截断 10 分钟,让我们取 的模18 and 10,所以18 % 10这给了我们8
  3. 现在,我们有了8要减去的分钟数,但是作为整数,要减去timestamp[tz]我们需要一个interval,因为整数代表分钟,我们可以这样做:8 * interval '1 minute',这将给我们00:08:00

在最后一个查询中获得上述 3 个步骤,我们有(我将展示每一列以便更好地理解):

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;

返回:

           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
...

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

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;

返回:

    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)

那是您给出的确切输出,但我相信这是您实际期望的,如果不是,这只是一个小的调整问题。

于 2015-07-01T13:10:04.683 回答
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
        ;
于 2015-07-01T14:13:51.130 回答