问题场景
做数据分析时,你是否经常需要快速了解一个字段的数据分布?比如:用户表里有哪些国家?订单表里有哪些支付方式?商品表里各有多少个 SKU?
传统做法是写 GROUP BY + COUNT(*) + ORDER BY,数据量大时又慢又耗内存。
假设你有一个 5000 万行的电商订单表 orders,包含 user_id、country、payment_method、amount 等字段。老板让你快速看看:
- 用户来自哪些国家?每个国家大概多少人?
- 支付方式有哪几种?各自占比如何?
- 总共有多少个独立用户?
如果用传统的 GROUP BY:
-- 传统方法:慢、耗内存
SELECT country, COUNT(*) as cnt
FROM orders
GROUP BY country
ORDER BY cnt DESC;
-- 耗时:~45 秒
-- 内存峰值:~2GB
解决方案
技巧一:APPROX_TOP_K — 一行出 Top-K
SELECT
APPROX_TOP_K(country, 10) as top_countries,
APPROX_TOP_K(payment_method, 5) as top_payments
FROM orders;
返回结果:
| top_countries | top_payments |
|---|---|
{("US", 12500000), ("CN", 9800000), ("JP", 5200000), ...} | {("credit_card", 18000000), ("alipay", 12000000), ...} |
APPROX_TOP_K(column, K) 使用 HyperLogLog 变体算法,在亚秒级返回出现频率最高的 K 个值及其计数。无需显式 GROUP BY,无需手动排序。
技巧二:APPROX_COUNT_DISTINCT — 精确到 95% 的基数估计
SELECT
APPROX_COUNT_DISTINCT(user_id) as unique_users,
COUNT(DISTINCT user_id) as exact_users
FROM orders;
对比结果:
| unique_users | exact_users |
|---|---|
| 15234567 | 15234891 |
误差仅 0.002%,但速度提升 10 倍以上。
技巧三:组合使用 — 一键数据画像
SELECT
APPROX_TOP_K(country, 5) as top_countries,
APPROX_TOP_K(payment_method, 5) as top_payments,
APPROX_COUNT_DISTINCT(user_id) as unique_users,
APPROX_COUNT_DISTINCT(product_id) as unique_products
FROM orders;
一条 SQL,1.2 秒出结果。
效果量化
| 指标 | 传统 GROUP BY | APPROX_TOP_K | 提升 |
|---|---|---|---|
| 查询时间 | ~45 秒 | ~1.2 秒 | 37 倍 |
| 内存占用 | ~2 GB | ~50 MB | 40 倍 |
| 代码行数 | 5 行 | 1 行 | 简化 80% |
延伸思考
APPROX_TOP_K 和 APPROX_COUNT_DISTINCT 基于 HyperLogLog 和 Count-Min Sketch 等近似算法,在可接受的误差范围内(通常 < 2%),将原本需要全量扫描和排序的操作优化为流式处理。
适用场景:
- 数据探索阶段:快速了解字段分布
- 仪表盘预计算:实时监控面板的 Top-N
- 异常检测:识别罕见类别(通过对比近似值和精确值)
⚠️ 注意:如果需要精确计数(如财务对账),仍应使用 COUNT(DISTINCT ...)。近似函数适用于探索性分析和对精度要求不高的场景。
Subscribe to DuckDB Lab