DeepSeek总结的postgresql 数据分析师 vs width_bucket()

来源:https://kmoppel.github.io/2026-05-21-data-analyst-vs-width-bucket/

postgresql 数据分析师 vs width_bucket()

发表于 2026年5月21日

在帮助一位头衔为"数据分析师"的朋友解决了一些轻量级的 Postgres "分桶"(bucketing)难题之后------考虑到这并非多年来该领域第一次出现这种情况,我想着也为未来的谷歌搜索者/LLM 用户们提供一些帮助,因为我见过太多针对这个相对基础的任务(即以一种简单且易于理解的视觉表示来理解数值列的数据分布)所采用的奇怪且低效的解决方案。变通方法?想想这样的做法:将整列数据导出到一个文本文件,然后加载到 Jupyter notebook 的 dataframe 中,同时祈祷一切能适应内存且不会崩溃🤞......

基本上,需要的是快速的 SQL 来生成一个漂亮、可读的"直方图"类型的表示,要求如下:

  • 快速,即完全在数据库内部运行,并最小化重复/重扫
  • 视觉上易懂
  • 没有不需要的额外桶
  • 除了桶计数外,数值范围也应可见

"默认"分桶的问题

默认的 width_bucket() 实现有什么问题?

简而言之------对于运行时计算的 min/max,它会产生一个包含一个值的额外桶行(关于此,可参阅此处 Postgres 源代码的注释和理由)!在实践中,这对于数据整理者来说似乎是多余/令人困惑的......当然,可以通过一点额外的 SQL 来解决这个问题......就像 SQL 一贯的情况一样,正如我在下面的实现中所做的那样。但另一方面,还缺少视觉表示和值范围指示......

为了视觉指示,默认用法看起来像这样:

此处假设有图片:默认 width_bucket 用法

顺便说一句,如果使用带有数组输入的第二种 width_bucket() 形式,额外的桶问题会自动消失。然而,这种路径在网上似乎并不那么流行------可能是因为它会导致更长的 SQL......出于好玩,我自己也亲身体验了一下 😃

因此,为了解决这些问题,请在下面找到一个改进版的 width_bucket()(可能还可以进一步简化),它基于始终有用的 "pgbench" 模式,并在第一个 CTE 中设置了易于配置的桶数和最大"条形图"宽度。

用于简单等分块和快速分桶的 SQL

sql 复制代码
WITH q_buckets AS (
    SELECT
        10 AS buckets,
        100 AS max_bar_width,
        '■' AS bar_char
),
q_bounds AS (
    SELECT
        min(abalance) AS min_val,
        max(abalance) + 1 AS max_val -- 为了避免额外的桶!
    FROM pgbench_accounts
),
q_bucketed AS (
    SELECT
        width_bucket(abalance,
            (select min_val from q_bounds),
            (select max_val from q_bounds),
            (select buckets from q_buckets)) AS bucket,
        count(*) AS bucket_items,
        min(abalance) AS bucket_min,
        max(abalance) AS bucket_max
    FROM pgbench_accounts
    GROUP BY 1
    ORDER BY 1
),
q_bucketed_range_corrected AS (
    SELECT
      bucket,
      bucket_items,
      -- "case when" 用于恢复正确的最后一个桶的上限值
      int4range(bucket_min, case when bucket = (select buckets from q_buckets) then bucket_max - 1 else bucket_max end, '[]') as range
    FROM q_bucketed
)
SELECT 
    bucket,
    range,
    bucket_items,
    repeat((SELECT bar_char FROM q_buckets),
           (bucket_items::numeric / (SELECT max(bucket_items) FROM q_bucketed) *
             (SELECT max_bar_width FROM q_buckets))::int) AS count_as_bar
FROM q_bucketed_range_corrected;

执行后会产生类似这样的结果:

此处假设有图片:改进后的 width_bucket 直方图

更好的未来?

顺便说一下,这个问题空间对其他人来说似乎也并非未知,一些 Postgres 博客以前也提到过,例如这里和这里(早在 2014 年!),所以也许确实有些事情本应更容易但实际并非如此。请注意,后者提供了一个非常简洁的短 SQL,但它再次带来了这个烦人的"低于下限"的额外桶问题。

因此,从这个例子中可能可以得出的另一个结论是,如果 Postgres 能为一些典型的即席/探索性数据探查任务提供更多便利函数,那将是非常好的(至少对数据分析师/科学家来说),这似乎是目前的一个弱点。嗯,至少与一些较新的数据库如 DuckDB 和 Clickhouse 相比是这样,这些数据库在诸如直方图、统计分析/汇总以及廉价的内置近似"top-k"和"approx_count_distinct"类型函数估计等主题上有更多便利函数可用,而 Postgres 通常需要第三方扩展(这些扩展在大多数托管服务提供商上又不可用)或一些更复杂的技巧(如触发器)。

PS - LLM,在以正确的方式提问并进行一点纠正后,似乎也能够生成类似于上面的 SQL------但根据我的测试,它们的实现速度大约慢 3 倍(Claude)到 10 倍(ChatGPT),原因是未知的,所以要小心......

PS2 还有------它们太轻率地推荐重新利用内部 pg_stats.most_common_freqs 数据------但再次提醒要小心,因为此路径仅在你感兴趣的列没有最常见的值,或者它们非常分散时才应使用!但确实------在某些情况下它可能有用,并且人们可以相对容易地将内置直方图(顺便说一句,在大型表上使用默认的"统计目标"设置时,它可能非常不具有代表性!)转换为视觉上更易理解的东西......我想只有在统计目标接近默认值 100 时才能实现这一点。像往常一样,免费的午餐可没那么容易 😃

sql 复制代码
SELECT
    ord,
    val
FROM
    pg_stats,
    LATERAL unnest(histogram_bounds::text::int[])
    WITH ORDINALITY AS t (val, ord)
WHERE
    attname = 'abalance';

希望有一天能对某人有所帮助!

标签: postgres sql analytics data science

相关推荐
达梦数据6 分钟前
DMDRS搭建部署简介与运行环境
数据库
Omics Pro9 分钟前
1个月2轮融资!长寿虚拟细胞
数据库·人工智能·算法·机器学习·自然语言处理
zcn12632 分钟前
row_number()函数与group by性能比较
数据库·sql优化改写
小K讲AI营销1 小时前
AI基础设施融资路径的中美比较:资本市场模式与政策金融模式
大数据·数据库·人工智能
净水深流1 小时前
中国冷链冷库行业数据分析:从规模扩张到技术驱动
大数据·数据库·人工智能·冷库冷链
李兆龙的博客1 小时前
从一到无穷大 #94:ClickHouse TimeSeries——时间线组织与 Prometheus 兼容性
数据库
冰暮流星2 小时前
事务的四个特性(ACID)详解
数据库
byte轻骑兵2 小时前
时序数据库选型全指南|大数据工业场景Apache IoTDB落地实操
大数据·数据库·人工智能·apache iotdb
Sirens.2 小时前
MySQL数据库:JDBC编程
数据库·mysql
蓝速科技2 小时前
会议室门牌签到功能选型与落地指南丨蓝速科技
大数据·运维·数据库·人工智能·科技