ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

四级地址表设计实战:联动ID、层级与是否末级字段详解

四级地址表设计实战:联动ID、层级与是否末级字段详解 简介中国省、市、县、街道乡镇四级的完整行政区划表覆盖国内常见行政层级包含名称、联动ID、层级、是否末级等关键字段适合需要实现城市联动选择、地址管理或区域数据统计的开发者、测试人员及运维工程师。压缩包共6个文件其中xlsx表格便于直观查阅与二次加工sql脚本可快速导入数据库txt说明辅助理解字段含义3张png截图用于预览数据样例整体约2.48MB轻量易用。目前已有603人学习下载。数据以父子ID串联四级体系联动ID采用自增拼接规则例如11-1101-110107-110107001可直接支撑前端省市区级联组件是否末级字段显著提升末端街道乡镇筛选效率。无论是初始化业务库、开发收货地址选择器还是清洗统计地理数据这份表都能节省大量人工整理成本适合作为行政区划基准数据长期复用。 做这类四级地址数据的人多半都被“地址”这个东西折磨过。上次我给一个连锁门店系统补基础数据客户丢过来一份所谓的“中国省市县街道乡镇四级地址表”里面一个联动ID都没有层级字段直接用汉字“省、市、区、街道”写在描述里最要命的是“是否末级”这一列全是1前端拿到之后完全不知道该往哪儿继续点。后来我自己清洗、生成、落库把县和街道这层补全重新导出成 xlsx 和 sql 两份文件才彻底想明白这套数据如果结构设计对了能让项目省掉一大半沟通成本。这份项目标题里写得很清楚——四级城市地区表xlsx、sql 文件国内地址字段是名称、联动ID、层级、是否末级。看起来简单但它背后其实涉及一套标准的树形数据设计省、市、县、街道乡镇四级靠联动ID串父子关系靠层级告诉系统我在第几层靠是否末级判断这节点还有没有下一级。这篇文章我就把这套表怎么设计、怎么处理、怎么保证不出乱子的逻辑完整讲一遍顺便把我踩过的坑都标出来。1. 一套四级地址表到底要解决什么问题1.1 四个字段其实对应一棵完整的地址树先看字段设计。一个合格的四级地址表最低限度长这样字段含义说明name地址名称省份名、城市名、区县名、街道/乡镇名pid / 联动ID父级ID指向上一级的ID省一级为0或空level层级1省、2市、3区县、4街道乡镇严格数字is_leaf是否末级1表示没有下级0表示下面还有一层我第一次接触这种表的时候也怀疑过到底为什么要把字段拆得这么细。因为实际项目里前端要做“省—市—区县—街道乡镇”四级联动后端做物流运费模板或者BI系统做区域维度统计本质上都是在跟一棵树打交道。联动ID存的就是父子关系level告诉代码当前节点在哪一层is_leaf则是给前端的一个“你是不是还要继续请求接口”的信号。这三个字段配合起来就能从任意一个节点出发向上查到所有祖宗节点向下查到所有子孙节点。比如用户选了“浙江省杭州市西湖区”你拿着这条记录的联动ID就能反查“浙江省”“杭州市”不用再造一张冗余表。1.2 为什么缺“是否末级”会坑到前端我第一次做地址联动时拿到一张只有 name 和 pid 的表没有 is_leaf 字段。前端同事问了一个我半天答不上来的问题点到一个县级节点时我怎么知道下面还有没有街道当时只能让后端临时查一下“这个ID有没有下级”结果全国四万多个县级、乡级节点每个节点都要跑一次 count 查询慢得没法看。is_leaf 就是干这个的。它在数据准备阶段就把“有没有下级”这个状态写死在表里前端联动时看一眼字段就知道是继续展开还是把当前节点当叶子结果根本不需要额外请求。更重要的是它保证了地址树的闭合性。有些偏远地区确实存在“区县下面直接就是村”没有街道层那这条区县记录就必须标记成是末级否则业务系统会永远以为下面还有一层。所以这四个字段不是随便扔出来的它们是一个数据模型里最能抵抗复杂业务变化的最小集合。如果这份表是你要拿去做电商下单、ERP门店、物流区域配置的我建议原样保留千万别随手删字段。2. xlsx 和 sql一鱼两吃设计时各有讲究2.1 xlsx 别光顾着好看数据格式才是命门很多人拿到地址表第一反应是给它加表格样式、加筛选、加颜色但真正的坑不在颜值在数据类型。最典型的两个问题一是行政区划代码丢了前导0二是xlsx里出现删不掉的小单引号。前者是因为你用Excel打开一份csv或sql导出的文件Excel会自动把类似“110101”这种数字文本当成数值然后默默把前面的“0”吞掉。实际上很多地址表里虽然主键是自增数字但还是会附一个行政区划编码列这种字段一旦被Excel改了格式后面做数据关联就彻底对不上。我的建议是xlsx里的ID、父级ID、区划代码这类列全部设置成文本格式。如果你手头已经是csv导入Excel时别双击打开用“数据—从文本/CSV”导入并在导入向导里把对应列指定为文本。至于那个删不掉的前导单引号多半是文件本身从某个导出工具里带的SQL字符串写法Excel会把它当普通字符直接显示在单元格里。处理方式很简单先在文本编辑器里批量替换掉那串开头的单引号再导入Excel。如果数量少也可以把那一列设置成文本后分列手工清除。2.2 SQL 文件要能直接执行建表和索引一次到位SQL文件的价值是给数据库用的所以不能只给一堆INSERT语句了事必须把建表语句、索引、插入数据、甚至后续查询都会涉及到的字段约束一起写清楚。我一般建议用单表结构存全部四级地址这样最简单CREATE TABLE region ( id BIGINT PRIMARY KEY, pid BIGINT NOT NULL DEFAULT 0, name VARCHAR(50) NOT NULL, level TINYINT NOT NULL DEFAULT 1, is_leaf TINYINT NOT NULL DEFAULT 1, KEY idx_pid (pid), KEY idx_level (level) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;索引就两个pid 和 level。pid 用来做父子查询level 用来过滤某一级下的节点。如果只建主键不建索引全国五万多个乡镇节点随便一次联动查询都会全表扫描页面卡到怀疑人生。插入数据时保持“父ID”和“子ID”的顺序一致先省、后市、再县、最后乡镇。虽然INSERT时不用严格保证但如果后续做批量导入、断点续传顺序一致能救命。sql文件里最好也带上事务比如 MySQL 下用START TRANSACTION; ... COMMIT;避免导一半失败留下残缺数据。2.3 联动ID和层级怎么配合才有最大兼容性关于联动ID最怕的是有人把“行政区划代码”直接当成父级ID来用。国家码确实有规律比如前两位是省前四位是市但到了街道乡镇这级代码里经常有调整、合并、废弃的情况直接拿国家码做联动关系一旦遇到数据更新就特别难受。更稳的做法是导入时统一生成一套自增ID。比如省级ID从110000_1开始市级ID用父级ID乘以1000再加序号这样保证了全局唯一而且从ID本身就能猜出父子关系。就算以后某条行政区划调整了也只改name和is_leaf不用动整棵树的父子结构。层级字段也别搞什么“a/b/c”这种乱七八糟的值统一用数字1、2、3、4。后续不管用什么语言查数据都能一层一层拼出来不会出现大小写、全半角不匹配的问题。说真的数据表是做给机器读的任何带歧义的值都是给自己埋雷。3. 从原始素材到干净四级表一条能落地的处理链路3.1 先理解原始地址数据的两种常见形态我做过的地址数据处理原始素材大概分成两类。第一类是市场上流传很广的“省市区街道乡镇合并表”每一行只有两个字段一个是“浙江省杭州市西湖区留下街道”这种整串地址另一个是某种编号。这种表表面上看信息挺全但真要拆开用得先做地址解析把省会、市会、区县、街道拆出来非常费劲。第二类是比较标准的“多级表”省、市、县、街道各占一列但经常出现“某地区某街道名字重复”或者同一街道在市区合并后有新旧两条记录需要根据最新行政区划口径去重。这时候就得靠联动ID继续向上关联避免张冠李戴。无论拿到哪一类我的铁律都是先备份原文件再在副本上处理。因为很多原始数据是你花钱买来、或者是同事辛苦爬下来的一旦操作失误把源文件改了后悔都来不及。3.2 拆分、去重、补编码三步走假设你手头是一份“省市区街道”合并在一个字段里的表处理链路大致如下确定省如果这个字段以“省”“自治区”“市”结尾用规则或关键词字典识别出省级单位。切市级和区县级按“市”“区”“县”“旗”等关键字切分同时注意“自治州”“地区”等特殊情况。剩余部分作为街道/乡镇如果不能识别任何下级就先把这行标为末级。去重尽量用行政区划代码去重如果数据里没有代码就只能按“名称父级ID”分组。去重SQL可以这么写SELECT name, pid, level, COUNT(*) FROM region GROUP BY name, pid, level HAVING COUNT(*) 1;查出重复项以后再人工判断到底留哪一条。我自己做的时候优先保留能关联上国家行政区划代码的那一条代码对不上的一律删。宁可少几条也不能让联动ID出现“一个父节点底下有两个同名子节点且ID不同”的状况。3.3 同源导出 xlsx 和 sql避免手改联动关系这一步是最容易被人忽略的。很多项目会先让我改Excel改完了再让人导sql结果两边数据经常对不上。正确的做法是用一份标准源数据文件脚本同时生成xlsx和sql。我习惯用Python的pandas做中转把清洗好的数据读进来然后分别写两个导出函数# 核心逻辑从清洗后的DataFrame导出两种格式 df.to_excel(region.xlsx, indexFalse) with open(region.sql, w, encodingutf-8) as f: f.write(SET NAMES utf8mb4;\n) f.write(START TRANSACTION;\n) for _, row in df.iterrows(): sql fINSERT INTO region (id, pid, name, level, is_leaf) VALUES ({row[id]}, {row[pid]}, {row[name]}, {row[level]}, {row[is_leaf]});\n f.write(sql) f.write(COMMIT;\n)同源生成的另一个好处是你可以把清洗逻辑固化在脚本里行政区划一调整跑一遍脚本就重新生成两份文件不用再手工维护双份数据。这也是我后来做地址数据项目必走的一条路。4. 把四级地址表接进业务系统的几个硬经验4.1 五万条数据不卡但查询和缓存得设计好全国街道乡镇级别加起来大致在四万到五万条左右量级不大但如果每次打开页面都把整张表查出来塞给前端一样会卡。因为树形控件要把这五万条数据组装成一棵层级对象浏览器性能撑不住。比较稳妥的方案是“按级加载”初始化时只查询level1的省份列表用户点到某个省级节点时再用pid该省份ID去查下一层。这样每次返回的数据只有几十条接口响应稳定在几十毫秒。is_leaf字段在这里就能发挥作用如果节点已经是末级前端压根不会发起下一级请求。如果后台业务需要一次性拿到完整树比如做报表维度、导出全地址编码那也不建议直接把五万条都返回给页面而是提前在服务端把树组装好序列化成JSON丢给前端前端只负责渲染。4.2 用递归查询还是用逐级查询得看场景MySQL 8.0以上支持递归CTE所以所有下级节点其实可以一条SQL查出来WITH RECURSIVE region_tree AS ( SELECT id, pid, name, level, is_leaf FROM region WHERE id 330100 UNION ALL SELECT r.id, r.pid, r.name, r.level, r.is_leaf FROM region r INNER JOIN region_tree t ON r.pid t.id ) SELECT * FROM region_tree;但实际项目里我不太建议在公共服务里频繁用递归CTE因为每一次递归都是变长查询数据库优化器未必能走索引。更常见的做法是直接用WHERE pid ?查一级然后前端用联动事件一层层加载。只有在你确实需要“一次性把某城市底下所有街道”取出来的时候递归CTE才真正有价值。4.3 联动ID断链和重复父子关系是最隐蔽的雷我自己排查过很多回业务方反馈“选了市底下区县不显示”最后查出来都是数据问题。最典型的是联动ID断链某条县级数据的pid写成了一个不存在的市级ID导致JOIN的时候查不到父级整个分支静默失败。所以sql交付之前我建议先跑一遍数据质量检查把所有还存在“孤儿节点”的行揪出来SELECT * FROM region r LEFT JOIN region p ON r.pid p.id WHERE r.pid 0 AND p.id IS NULL;这个查询我几乎每次交数据都会跑一次。另外还要检查重复父子关系比如同一父节点底下不允许出现两条完全同名的子节点。如果一个文件里允许出现“西湖区”下有两个“留下街道”后端做下拉选择时就会产生重复选项用户一旦选错订单地址就全错了。5. 常见问题与排查速查问题原因解决办法xlsx 里单元格前面多了一个删不掉的单引号数据源是SQL导出字符串带了引号前缀用文本编辑器批量替换掉开头的单引号再导入ExcelID列前面的0消失了Excel把文本当数值处理导入时设置为文本格式或用SQL文件导入数据库SQL文件导入出现乱码文件编码和数据库字符集不一致统一保存为UTF-8SQL文件开头加SET NAMES utf8mb4点开某市看不到下级区县父子联动ID断链或子节点level标记错误运行上面的孤儿节点检查SQL同一个父级下出现重复同名子节点源数据有重复记录GROUP BY去重人工确认保留最新区划明明街道下面还有社区is_leaf却标了1数据版本过旧或录入错误重新核对最新行政区划口径更新is_leafDBeaver执行SQL文件报语法错误文件里带了Excel特殊字符或注释不兼容用纯文本编辑器打开检查去掉不可见字符这些坑我一个一个都踩过。尤其那句“xlsx数据前面有个删不掉”听起来像Excel问题实际上大多数时候是文本文件本身带了单引号只不过你打开方式不对。遇到这种事先别在Excel里折腾回到源文件用Notepad这类工具看一眼再决定怎么处理。我做这份四级地址表最大的感受是文件本身不复杂真正难的是把联动ID、层级、是否末级这三个状态维护得严谨。只要这三个字段保持干净不止前端联动省事后续做物流计价、区域库存、门店覆盖范围都能直接从同一棵地址树上扩展。最后再分享一个小技巧拿到任何地址数据我都会先抽查三个节点一个是省级根节点一个是普通市辖区一个是偏远的县级市。只要这三个位置的父子关系都能正确反查数据整体可信度就很高。剩下的时间好好把去重和断链检查跑一遍比什么都强。本文还有配套的精品资源点击获取
返回列表