资讯动态

省市区三级联动表格:从Excel到数据库的完整落地指南

发布时间:2026/10/27 14:18:27 来源:尧图企业网站定制
简介这份资源面向需要处理中国行政区划数据的开发者与数据分析人员提供省、市、区三级联动的Excel表格方案可用于数据管理、统计报表以及Web下拉选择框的级联过滤帮助保证录入数据的准确与一致。压缩包共21个文件约2.1MB以dll类库、pdb调试符号、exe测试程序为主另含xls数据表与xml配置说明其中NPOI等库用于在程序中读写Excel测试程序可演示联动效果。目前已有3937人学习下载说明其在同类需求中具备一定参考价值。读者可获得覆盖全国的省市区数据源表格、可运行的联动演示程序以及配套的.NET处理组件既能直接套用表格模板也能参考其实现思路将三级联动逻辑迁移到自己的业务系统或数据采集流程中减少自行整理行政区划层级关系的工作量。1. 省市区三级联动表格一份能直接塞进项目的行政区划底表做后台表单的人大概都遇到过这种需求用户注册要选籍贯收货地址要填省市区门店录入要挂行政区划。前端组件库给的级联选择器看着挺美真接上数据才发现——省市区数据从哪来网上搜到的要么是几年前的旧版本要么缺了街道层级要么格式乱七八糟字段名一会儿name一会儿label导入数据库还得自己写清洗脚本。这份「最全省市区三级联动 excel 表格」解决的就是这个最脏最累的活把全国省、市、区三级行政区划整理成结构化表格拿到手就能转 JSON、灌数据库、喂给前端级联组件。适合谁做管理后台的全栈、写表单校验的后端、以及被产品经理一句「加个地址选择」逼疯的前端。下面按「表格长什么样 → 怎么转成代码能用的数据 → 怎么接进表单 → 坑在哪」的顺序拆。2. 先看清表格结构三级联动底表的字段设计与编码逻辑拿到一份行政区划表格第一件事不是急着写代码而是把表头和行结构看明白。三级联动的本质是一棵三层的树Excel 是扁平的所以表格必须用某种方式把「父子关系」编码进去。常见做法有两种一种是每行一个区县省、市两列重复填充靠重复值表达归属另一种是每行一个节点用parent_id指向上级。这份表格走的是前一种也就是最贴近人阅读习惯的宽表结构转代码时再自己拍平成树。2.1 表头字段与行政区划代码的含义一份合格的省市区表格核心字段通常长这样字段名示例说明省某省一级行政区名称市某市二级行政区名称直辖市此列与省同名或留空区/县某区三级行政区名称省代码0000006 位行政区划代码前两位有效市代码0000006 位代码前四位有效区代码000000完整 6 位代码层级3标识当前行属于第几级行政区划代码是这套数据的骨架不是随便编的序号。国家标准里6 位代码的前两位代表省级中间两位代表市级后两位代表区县级所以「省代码 市代码 区代码」天然构成前缀关系。理解这一点非常关键后面做校验、做去重、做增量更新全靠这个前缀逻辑。很多人拿到表格直接按名称匹配结果遇到「某市」下有两个同名「城关区」就翻车了血泪经验就是——名称只用来展示代码才是唯一键。表格里还有一个容易被忽略的细节直辖市的处理。直辖市在行政层级上直接管区县没有地级市这一层但为了保持三级结构统一表格通常会把「市」这一列填成与省同名或者填一个占位值。你在转数据时如果不处理这个前端级联就会出现「某市 → 某市 → 某区」这种看着很蠢的重复层级。常见做法是在转换脚本里判断若省代码等于市代码则跳过市级节点直接把区县挂到省下面。2.2 宽表转树形结构parent_id 的生成规则宽表适合人看不适合程序用。前端级联组件、后端树形查询要的都是带id、parent_id、label、value的节点数组。转换的核心逻辑就一句话按代码前缀建立父子关系。省节点的parent_id为 0市节点的parent_id是它所属省的代码区县节点的parent_id是它所属市的代码。这里有个实操细节Excel 里的代码列经常被存成数字前导零会丢。比如某省代码010000存进 Excel 变成10000读出来再拼前缀就全乱了。所以读表时第一件事是把代码列强制按字符串处理并左补零到 6 位。这个坑几乎每个用 Excel 底表的人都踩过后面避坑章节还会展开。2.3 用 Python 把 Excel 读成可用的 JSON 树下面这段脚本是我一般会先跑一遍的「体检 转换」流程用pandas读表用代码前缀建树输出前端能直接吃的 JSON。字段名按你实际表格调整逻辑不变。import pandas as pd import json # 读表时把所有列按字符串读避免代码前导零丢失 df pd.read_excel(region.xlsx, dtypestr) # 统一去空格防止「某省 」和「某省」被当成两个节点 for col in [省, 市, 区/县, 省代码, 市代码, 区代码]: df[col] df[col].fillna().str.strip() # 代码左补零到 6 位这是后续前缀匹配的前提 def pad6(code): return code.zfill(6) if code else df[省代码] df[省代码].map(pad6) df[市代码] df[市代码].map(pad6) df[区代码] df[区代码].map(pad6) nodes {} # code - node用字典去重天然处理重复行 for _, row in df.iterrows(): prov_code, city_code, dist_code row[省代码], row[市代码], row[区代码] # 省级节点 if prov_code and prov_code not in nodes: nodes[prov_code] { value: prov_code, label: row[省], parent_id: 0, children: [] } # 市级节点直辖市省代码等于市代码时跳过避免重复层级 if city_code and city_code ! prov_code and city_code not in nodes: nodes[city_code] { value: city_code, label: row[市], parent_id: prov_code, children: [] } # 区县节点 if dist_code and dist_code not in nodes: parent city_code if city_code ! prov_code else prov_code nodes[dist_code] { value: dist_code, label: row[区/县], parent_id: parent, children: [] } # 第二遍挂父子关系 roots [] for code, node in nodes.items(): pid node.pop(parent_id) if pid 0 or pid not in nodes: roots.append(node) else: nodes[pid][children].append(node) with open(region_tree.json, w, encodingutf-8) as f: json.dump(roots, f, ensure_asciiFalse, indent2)这段代码有三个关键点值得说清楚。第一dtypestr必须在读表时就指定不能读完再转因为 pandas 默认会把纯数字列推断成 int前导零在读取阶段就已经没了后面补零也补不回来。第二用字典nodes按代码去重比先建列表再set去重更稳因为同一代码可能在不同行重复出现字典天然覆盖。第三父子挂载放在第二遍循环避免「子节点先于父节点出现」时找不到 parent 的问题——这是树形转换里最常见的顺序陷阱一次遍历搞定的写法看着简洁实际遇到乱序数据就崩。跑完之后你会得到一个嵌套 JSON省节点下挂市市节点下挂区县直辖市的区县直接挂在省下。前端el-cascader、antd Cascader这类组件基本都能直接消费这个结构字段名对不上就加一层map重命名不用改逻辑。3. 接进表单与数据库级联组件的字段映射和存储方案数据转成树只是第一步真正落地要解决两个问题前端怎么把它接进级联选择器后端拿到用户选的三级代码后怎么存、怎么查。这两件事看着简单实际各有各的讲究尤其是存储方案选错后面做统计和筛选会非常难受。3.1 前端级联组件的字段映射与懒加载取舍主流组件库的级联选择器对数据结构的要求大同小异一个options数组每个节点有value、label、children。上面生成的 JSON 字段名刚好对得上直接import或fetch进来即可。但有一个取舍必须提前想清楚全量加载还是懒加载。全量加载就是把整棵树一次性塞给组件。全国省市区三级节点加起来数量不小JSON 文件体积通常在几百 KB 到 1MB 量级gzip 之后能压到一百多 KB。对管理后台这种内网、低频场景全量加载完全够用实现最简单用户点开就是秒出。懒加载则是先只给省级用户选了省再请求市选了市再请求区县适合移动端或对首屏体积敏感的场景代价是要多写三个接口和缓存逻辑。我的建议是后台系统一律全量别为了省那一百多 KB 给自己找麻烦。真要做懒加载缓存键用父级代码别用名称否则遇到重名区县又会翻车。字段映射上如果你的组件要text而不是label写个递归函数统一改名就行// 把后端返回的 label/children 结构转成组件需要的 text/children function toCascaderOptions(nodes) { return nodes.map(n ({ value: n.value, text: n.label, children: n.children n.children.length ? toCascaderOptions(n.children) : undefined // 叶子节点不要给空数组部分组件会渲染出空箭头 })); }注意叶子节点children要给undefined而不是[]这是踩过的坑某些组件版本看到空数组会认为「还有下一级但没加载」渲染出一个点不开的箭头用户以为卡了。3.2 数据库存储存三级代码还是存拼接字符串后端存储方案常见三种各有适用边界方案存储形式优点缺点存三个字段province_code / city_code / district_code查询灵活可按任意级筛选字段多写入要校验一致性存拼接串000000,000000,000000单字段简单按省统计要like前缀索引难用存末级代码只存 district_code最省空间查省市级要反查前缀多一次关联我一般选第一种三个字段分开存。原因很实际业务上经常要按省维度做统计报表where province_code ?走索引干净利落存拼接串的话like 01%虽然也能用但前缀匹配的索引效率和数据可读性都差一截。存末级代码看着优雅实际每次查省名都要回表关联行政区划表报表一多就难受。写入时的一致性校验别省三个代码必须满足前缀关系即city_code前两位等于province_code前两位district_code前四位等于city_code前四位。这个校验放在后端接口层能挡掉前端传错、脏数据导入等一堆问题。建表时给三个字段各建普通索引联合索引按实际查询组合再定别一上来就堆索引。3.3 回显与反查根据代码还原「省市区」文本用户编辑地址时后端只存了代码前端要回显成「某省 / 某市 / 某区」的级联选中态。做法是拿三个代码去树里逐级查找或者干脆维护一份code - label的扁平映射表。后者更快转换脚本里顺手就能生成# 在生成树的同时输出扁平映射供回显和反查使用 flat {code: node[label] for code, node in nodes.items()} with open(region_flat.json, w, encodingutf-8) as f: json.dump(flat, f, ensure_asciiFalse)回显时flat[province_code] flat[city_code] flat[district_code]拼出来即可O(1) 查找比递归遍历树快得多。这个扁平表还有个妙用做数据清洗时拿它反查「某个名称对应哪些代码」能快速发现重名区县避免按名称匹配的误伤。4. 避坑与排查行政区划表格落地时最容易翻车的五件事这份表格本身质量再好落地过程中该踩的坑一个不会少。下面五条是我和同事真实翻过的车按「现象 → 原因 → 解决」写清楚照着排查能省不少时间。4.1 代码前导零丢失导致前缀匹配全乱现象转换出来的树层级错乱某些市挂到了错误的省下面或者干脆变成根节点。原因Excel 里代码列被存成数值型010000读出来是10000补零逻辑没做或者做晚了前缀比对自然对不上。解决读表时dtypestr强制字符串读完后统一zfill(6)并且在转换脚本开头加一句断言检查所有代码长度是否为 6不为 6 直接报错中断别让脏数据流到下游。4.2 直辖市出现「省→市→区」重复层级现象前端级联点开某直辖市出现「某市 → 某市 → 某区」用户一脸问号。原因表格为保持三级结构把直辖市的市级列填成了与省同名转换脚本没做特殊判断。解决建树时判断city_code prov_code则跳过市级节点区县直接挂省如果表格里市级列是空的则用province_code兜底作为区县的 parent。两种表格形态都要兼容写转换脚本时先print几行直辖市数据看看实际长什么样。4.3 同名区县按名称匹配导致数据串台现象按名称做关联或去重时两个不同市的同名区县被合并成一个统计数据对不上。原因行政区划里同名区县不少名称不是唯一键。解决所有关联、去重、缓存键一律用 6 位代码名称只用于展示。如果历史数据里只有名称没有代码先做一次「名称 → 代码」的映射清洗映射不上的单独列出来人工确认别硬匹配。4.4 表格版本过旧区划调整后数据对不上现象用户反馈某个新设的区在级联里选不到或者某个已撤销的区还在列表里。原因行政区划会调整底表有版本时效性拿几年前的表格用就会这样。解决把行政区划数据当成需要定期更新的配置而不是一次性导入的死数据。转换脚本里保留原始 Excel每次更新只替换文件重跑脚本数据库用「代码存在则更新、不存在则插入」的 upsert 逻辑别整表删了重灌否则历史订单关联的旧代码会变成孤儿数据。4.5 叶子节点空 children 渲染出假箭头现象级联选到区县这一级旁边还有个箭头点开是空的。原因转换时给叶子节点赋了children: []部分组件把空数组当成「有下级但未加载」。解决叶子节点不输出children字段或者显式设为undefined如果组件要求必须有该字段就设成null并在组件配置里关掉「懒加载」开关。这个坑排查起来很费眼因为数据结构看着没问题问题出在组件对空数组的语义判断上。5. 增量维护与校验让这份底表长期可用的两个习惯表格拿到手能用只是起点行政区划数据是会变的真正省心的是建立一套「更新即重跑」的维护习惯。我现在的做法是原始 Excel 只读不改所有加工逻辑沉淀在一个转换脚本里脚本输出三份产物——树形 JSON 给前端、扁平映射给回显、SQL 插入语句给数据库初始化。每次官方区划调整替换 Excel、重跑脚本、执行 upsert全程十分钟以内。校验环节有两个必做的检查建议写进脚本末尾自动跑。第一个是完整性检查统计省级节点数量是否在合理区间、每个省下是否至少有一个市或区县、有没有parent_id指向不存在代码的孤儿节点。第二个是前缀一致性检查遍历所有区县节点断言其代码前四位能在市级节点里找到找不到就打印出来人工看。这两条检查能挡住绝大多数脏数据比事后用户投诉再回头查强太多。# 转换脚本末尾的自动体检发现问题直接抛异常 prov_count len([n for n in roots]) assert 30 prov_count 40, f省级节点数量异常: {prov_count} orphans [c for c, n in nodes.items() if n.get(parent_id) and n[parent_id] not in nodes and n[parent_id] ! 0] assert not orphans, f存在孤儿节点: {orphans[:5]} for code, node in nodes.items(): if len(code) 6 and code[2:] ! 0000: # 区县级 assert code[:4] 00 in nodes or code[:2] 0000 in nodes, \ f区县 {code} 找不到上级这段体检代码的价值在于把「隐性错误」变成「显性报错」。以前我图省事跳过校验结果一次区划调整后某个市的代码变了区县全成了孤儿前端级联点开是空的排查了半天才定位到是底表更新时漏了一行。从那以后我每次替换 Excel 都强制走一遍这套断言宁可脚本报错中断也不让半成品数据进库。希望这份表格和这套流程能帮你把「加个地址选择」这种需求从半天缩到半小时。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑