如何编写SQL以查询各省份销售额前十名的城市汇总数据?

作者:袖梨 2026-07-01
必须用窗口函数 ROW_NUMBER() 按省份分区排序,才能实现各省独立取前10;错误做法是 GROUP BY 后直接 ORDER BY + LIMIT,结果为全国 Top10。

用窗口函数 ROW_NUMBER() 排名,避免 GROUP BY 后丢失城市粒度

直接对省份 + 城市分组求和再排序,容易误用 ORDER BY 仅作用于最终结果集——这没法保证“每个省内独立取前10”。必须用窗口函数在分组聚合后、按省份分区排序。
常见错误是写成 SELECT province, city, SUM(sales) FROM t GROUP BY province, city ORDER BY SUM(sales) DESC LIMIT 10,这返回的是全国销售额 Top10,不是各省 Top10。

正确做法分两步:先聚合(GROUP BY province, city),再用 ROW_NUMBER() OVER (PARTITION BY province ORDER BY SUM(sales) DESC) 标记名次:

SELECT province, city, sales_sumFROM (  SELECT     province,    city,    SUM(sales) AS sales_sum,    ROW_NUMBER() OVER (PARTITION BY province ORDER BY SUM(sales) DESC) AS rn  FROM orders  GROUP BY province, city) rankedWHERE rn <= 10;

注意 ROW_NUMBER()RANK() 的并列处理差异

如果某省第10名有多个城市销售额相同,ROW_NUMBER() 会强制给唯一序号(比如 9、10、11),导致实际返回 11 行;而 RANK() 会并列(比如 9、10、10、12),此时 WHERE rn 可能漏掉并列的第10名。

  • 要严格限制最多10行/省 → 用 ROW_NUMBER()
  • 要保留所有并列第10名 → 改用 RANK(),但 WHERE 条件得改成 r (别漏改别名)
  • MySQL 8.0+、PostgreSQL、SQL Server 2017+、Oracle 都支持;SQLite 3.35+ 也支持,旧版不支持窗口函数

聚合前过滤脏数据,否则 SUM(sales) 结果失真

真实订单表常含 sales 为 NULL 或负值(退货)、province/city 为空或“未知”、“其他”等非标准值。这些不剔除,会导致:

  • SUM() 忽略 NULL,但若整组都是 NULL,该城市不出现 → 看似“没数据”,实为脏数据掩盖
  • 负销售额拉低排名,把本该进 Top10 的城市挤出去
  • “华东”“华北”等大区名混在 province 字段里,和真实省份同名分区 → 分区错乱

建议在子查询中加清洗条件:

WHERE sales > 0   AND province IS NOT NULL   AND city NOT IN ('未知', '其他', '')   AND province IN ('广东', '江苏', '浙江', ...)

大数据量时加复合索引提升性能

orders 表超千万行,上述查询可能慢在两处:按 province/city 分组扫描、以及窗口函数排序。单列索引效果有限。

推荐创建覆盖索引:

CREATE INDEX idx_province_city_sales ON orders (province, city, sales);

这个顺序很重要:province 在前支持分区裁剪,city 第二支持组内去重,sales 最后让 SUM()ORDER BY 尽量走索引有序扫描,避免额外排序。

如果业务中经常查“某省 Top N”,还可考虑物化省份汇总表,但实时性要求高时,索引 + 窗口函数仍是更轻量的选择。

相关文章

精彩推荐