将数据按频率分组成存储桶在Postgres 11.6中。

3
使用Postgres 11.6,我正在尝试分析一些事件数据。目标是找到所有具有特定名称的事件的持续时间,然后将每个事件均匀地分成桶。我们寻找任何特定事件的“簇”时间。在这里,我编辑我的问题,因为特定情况可能会掩盖我想要询问的内容。
简单示例
问题是“如何按值对行进行分组,然后按频率将发生次数分成带有计数和平均值的桶。” 这里是手动完成的玩具示例,并取得了平均值:
带有值的月份,这里的每个数字表示一行。
Jan    12    24    60    150    320    488                        
Feb     8    16    40    100    220                            
Mar     4     8    20    310                                

总体数据

Month    Count    Avg    Min    Max                                
Jan      6        176    12    488                                
Feb      5         77     8    220                                
Mar      4         86     4    310                                

相同的原始数据,但包含更多数据,其中包括重复的值和更广泛的范围。
Jan    12    12    12    12    24    24    60     60    150    320     488    500
Feb     8     8     8     8     8    16    40    100    220    440    1100    
Mar     4     8     8     8     8    20    20     20     20    310        

总体数字

Month    Count    Avg    Min     Max                                
Jan      12       140    12      500                                
Feb      11       178     8     1100                                
Mar      10        43     4      310                                

其中之一数据集划分成3个桶的模拟

Month    Count    Avg    Min    Max    Bucket                            
Jan      4         12     12     12    0                            
Jan      4         42     24     60    1                            
Jan      4        365    150    500    2                            
...and so on for Feb and Mar

我只是猜测以上模型中的桶会如何分配。

这基本上就捕捉到了我尝试做的事情。按月份名称(在我的实际情况中是从_to_node)进行分组,将结果行分成桶,然后为每个桶获取最小值、最大值、平均值和计数。听起来像是一个数据透视表(pivot)?

真实表格设置

以下是我正在获取供稿的表格结构:

CREATE TABLE IF NOT EXISTS data.edge_event (
    id uuid,
    inv_id uuid,
    facility_id uuid,
    from_node citext,
    to_node citext,
    from_to_node citext,
    from_node_dts timestamp without time zone,
    to_node_dts timestamp without time zone,
    seconds integer,
    cycle_id uuid
);

持续时间是以秒为单位预先计算的,目前感兴趣的区域仅限于from_to_node名称。因此,可以将示例视为

CREATE TABLE IF NOT EXISTS data.edge_event (
    from_to_node citext,
    seconds integer
);

原始数据

edge_event表中,大约有300K个事件行,其中包含159个不同的from_to_node值。有些只在少数edge_event记录中找到,有些则在成千上万个记录中找到。这样太多了,无法提供良好的样本。但是为了使问题更容易理解,from_to_node可能是:

Boxing_Assembly 1256

表示“将该零件从装箱阶段移动到装配阶段需要1256秒”。对于“Boxing_Assembly”,我们可能有其他10,000条不同持续时间的记录。

目标

我们希望从每个from_to_node中获得两个内容。对于像“Boxing_Assembly”这样的内容,我正在尝试做到以下两点:

  1. 将耗时分为桶,例如20个桶。这是制作直方图所需的。

  2. 对于每个桶,获取以下内容:

    • edge_event行的计数
    • 桶内avg(seconds)
    • 桶内min/first_value(seconds)
    • 桶内max/last_value(seconds)

因此,我们正在查看持续时间以寻找聚类,然后从任何常见聚类中获取原始秒数。

尝试过的方法

我尝试了很多不同的代码,但都没有成功。似乎这是一个需要GROUP BY和/或窗口函数的问题。由于我的结果与目标相差甚远,所以肯定有些东西我没有弄懂。

我知道我没有提供样本数据,这使得帮助变得更加困难。但我猜我缺少一些概念。基本上,我想知道如何按from_to_nodeseconds拆分edge_event数据。考虑到from_to_nodes的巨大范围,我试图根据它们自己的最小值/最大值单独对每个桶进行分组。

非常感谢您的帮助。

草稿尝试

我开发了一条查询语句,有点可行,但并非完全正确。这是我的原始发布帖子的编辑版本。

WITH
min_max AS
(
    SELECT from_to_node,
           min(seconds),
           max(seconds)

     FROM edge_event

 GROUP BY from_to_node
) 

   SELECT edge_event.from_to_node,
           width_bucket (seconds, min_max.min, min_max.max, 99) as bucket, -- Bucket are counted from 0, so 9 gets you 10 buckets, if you have enough data.
          count(*) as frequency,
          min(seconds) as seconds_min,
          max(seconds) as seconds_max,
          max(seconds) - min(seconds) as bucket_width,
          round(avg(seconds)) as seconds_avg

      FROM edge_event
      JOIN min_max ON (min_max.from_to_node = edge_event.from_to_node)

     WHERE min_max.min <> min_max.max AND -- Can't have a bucket with an upper and lower bound that are the same.
             edge_event.from_to_node IN (
                'Boxing_Assembly',
                'Assembly_Waiting For QA')

  GROUP BY edge_event.from_to_node,
             bucket

  ORDER BY from_to_node,
           bucket

我得到的结果看起来相当不错:
from_to_node            bucket   frequency    seconds_min   seconds_max     bucket_width   seconds_avg
Boxing_Assembly              1         912            17           7052     7035                  3037
Boxing_Assembly              2         226          7058          13937     6879                  9472
Boxing_Assembly              3          41         14151          21058     6907                 16994
Boxing_Assembly              4          16         21149          27657     6508                 23487
Boxing_Assembly              5           4         28926          33896     4970                 30867
Boxing_Assembly              6           1         37094          37094        0                 37094
Boxing_Assembly              7           1         43228          43228        0                 43228
Boxing_Assembly             10           2         63666          64431      765                 64049
Boxing_Assembly             14           1         94881          94881        0                 94881
Boxing_Assembly             16           1         108254        108254        0                108254
Boxing_Assembly             37           1         257226        257226        0                257226
Boxing_Assembly             40           1         275140        275140        0                275140
Boxing_Assembly             68           1         471727        471727        0                471727
Boxing_Assembly            100           1         696732        696732        0                696732
Assembly_Waiting For QA      1       41875              1         18971    18970                   726
Assembly_Waiting For QA      9           1         207457        207457        0                207457
Assembly_Waiting For QA     15           1         336711        336711        0                336711
Assembly_Waiting For QA     38           1         906519        906519        0                906519
Assembly_Waiting For QA    100           1        2369669       2369669        0               2369669

一个问题在于这些桶的大小并不均匀...它们看起来有点奇怪。我也尝试过指定10、20或100个桶,但结果相似。我希望有一种更好的方法来分配数据到桶中,以及有一种方法可以让空的桶变成零条目桶。


1
你可能想要研究一下Postgres的width_bucket函数。此外,发布文本格式的样本数据和预期结果将极大地增强您获得满意的答案。 - Belayer
感谢您的评论,我刚刚添加了一个模拟数据的示例(作为文本),以更好地传达想法。是的,我怀疑 width_bucket 就是我想要的... 我在之前发布的失败示例中尝试过它。 - Morris de Oryx
我已经更新了我的问题,并提供了一个草稿,它比我原来的错误示例更好。 - Morris de Oryx
1个回答

0

我会使用PostgreSQL优化器来完成这个任务。它可以准确地收集你所需的信息。

创建一个临时表格,包含你感兴趣的数值,并对其进行ANALYZE操作。然后查看pg_stats,寻找以下内容:

  • 如果有“最常见的数值”,你可以在那里找到它们及其频率。

  • 否则,寻找彼此相近的直方图边界。这样的桶是一个区间,其中数值被“聚合”在一起。


嘿,这个主意不错,我会试试看的。你觉得调整ALTER TABLE SET STATISTICS能适用于这种情况吗? - Morris de Oryx
当然,如果您需要超过100个桶。 - Laurenz Albe
这个使用 pg_stats 的想法似乎非常聪明,也很有前途。基本思路是 1)选择数据,2)将其推入临时表中,3)进行 ANALYZE,4)unnest 直方图边界,5)针对这些边界进行 LEFT JOIN 以将源数据分桶?这似乎应该能够很好地工作,但它并不是一个显而易见的解决方案,并且完全依赖于 Postgres。在 Postgres 中是否有另一种从系列生成直方图边界的方法? - Morris de Oryx
当然,您可以重新实现PostgreSQL收集统计信息的方法,包括Vittel算法等。您已经有了这个想法,但查询将稍微复杂一些,因为您需要计算相邻直方图边界之间的差异。我认为所有值得拥有的数据库都应该在其统计信息中拥有直方图,但当然实现方式会千差万别。 - Laurenz Albe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接