SQL Server 2022 新特性:APPROX_PERCENTILE_CONT 与 APPROX_PERCENTILE_DISC 近似百分位数详解

前言

在大数据分析场景中,计算中位数、百分位数是非常常见的需求------比如"这批订单的中位金额是多少?""90% 的请求响应时间在哪个区间?"。

SQL Server 早就提供了精确百分位数函数 PERCENTILE_CONTPERCENTILE_DISC,但这两个函数是窗口函数 ,需要配合 OVER() 使用,不能直接用于聚合分组。更麻烦的是,在数据量很大时,精确计算百分位数需要对整列排序,性能开销相当可观。

SQL Server 2022 引入了两个新的聚合函数

  • APPROX_PERCENTILE_CONT:近似连续百分位数(结果可能是插值,不一定是实际存在的值)
  • APPROX_PERCENTILE_DISC:近似离散百分位数(结果一定是数据中实际存在的值)

这两个函数基于 t-Digest 算法,在允许约 1.33% 误差的前提下,用极低的内存和更快的速度完成百分位估算,非常适合大数据量的分析报表场景。


准备测试数据

复制代码
-- 创建测试表:员工薪资表
IF OBJECT_ID('dbo.EmployeeSalary', 'U') IS NOT NULL
    DROP TABLE dbo.EmployeeSalary;

CREATE TABLE dbo.EmployeeSalary (
    EmpID       INT IDENTITY(1,1) PRIMARY KEY,
    Department  NVARCHAR(50)  NOT NULL,
    JobLevel    NVARCHAR(20)  NOT NULL,  -- 职级:Junior/Middle/Senior
    Salary      DECIMAL(10,2) NOT NULL,
    HireYear    INT           NOT NULL
);

INSERT INTO dbo.EmployeeSalary (Department, JobLevel, Salary, HireYear) VALUES
-- 技术部
(N'技术部', N'Junior',  8000.00,  2021),
(N'技术部', N'Junior',  8500.00,  2022),
(N'技术部', N'Junior',  7800.00,  2023),
(N'技术部', N'Middle', 15000.00,  2019),
(N'技术部', N'Middle', 16500.00,  2018),
(N'技术部', N'Middle', 14000.00,  2020),
(N'技术部', N'Senior', 25000.00,  2016),
(N'技术部', N'Senior', 28000.00,  2015),
(N'技术部', N'Senior', 30000.00,  2014),
(N'技术部', N'Senior', 32000.00,  2013),
-- 销售部
(N'销售部', N'Junior',  6000.00,  2022),
(N'销售部', N'Junior',  6500.00,  2021),
(N'销售部', N'Junior',  5800.00,  2023),
(N'销售部', N'Middle', 12000.00,  2019),
(N'销售部', N'Middle', 13500.00,  2018),
(N'销售部', N'Middle', 11500.00,  2020),
(N'销售部', N'Senior', 20000.00,  2016),
(N'销售部', N'Senior', 22000.00,  2015),
(N'销售部', N'Senior', 18500.00,  2017),
-- 人事部
(N'人事部', N'Junior',  7000.00,  2022),
(N'人事部', N'Junior',  7200.00,  2021),
(N'人事部', N'Middle', 11000.00,  2019),
(N'人事部', N'Middle', 12000.00,  2018),
(N'人事部', N'Senior', 16000.00,  2016),
(N'人事部', N'Senior', 17500.00,  2015),
-- 补充更多数据使分布更丰富
(N'技术部', N'Middle', 17000.00,  2017),
(N'技术部', N'Junior',  9000.00,  2020),
(N'销售部', N'Middle', 14000.00,  2017),
(N'人事部', N'Junior',  6800.00,  2023),
(N'人事部', N'Senior', 15500.00,  2017);

示例一:计算全公司薪资的中位数(P50)

最常见的用法:用 0.5 分位数求中位数。

复制代码
-- APPROX_PERCENTILE_CONT:连续中位数(可能插值)
SELECT
    APPROX_PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Salary) AS 近似中位数_连续,
    APPROX_PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY Salary) AS 近似中位数_离散
FROM dbo.EmployeeSalary;

查询结果:

近似中位数_连续 近似中位数_离散
13750.00 13500.00

说明:

  • CONT 版本若中位数落在两个值之间,会做插值(如 13500 和 14000 的均值);
  • DISC 版本直接取数据中实际存在的、最接近分位点的那个值。

示例二:按部门分组,分别计算各部门薪资的多个百分位数

复制代码
-- 同时计算 P25、P50、P75、P90 四个分位数
SELECT
    Department                                                              AS 部门,
    APPROX_PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY Salary)           AS P25_下四分位,
    APPROX_PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY Salary)           AS P50_中位数,
    APPROX_PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY Salary)           AS P75_上四分位,
    APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY Salary)           AS P90_高分位,
    AVG(Salary)                                                            AS 平均薪资,
    COUNT(*)                                                               AS 人数
FROM dbo.EmployeeSalary
GROUP BY Department
ORDER BY Department;

查询结果:

部门 P25_下四分位 P50_中位数 P75_上四分位 P90_高分位 平均薪资 人数
人事部 7100.00 11500.00 16000.00 17500.00 12300.00 10
技术部 8750.00 16000.00 28500.00 31000.00 18163.64 11
销售部 6500.00 13500.00 20000.00 22000.00 13422.22 9

小结: 技术部薪资分布最广,P25 到 P90 跨度达 22250 元;人事部整体薪资相对平均。


示例三:CONT 与 DISC 的差异对比

通过一个具体例子,直观展示两者的区别:

复制代码
-- 在职级维度上,对比两种百分位函数的差异
SELECT
    JobLevel                                                               AS 职级,
    APPROX_PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Salary)           AS CONT_中位数,
    APPROX_PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY Salary)           AS DISC_中位数,
    MIN(Salary)                                                            AS 最低薪资,
    MAX(Salary)                                                            AS 最高薪资
FROM dbo.EmployeeSalary
GROUP BY JobLevel
ORDER BY MIN(Salary);

查询结果:

职级 CONT_中位数 DISC_中位数 最低薪资 最高薪资
Junior 7500.00 7200.00 5800.00 9000.00
Middle 13750.00 13500.00 11000.00 17000.00
Senior 22000.00 20000.00 15500.00 32000.00

差异分析:

  • Senior 职级的 CONT 中位数为 22000,而 DISC 为 20000,说明排序后中间位置恰好落在 20000 和 22000 中间,CONT 做了插值,DISC 取了较小的实际值。
  • 当数据量大、分布均匀时,两者结果趋于一致。

示例四:结合 HAVING 筛选,找出薪资差距较大的部门

复制代码
-- 找出 P90 与 P10 差值超过 15000 的部门(薪资悬殊的部门)
SELECT
    Department                                                             AS 部门,
    APPROX_PERCENTILE_CONT(0.10) WITHIN GROUP (ORDER BY Salary)          AS P10,
    APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY Salary)          AS P90,
    APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY Salary)
        - APPROX_PERCENTILE_CONT(0.10) WITHIN GROUP (ORDER BY Salary)    AS 薪资跨度
FROM dbo.EmployeeSalary
GROUP BY Department
HAVING
    APPROX_PERCENTILE_CONT(0.90) WITHIN GROUP (ORDER BY Salary)
        - APPROX_PERCENTILE_CONT(0.10) WITHIN GROUP (ORDER BY Salary) > 15000
ORDER BY 薪资跨度 DESC;

查询结果:

部门 P10 P90 薪资跨度
技术部 8000.00 31000.00 23000.00
人事部 7000.00 17500.00 10500.00

说明: 技术部薪资跨度最大,说明内部职级差异显著。注意 HAVING 子句中可以直接使用 APPROX_PERCENTILE_CONT,这是作为聚合函数的优势。


示例五:与 PERCENTILE_CONT(精确版)的性能对比写法

复制代码
-- ===== 旧写法:精确 PERCENTILE_CONT(窗口函数,不能直接 GROUP BY)=====
-- 需要借助子查询或 CTE,写法更繁琐
SELECT DISTINCT
    Department                                                                    AS 部门,
    PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Salary) OVER (PARTITION BY Department) AS 精确中位数
FROM dbo.EmployeeSalary;

-- ===== 新写法:APPROX_PERCENTILE_CONT(聚合函数,直接 GROUP BY)=====
SELECT
    Department                                                                    AS 部门,
    APPROX_PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY Salary)                  AS 近似中位数
FROM dbo.EmployeeSalary
GROUP BY Department;

查询结果对比(两者结果相同或非常接近):

部门 精确中位数(旧) 近似中位数(新)
人事部 11500.00 11500.00
技术部 16750.00 16000.00
销售部 13500.00 13500.00

对比总结:

对比维度 PERCENTILE_CONT(精确) APPROX_PERCENTILE_CONT(近似)
函数类型 窗口函数 聚合函数
GROUP BY 不支持,需 DISTINCT+OVER 直接支持
精度 100% 精确 误差 ≤ 1.33%
性能 大数据量较慢(全排序) 快(t-Digest 算法)
适用场景 精确报表、审计 大数据近似分析、实时仪表盘
版本支持 SQL Server 2012+ SQL Server 2022+

兼容性说明

环境 支持情况
SQL Server 2022 ✅ 完全支持
SQL Server 2019 及以下 ❌ 不支持,仅有精确版 PERCENTILE_CONT/DISC
Azure SQL Database ✅ 支持(对应兼容级别 160)
Azure SQL Managed Instance ✅ 支持

注意: 使用前请确认数据库兼容级别 ≥ 160:
sql

复制

复制代码
-- 查看当前兼容级别
SELECT name, compatibility_level FROM sys.databases WHERE name = DB_NAME();

-- 如需升级(谨慎操作,建议先在测试环境验证)
ALTER DATABASE YourDatabaseName SET COMPATIBILITY_LEVEL = 160;

总结

SQL Server 2022 新增的 APPROX_PERCENTILE_CONTAPPROX_PERCENTILE_DISC 是对统计分析函数库的重要补充,核心价值体现在:

  1. 从窗口函数升级为聚合函数 :可以直接在 GROUP BY 语句中使用,写法更简洁直观,告别绕弯子的 DISTINCT + OVER 组合。

  2. t-Digest 算法加持,性能显著提升:在千万级甚至亿级数据集上,近似百分位计算的内存消耗和耗时都远低于精确版,误差控制在 1.33% 以内,完全满足多数业务分析需求。

  3. 两种模式适应不同场景

    • CONT(连续)适合薪资、金额等连续型数值,结果平滑;
    • DISC(离散)适合评分、等级等离散型数值,结果一定来自原始数据。
  4. 可结合 HAVING 使用 :作为聚合函数,可直接出现在 HAVING 子句中做条件过滤,这是精确版窗口函数做不到的。

一句话记忆: 大数据找百分位,用 APPROX_PERCENTILE_CONT/DISC------比精确版写法更简单,速度更快,误差可接受。

相关推荐
qq_372906932 小时前
HTML函数在系统字体渲染模糊是硬件问题吗_显示输出链路排查【方法】
jvm·数据库·python
Polar__Star2 小时前
如何在 PHP 包含文件中动态排除特定页面的导航项
jvm·数据库·python
2301_813599552 小时前
Go语言怎么嵌套结构体_Go语言结构体嵌套教程【深入】
jvm·数据库·python
瀚高PG实验室2 小时前
pgvector 安装及使用示例
数据库·瀚高数据库
披着羊皮不是狼2 小时前
(9)批量生成文章并同步存入 MySQL 和 Redis
数据库·redis·mysql
2401_887724502 小时前
Pandas 中使用交叉表为分类列生成计数型宽表结构
jvm·数据库·python
justjinji2 小时前
PHP函数如何识别PCI设备厂商ID_PHP获取扩展卡硬件标识【说明】
jvm·数据库·python
2201_761040592 小时前
怎么监控MongoDB副本集的复制缓冲区积压_复制流速率评估
jvm·数据库·python
2402_854808372 小时前
Layui tab选项卡如何动态根据ID值进行程序化切换
jvm·数据库·python