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 小时前
Redis
数据库·redis·mysql·缓存·docker·容器·github
dinl_vin6 小时前
FastAPI 系列 ·(四):数据库集成——SQLAlchemy 2.0 异步 ORM 与 Alembic 迁移
java·数据库·fastapi
坚定信念,勇往无前7 小时前
electron-vite 安装better-sqlite3
javascript·数据库·electron
大明者省7 小时前
Ubuntu22.04 宝塔面板与 XFCE 远程桌面端口兼容性分析
运维·服务器·数据库·笔记
liudanzhengxi8 小时前
巧用ULN2003A轻松扩展单片机IO口
数据库·mongodb
Teable任意门互动8 小时前
深度解析:AI 赋能开源多维表格,实现企业全场景数据整合与高效应用
数据库·人工智能·低代码·信息可视化·开源·数据库开发
DevOpenClub8 小时前
职教高考及高职分类招生控制线 API 接口
java·数据库·高考
funnycoffee1238 小时前
华为S5736交换机3层ECMP负载方式
linux·服务器·数据库