MySQL 8.0.19+ 的 HISTOGRAM 函数仅用于优化器统计,不能直接返回直方图数据;需用 FLOOR 或 NTILE 手动分桶,结合过滤异常值与 NULL 才能准确可视化分布。

用 HISTOGRAM 函数直接生成分组频次(MySQL 8.0.19+)
MySQL 8.0.19 起原生支持 HISTOGRAM,但它只用于查询优化器统计,**不能直接返回直方图数据**。别被名字误导——你执行 SELECT JSON_EXPLAIN(...) 看到的只是内部估算,无法按部门、按薪资区间取数。想可视化,得自己分桶。
手动分桶:用 FLOOR 或 CASE WHEN 划分薪资区间
核心是把连续的 salary 映射成离散的区间标签,再按部门 + 区间聚合计数。推荐用 FLOOR 避免写一堆 CASE:
SELECT dept, FLOOR(salary / 5000) * 5000 AS salary_bin, COUNT(*) AS freq FROM employees GROUP BY dept, salary_bin ORDER BY dept, salary_bin;
这会把薪资按每 5000 元一档分组(如 0–4999 → 0,5000–9999 → 5000)。注意:FLOOR(salary / 5000) * 5000 比 ROUND(salary, -3) 更可控,不会因四舍五入把 4999 错分到 5000 档。
- 若需左闭右开区间(如 [5000, 10000)),用
FLOOR((salary - 1) / 5000) * 5000 + 5000 - 避免用
CAST(salary AS SIGNED)强转后再除——浮点误差可能让 9999.999 变成 9999,导致分桶偏移 - PostgreSQL 用户请改用
FLOOR(salary / 5000.0)::INT * 5000,显式指定除法为浮点
动态区间宽度:用 NTILE 做等频直方图(非等宽)
当各部门薪资分布差异大(比如技术部集中在 15k–30k,行政部在 6k–12k),固定宽度分桶会导致某些部门大量空桶或桶内不均。这时用 NTILE(10) OVER (PARTITION BY dept ORDER BY salary) 把每部门薪资等分为 10 组,每组频次自然接近:
SELECT
dept,
ntile_group,
COUNT(*) AS freq
FROM (
SELECT
dept,
NTILE(10) OVER (PARTITION BY dept ORDER BY salary) AS ntile_group
FROM employees
) t
GROUP BY dept, ntile_group
ORDER BY dept, ntile_group;NTILE 的坑在于:如果某部门人数不能被 10 整除,前几组会多 1 人,但整体仍比固定宽度更反映实际分布形态。它不输出具体薪资范围,只保证每组人数尽量相等——适合对比部门间“相对分布形状”,不适合展示绝对薪资水平。
导出到 Python / Excel 前,先处理 NULL 和异常值
真实数据里常有 salary IS NULL、salary < 0 或明显异常(如 CEO 薪资 9999999)。这些不处理会污染分桶结果:
- 加
WHERE salary > 0 AND salary < 1000000过滤离群值(阈值按业务定) - 用
COUNT(*) FILTER (WHERE salary IS NOT NULL)(PostgreSQL)或SUM(CASE WHEN salary IS NOT NULL THEN 1 ELSE 0 END)(MySQL)单独统计有效记录数 - 别依赖
AVG(salary)算中位数——它对异常值极度敏感;真要稳健中心趋势,得用PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary)(PostgreSQL)或子查询模拟
直方图不是画出来就完事;桶怎么划、异常怎么筛、NULL 怎么归类,每个选择都会改变你看到的分布形态。尤其跨部门比较时,统一的过滤逻辑和分桶策略比绘图工具本身重要得多。

















