五级行政区划SQL脚本:省市区街道居委数据导入与联动查询实战 简介面向数据库开发、后端工程师及需要行政区划基础数据的项目开发者这份资源提供可直接落库的中国五级行政区划数据脚本覆盖省级、地级、县级、乡级到村级五个层级的完整划分解决手动整理层级数据耗时且易出错的问题。压缩包共2个文件均为sql脚本整体约12.23MB分别对应五级行政区划数据及带层级关联的完整数据表执行后即可在数据库中生成结构清晰的行政区划表便于按层级检索、关联地址信息或支撑地区统计类业务。资源已有615人学习下载适合需要快速搭建地址库、做区域数据初始化或验证层级查询逻辑的开发者参考使用省去从零采集与清洗数据的环节。1. 五级行政区划 SQL 脚本一份能直接灌进业务库的省市区街道居委数据做后台系统的人大概都遇到过这种需求用户注册要选籍贯订单要填收货地址门店要归属到某个街道。前端三级联动好办省市区随便找个接口就能凑合可一旦业务要求精确到「街道」甚至「居委会」网上那些免费接口就开始掉链子——要么缺层级要么编码对不上要么字段名跟你的表结构八竿子打不着。这份五级行政区划数据库表 SQL 脚本解决的正是这个卡点它把省、市、区县、街道/乡镇、村/社区五个层级的行政区划连同层级编码、父级关系、名称、层级标识一起打包成可直接执行的建表加插数脚本导入后就是一张能支撑地址选择、区域统计、权限按区域划分的基础表。适合正在做电商、政务、CRM、门店管理这类需要精确地址归属的开发者也适合想给已有系统补一套本地化区划字典的团队。数据库选型上脚本以通用 SQL 为主MySQL、PostgreSQL 这类主流关系库基本能直接跑改造成本低。2. 表结构与数据模型五级区划怎么用一张表装下2.1 单表自关联还是五张表分开拿到这类脚本第一件事不是急着执行而是先看它怎么建模。行政区划数据有个天然特点层级固定但每层数量差异极大省只有三十多个村/社区却是几十万级。常见做法有两种一种是五张表分别存省、市、区、街道、村靠外键串起来另一种是一张表加parent_id自关联再用level字段标记层级。这份脚本走的是后者单表结构理由很实在——五级联动查询时单表按parent_id递归或逐层查都方便不用 join 五次而且区划数据是只读字典不存在频繁更新带来的锁竞争问题。单表方案的核心字段大致是这样组织的CREATE TABLE sys_region ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, code VARCHAR(12) NOT NULL COMMENT 行政区划编码层级内唯一, name VARCHAR(64) NOT NULL COMMENT 区划名称, short_name VARCHAR(32) DEFAULT NULL COMMENT 简称如朝阳, parent_code VARCHAR(12) DEFAULT NULL COMMENT 父级编码顶级为 NULL 或 0, level TINYINT NOT NULL COMMENT 层级1省 2市 3区县 4街道 5村社区, pinyin VARCHAR(64) DEFAULT NULL COMMENT 拼音用于检索, sort INT DEFAULT 0 COMMENT 同级排序, PRIMARY KEY (id), UNIQUE KEY uk_code (code), KEY idx_parent (parent_code), KEY idx_level (level) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT五级行政区划字典;这段建表语句里有几个点值得说清楚。code用字符串而不是整型是因为行政区划编码有前导零而且长度不固定用VARCHAR(12)能兼容到村级编码parent_code存父级编码而不是父级主键 id好处是导入数据时不用先查父级 id脚本里直接按编码建立关系导入顺序也不敏感。level字段是查询的关键前端做联动时第一层永远查level1第二层查parent_code选中省编码逐层往下逻辑非常直白。pinyin和short_name属于锦上添花做搜索联想和地址简写时用得上没有也不影响主流程。提示如果你的业务库已经有自己的区划表别直接覆盖先把脚本里的表名和字段名对一遍用ALTER补字段比整表替换安全得多。2.2 编码规则决定了你能不能做区域统计五级区划数据能不能用很大程度取决于编码规则是否规整。国家标准里省级编码两位、市级四位、区县六位、街道九位、村级十二位逐级在父级编码后追加。这份脚本的数据基本遵循这个规律所以你可以用编码前缀做很多事比如统计某个市下面所有区县直接WHERE code LIKE 1101%就能圈出来不用递归。做区域权限时也一样给用户绑定一个区县编码他管辖范围内所有街道和村用前缀匹配就能一次性捞出来。-- 查某个省下的所有地级市 SELECT code, name FROM sys_region WHERE parent_code 11 AND level 2 ORDER BY sort; -- 用编码前缀统计某市下辖区县数量 SELECT COUNT(*) FROM sys_region WHERE code LIKE 1101% AND level 3;第一条按parent_code精确查子级适合做联动下拉第二条用LIKE前缀匹配适合做统计和权限范围计算。这里有个容易翻车的点前缀匹配必须带上level条件否则1101这个前缀可能同时命中市级和区县级记录数量就重复了。我一般会在业务层封装一个listChildren(parentCode)方法内部固定加level parentLevel 1避免调用方各写各的。2.3 索引怎么配才不拖慢联动查询区划表数据量大村级记录几十万条索引配不好前端每敲一个字就查一次数据库很快就顶不住。上面建表语句里配了三个索引uk_code保证编码唯一idx_parent支撑按父级查子级idx_level支撑按层级过滤。实际用下来联动查询走idx_parent就够了因为每次都是「已知父级编码查直接子级」命中率很高。真正需要额外注意的是拼音搜索如果业务要做「输入 chaoyang 出朝阳区」pinyin字段上最好再加一个前缀索引或者干脆把搜索放到前端做本地过滤别每次都打数据库。-- 拼音前缀检索注意只对前缀有效中间匹配用不上索引 SELECT code, name FROM sys_region WHERE pinyin LIKE chaoyang% AND level 3 LIMIT 20;这条查询能走索引的前提是pinyin上有索引且匹配模式是前缀。如果写成LIKE %chaoyang%索引直接失效几十万行全表扫接口响应能从几十毫秒飙到几百毫秒。血泪经验是区划搜索这类需求数据总量可控最稳的做法是启动时把整表加载进内存做本地检索数据库只负责持久化查询压力直接归零。3. 导入与验证从执行脚本到跑通一次五级联动3.1 执行脚本前先确认字符集和引擎脚本拿到手别急着source一把梭。先确认两件事数据库字符集和存储引擎。区划名称里有生僻字和少数民族语地名utf8三字节编码存不下必须用utf8mb4。存储引擎方面脚本如果是 MyISAM 写的导入前改成 InnoDB否则事务和行锁都用不了后续做增量更新会很难受。# 建库时显式指定字符集避免继承服务器默认值 mysql -u root -p -e CREATE DATABASE region_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 导入脚本注意指定字符集参数 mysql -u root -p --default-character-setutf8mb4 region_db region_five_level.sql第一条命令建库时锁定字符集第二条导入时再显式指定一次双保险。很多人导入后中文变问号就是这两处有一处没对上。导入完成后先别写业务代码跑几条验证 SQL 确认数据完整。-- 各层级记录数用来判断数据是否导全 SELECT level, COUNT(*) AS cnt FROM sys_region GROUP BY level ORDER BY level; -- 抽查一条村级记录确认父级链路能串起来 SELECT c.name AS village, s.name AS street, d.name AS district FROM sys_region c JOIN sys_region s ON c.parent_code s.code JOIN sys_region d ON s.parent_code d.code WHERE c.level 5 LIMIT 5;第一条按层级统计正常情况下省级三十多条、市级三百多条、区县两千多条、街道四万多条、村级六十万条上下数量级对不上说明脚本没导全或者中途报错被忽略了。第二条做三级 join 抽查能查出结果说明parent_code关系是通的这是后续所有联动逻辑的基础。3.2 后端接口怎么封装五级联动数据进库只是第一步真正让前端用起来还得封一层接口。常见做法是提供两个端点一个按父级编码查子级一个按编码查完整路径。前者给联动下拉用后者给地址回显用。# 基于 SQLAlchemy 的区划查询封装框架无关换 JDBC 也是同样思路 def list_children(parent_code: str): level int(parent_code and len(parent_code) // 2 1 or 1) return db.session.query(Region) \ .filter(Region.parent_code parent_code, Region.level level) \ .order_by(Region.sort) \ .all() def get_full_path(code: str): path [] node db.session.query(Region).filter_by(codecode).first() while node: path.append(node.name) node db.session.query(Region).filter_by(codenode.parent_code).first() return list(reversed(path))list_children里根据父级编码长度推算子级层级省两位推市四位、市四位推区六位逻辑自洽省得前端每次多传一个 level 参数。get_full_path从叶子往上逐级查父级最后反转成「省市区街道村」的顺序用于订单地址回显。这里有个性能细节逐级查父级是 N 次查询五级就是五次量小无所谓量大时改成一次查出所有祖先编码再批量查能省不少往返。我一般会在服务启动时把整表缓存进本地 Mapget_full_path直接内存里走接口响应稳定在个位数毫秒。3.3 前端联动组件的对接方式前端这边五级联动和三级联动的差别只在层数组件逻辑可以复用。核心是维护一个选中路径数组每选一级就清空后面所有级再按当前选中编码请求下一级。// 五级联动核心状态管理框架无关的写法 const path []; // 存每级选中的 {code, name} async function onSelect(level, item) { path.length level - 1; // 清空当前级之后的所有选择 path[level - 1] item; if (level 5) { const children await fetch(/api/region/children?parent${item.code}); renderOptions(level 1, children); // 渲染下一级下拉 } }path.length level - 1这行是联动组件的灵魂用户改了省级选择市、区、街道、村全部作废避免出现「省改了但村还是旧值」的脏数据。level 5判断防止在村级还去请求下一级。实际项目里还要处理回显场景编辑已有地址时按完整编码逐级反查并预填这时候用后端那个get_full_path接口最省事。4. 避坑与排查导入和使用区划数据最容易踩的五个坑4.1 导入后中文全是问号现象是执行完脚本SELECT出来名称字段全是???。原因基本锁定在字符集链路某一环断了客户端连接字符集、数据库默认字符集、表字符集三者不一致。解决方式是导入前用SHOW VARIABLES LIKE character_set%逐项确认建库建表都显式写utf8mb4导入命令加--default-character-setutf8mb4。已经导坏的改完字符集后重新导入别指望CONVERT能救回来问号是不可逆的。4.2 联动查询越查越慢现象是前端每选一级接口响应从几十毫秒逐渐涨到几百毫秒。原因通常是parent_code上没索引或者查询条件写成了LIKE %xx%导致全表扫。解决方式是确认idx_parent存在所有子级查询统一走parent_code ?精确匹配拼音搜索改前缀匹配或挪到内存。另外检查一下是不是每次请求都重新建了数据库连接连接池没配好也会让响应时间虚高。4.3 编码前缀统计数量翻倍现象是用code LIKE 1101%统计区县数量结果比实际多。原因是前缀同时命中了市级记录编码1101本身和区县记录110101等。解决办法是统计时固定加level条件或者用LENGTH(code) 6限定编码长度。这个坑我在两个项目里都见过本质是编码前缀天然包含父级自身写统计 SQL 时脑子里要有这根弦。4.4 区划数据更新后旧地址对不上现象是行政区划调整后比如某地撤县设区历史订单里的区划编码在新数据里查不到了。原因是脚本是某一时点的快照而区划本身会变。解决办法是区划表加一个status字段标记启用/停用更新时只停用不删除历史数据仍能查到名称新业务只查status1的记录。后悔药就是别物理删除区划记录字典表的数据删除成本远高于加个状态位。4.5 村级数据量太大拖慢启动现象是服务启动时把整表加载进内存做缓存启动时间从几秒变成几十秒。原因是村级六十万条记录全量加载内存和序列化开销都不小。解决办法是分级缓存省市区街道四级全量加载几万条内存无压力村级按需懒加载或只缓存热点区域。我一般会按业务实际覆盖范围做裁剪全国全量村级缓存对多数系统来说是过度设计。5. 二次开发把区划字典改造成业务可用的地址体系5.1 扩展字段与业务表关联原始脚本给的是标准区划字典落到具体业务还得扩。最常见的改造是加status、is_hot、ext_code三个字段status控制启停is_hot标记热门城市用于前端快捷选择ext_code存第三方物流或地图服务商的区域编码做地址解析对接时能省一层映射。业务表这边用户表、订单表、门店表都只存最末级区划编码需要展示完整地址时用get_full_path反查别在每个业务表里冗余存五级名称否则区划一调整就得批量刷数据。ALTER TABLE sys_region ADD COLUMN status TINYINT DEFAULT 1 COMMENT 1启用 0停用, ADD COLUMN is_hot TINYINT DEFAULT 0 COMMENT 是否热门城市, ADD COLUMN ext_code VARCHAR(32) DEFAULT NULL COMMENT 第三方区域编码; -- 业务表只存末级编码配合索引支撑按区域筛选 ALTER TABLE biz_order ADD COLUMN region_code VARCHAR(12) DEFAULT NULL; CREATE INDEX idx_region ON biz_order (region_code);status默认 1 保证存量数据可用is_hot让前端热门城市不用硬编码ext_code是给对接留的口子。订单表加region_code并建索引后按区域做订单统计、按街道划分配送范围都能直接查不用再 join 区划表。5.2 增量更新脚本的写法区划数据不是一成不变的每年都有调整。全量重导风险大正确姿势是写增量脚本新增的INSERT改名的UPDATE撤销的UPDATE status0。关键是所有操作以code为唯一键用INSERT ... ON DUPLICATE KEY UPDATE保证幂等重复执行不会产生脏数据。-- 增量更新示例新增一个街道已存在则更新名称 INSERT INTO sys_region (code, name, parent_code, level, sort) VALUES (110101001, 某新设街道, 110101, 4, 99) ON DUPLICATE KEY UPDATE name VALUES(name), sort VALUES(sort); -- 撤销的区划只停用不删除保住历史数据可查 UPDATE sys_region SET status 0 WHERE code 110101002;ON DUPLICATE KEY UPDATE依赖uk_code唯一索引所以这个索引千万不能删。停用用UPDATE status而不是DELETE前面避坑章节说过原因这里再强调一次字典表的历史记录是业务数据的锚点删了历史订单的地址就断了。5.3 验证改造是否成功的三个检查点改完之后别急着上线跑三个检查。第一随机抽十个末级编码get_full_path能返回完整的五级名称链第二按某个区县编码做前缀统计数量与parent_code逐级累加的结果一致第三把一条历史订单的region_code指向已停用区划详情页仍能正常显示地址名称。这三个点过了说明字典层和业务层的关联是稳的。检查项验证方式通过标准路径完整性抽查末级编码反查五级名称齐全无空统计一致性前缀统计对比逐级累加数量完全相等历史兼容停用区划关联历史单名称正常显示从那以后我每次接区划数据都强制先跑一遍层级统计和路径抽查再谈业务对接——数据本身不干净后面写再多代码都是白搭。希望这份拆解能帮你少走点弯路把这份 SQL 脚本顺顺当当灌进自己的库里跑起来。本文还有配套的精品资源点击获取