地址分组应优先结构化存储:写入前用api或离线库解析出province/city字段,避免sql中字符串截取;次选join行政区划表、json_extract或case when匹配,同时注意排序与空值处理。

SQL中用GROUP BY对地址字段做省份/城市分组不可靠
直接对原始地址字符串(如“广东省深圳市南山区科技园”)用GROUP BY SUBSTRING()或正则提取省份/城市,大概率出错。中国行政区划存在同名(如“中山市”和“中山路”)、嵌套(如“内蒙古自治区阿拉善盟”)、简称混用(“沪”“申”“上海”)等问题,纯文本切分无法覆盖边界情况。
实操建议:
- 地址解析必须前置:在写入数据库前,调用高德/百度API或离线行政区划库(如chinese_province_city_area_mapper)将原始地址转为标准
province、city字段,存为独立列 - 避免在SQL里实时调用HTTP API——多数数据库不支持,且会拖慢查询、触发限流
- 若只有原始地址字段且无法补数据,优先用
CASE WHEN匹配已知高频省市名(仅限简单报表场景),例如:SELECT CASE WHEN address LIKE '%北京市%' THEN '北京市' WHEN address LIKE '%广东省%' THEN '广东省' ELSE '其他' END AS province, COUNT(*) FROM user_table GROUP BY province
PostgreSQL中用JOIN关联行政区划表实现精准分组
当已有标准行政区划表(如regions含id、name、level、parent_id)且业务表存了区域ID(如region_id),分组最稳。
实操建议:
- 确保
regions表有合理索引:CREATE INDEX ON regions(id, level); - 向上递归查省份/城市需用
WITH RECURSIVE,例如查某区所属省:WITH RECURSIVE region_path AS ( SELECT id, name, level, parent_id FROM regions WHERE id = 123 UNION ALL SELECT r.id, r.name, r.level, r.parent_id FROM regions r INNER JOIN region_path rp ON r.id = rp.parent_id ) SELECT name FROM region_path WHERE level = 1;
- 实际分组时,把递归逻辑封装成视图或CTE,再
JOIN业务表,避免重复计算
MySQL 8.0+用JSON_EXTRACT处理嵌套地理JSON字段
如果地理信息以JSON格式存储(如{"province":"广东","city":"深圳","district":"南山"}),直接提取比字符串解析安全得多。
实操建议:
- 确认字段类型是
JSON而非TEXT,否则JSON_EXTRACT返回NULL - 提取时用
->>"$.province"(带双引号)获取去引号字符串,比->"$.province"更适合作GROUP BY - 注意JSON键名大小写敏感,且中文键需确保编码一致(推荐UTF8MB4)
- 示例:
SELECT JSON_UNQUOTE(JSON_EXTRACT(geo_json, '$.province')) AS province, JSON_UNQUOTE(JSON_EXTRACT(geo_json, '$.city')) AS city, COUNT(*) FROM orders GROUP BY province, city;
分组后排序和空值处理常被忽略
按省份分组后,ORDER BY province默认按字母序(“安徽省”排在“北京市”前),但业务常要求按行政层级或自定义顺序(如“直辖市优先”)。另外,NULL或空字符串的地理字段会导致分组结果遗漏。
实操建议:
- 用
CASE WHEN定制排序:ORDER BY CASE province WHEN '北京市' THEN 1 WHEN '上海市' THEN 2 WHEN '广东省' THEN 3 ELSE 99 END - 显式过滤空值:
WHERE province IS NOT NULL AND province != '',别依赖HAVING——它在分组后才生效,空值已参与分组计数 - 若需展示“未知地区”,用
COALESCE(province, '未识别')代替裸字段











