☰
2023全国五级行政区划SQL:12位编码、层级查询与避坑指南
2026/10/10 2:59:48 网站建设 项目流程

简介:全国五级行政区域数据库表及配套SQL文件,覆盖省级、地级、县级、乡级、村级五级行政区划名称与区域代码,适合需要准确行政区划数据的开发人员、数据分析师及GIS应用构建者,解决业务系统中数据时效性与代码对照问题。资源包共3个文件,包含2个SQL脚本与1个CSV表格,压缩包大小18.8MB;SQL脚本分别对应完整数据表与层级关联表,便于直接导入MySQL等主流数据库,CSV表格可作数据查阅或二次加工。数据更新至2023年3月,细分至行政村、社区一级,并涵盖民族乡、苏木等特殊区划类型;可用于地址匹配、业绩统计、地图可视化等场景,也可为省市县乡层级治理提供基准数据。目前已有5013人学习下载,数据表结构清晰,配合SQL可快速完成建库与初始化。如需原始表格或JSON格式,可联系作者获取,便于不同环境灵活使用。

1. 全国五级行政区划 SQL:它解决的不只是“查地址”,而是把层级关系一次对齐

做业务系统的时候,地址类字段是最容易翻车的地方之一。不少项目用省市区手工三级下拉,遇到要选到村镇级的需求,就只能临时找数据拼,结果要么层级不全,要么更新滞后,代码也散落在各种 SQL 片段里,换个环境就跑不起来。这份 2023 年最新版全国五级行政区域数据库表和 SQL 文件,补的正是这个短板——它不是一个普通的地名清单,而是带 12 位编码、父级关系、五级层级标记的一套完整数据表,导入后可以直接当成字典表、校验表,甚至作为地址子系统的底座。适合正在做电商物流、政务系统、网格化管理的开发者,尤其是被地址数据折腾过一轮、想少踩几个坑的人。

2. 五级数据怎么分:12 位编码和层级口径先对齐

2.1 五级不是“省市区县乡”这么简单,而是统计口径的五级

很多人在第一次看这张表的时候会问:不是只有省、市、县、乡四级吗,哪来的第五级?

这里说的五级,是按统计用区划代码的口径划分的:

  • 第一级:省级,包括省、自治区、直辖市
  • 第二级:地市级,包括地级市、地区、自治州、盟
  • 第三级:县区级,包括县、区、县级市、旗
  • 第四级:乡镇街道级,包括镇、乡、街道、苏木
  • 第五级:村居委员会级,包括村委会、居委会,以及部分类似村级的功能区代码

民间习惯里“省市区县”只到第四级,第五级日常很少用,所以普通项目往往只维护到乡镇。但真做网格化管理、物流末端派送、农村电商的时候,缺了第五级,订单和区域绑定就会露馅。2023 年更新版的价值,正是把这一级也补齐了,并且处理了一批近两年撤乡设镇、街道拆分后的代码变动。

需要留意的是,第五级并不是每个地区都有。像某些功能区、农场、兵团团场,代码体系不完全按常规的“乡镇下面挂村居”来走,后面避坑章节会专门说。

2.2 12 位编码的分段规则:每一段对应哪一级

这份 SQL 表里的核心字段是 12 位区划代码,分段规则和身份证前 6 位有重叠,但含义不完全一样。分段如下:

代码位对应级别说明
1-2 位省级表示省、自治区、直辖市
3-4 位地市级表示地级市、地区、自治州
5-6 位县区级表示区、县、县级市
7-9 位乡镇级表示镇、乡、街道
10-12 位村居级表示村委会、居委会

以一段虚构代码为例,比如990102123001,拆开就是:

  • 99:某省级行政区
  • 01:该省下的第一地市
  • 02:该市下的第一区县
  • 123:该区县下的一个乡镇或街道
  • 001:该乡镇下的第一个村或社区

前 6 位大体能和身份证号里的地区代码对上,但注意,这只是“大体”。统计口径的区划代码和公安户籍使用的行政区划代码偶尔有偏差,尤其是乡镇以后的部分,很多是统计部门为城乡划分单独编码的,不能用身份证前 6 位反推村级代码。做数据清洗的时候,最忌讳的事就是拿身份证号去推测用户所在村居级代码,推出来大概率对不上。

另外,这份表里除了代码和名称,通常还带一个明显的“级别”字段,用来快速过滤某一层级的全部记录。查询时优先用级别字段,不要在名称里做关键字匹配,否则会有大量重名干扰。

3. 把表建起来:DDL 结构、导入命令和字段设计思路

3.1 五级一张表还是五张表:为什么选单表加 level 标记

拿到这份数据资源以后,第一步不是急着导入,而是先想清楚表结构。网上流传的做法有好几种,有人说省、市、县、乡、村各建一张表,每张表外键关联,很像标准的关系建模;也有人说没必要,一张表自关联就够了。

我一般选单表,加一个 level 字段标记层级。原因是五级行政区划的层级虽然固定,但实际业务里经常要处理“这个直辖市的第几级缺失了”“那个省直辖县跨级了”这类情况。单表自关联的好处在于,接口层不需要为每一级单独写一套查询逻辑,只要传父级代码就能往下钻;数据有异常,一眼就能从 parent_code 字段看出来。

此外,多表方案在更新数据时非常痛苦。行政区划每年都有调整,单表更新只需要导入新数据,多表则要按层级分别更新,还要处理外键顺序,稍不留神就会因为先插子表后插父表报外键错误。

建表语句可以这样写:

DROP TABLE IF EXISTS region; CREATE TABLE region ( code CHAR(12) NOT NULL COMMENT '12位区划代码', name VARCHAR(100) NOT NULL COMMENT '区划名称', parent_code CHAR(12) DEFAULT NULL COMMENT '父级代码,顶级为NULL', level TINYINT NOT NULL COMMENT '1省 2市 3县区 4乡镇街道 5村居委', region_type VARCHAR(20) DEFAULT '' COMMENT '区/县/镇/乡/街道/社区/村委会等', PRIMARY KEY (code), KEY idx_parent_code (parent_code), KEY idx_level (level) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='全国五级行政区划表';

这里有几个关键点:

code用CHAR(12)而不是VARCHAR(12),因为代码长度固定,前导 0 很重要。如果用整数类型,990010000000这类代码会被去掉前导位,父级匹配直接失效。parent_code允许为空,顶级省级记录没有父级,不要为了结构完整给它塞一个假的“中国”父节点,否则查询链路会多出一层无意义的目录。

索引只加了parent_code和level。code是主键,天然有索引。这样设计覆盖了绝大多数查询场景:按父级查子级、按级别过滤、按代码精确匹配。如果业务经常按名称模糊搜索,可以再加一个name前缀索引,但一般不建议直接对名称建全列索引,字符串很长,索引膨胀明显。

3.2 导入三步走:字符集、导入命令、数据校验

建好表以后,导入这份 SQL 文件有标准流程,顺序错了会出乱码或者主键冲突。

第一步,确认文件字符集。用文本编辑器打开 SQL 文件,或者查看文件头,确认里面写的是utf8mb4还是gbk。这份资源标注为 2023 年最新版,大概率是 UTF-8 格式,但稳妥起见还是要确认。检查方法简单:

file region.sql

看到输出里带 UTF-8 字样就可以放心用。如果是 GBK,之后需要转码再导入。

第二步,命令行导入。直接执行:

mysql -uroot -p --default-character-set=utf8mb4 mydb < region.sql

这里--default-character-set=utf8mb4不能省。它指定客户端发送 SQL 语句时使用的字符集,不指定的话,MySQL 会按系统默认字符集解析,含生僻字的村名很容易变成问号。mydb是你自己的目标库名,导入前先建好库,并且确认库也是 utf8mb4,否则表结构里写了 utf8mb4 也会被库的默认字符集覆盖。

第三步,导入后做条数校验:

SELECT level, COUNT(*) FROM region GROUP BY level;

正常情况是第 1 级数量在 30 到 35 之间,第 5 级数量最多,通常有几十万条。如果查出来第 5 级一条都没有,说明导入的 SQL 不完整,或者表的 level 字段映射有问题。不要只看总条数,分级统计更可靠。

导入过程建议用一个事务包住整份 SQL,尤其是更新已有数据的时候。常见做法是在导入前执行START TRANSACTION,导入完成后检查数据无误再COMMIT,一旦发现数据异常可以直接ROLLBACK,不用重新清洗一遍文件。

4. 从村到省拉一条路径:递归 CTE、五层 JOIN 和级联接口的取舍

4.1 固定层级的五层自连接:简单直接

导入完成之后,最常用的需求是:给定一个村居代码,查出它的完整路径——村、乡镇、县区、地市、省。

因为层级固定是五级,最直接的方式就是五层自连接。这种写法思路简单,适合数据量几十万行的表,加上索引后性能并不差:

SELECT c.name AS village, t.name AS town, d.name AS district, s.name AS city, p.name AS province FROM region c LEFT JOIN region t ON t.code = c.parent_code LEFT JOIN region d ON d.code = t.parent_code LEFT JOIN region s ON s.code = d.parent_code LEFT JOIN region p ON p.code = s.parent_code WHERE c.level = 5 AND c.code = '990102123001';

这里用了LEFT JOIN而不是INNER JOIN,是有意的。如果某条数据的父级链断了,比如某个乡镇的parent_code指向了一个已停用的县,用INNER JOIN会把整条记录丢掉,只剩 NULL;用LEFT JOIN则至少能看到村和乡镇名称,查出来哪一级是 NULL,顺便就发现了脏数据。

参数说明:最内层的c表代表第五级,t、d、s、p分别是乡镇、县区、地市、省级。条件里的c.code换成实际要查询的村居代码。如果只想查某个乡镇下全部村的路径,可以把WHERE c.code =改成WHERE c.parent_code = '某乡镇代码'。

这种写法的缺陷也很明显:层级一旦变成六层甚至七层,就要继续加 JOIN,维护成本上升。但它有一个递归写法比不上的优势——不依赖 MySQL 版本,5.7 和 MariaDB 10.1 都能跑,生产环境如果还在用老版本,优先用它。

4.2 用递归 CTE 支持任意深度:适合做动态层级接口

如果项目用的是 MySQL 8.0 以上版本,推荐用递归 CTE 替代多层 JOIN,代码更简洁,而且天然支持“从任意节点向上回溯”或“向下展开”:

WITH RECURSIVE region_path (code, name, parent_code, level) AS ( SELECT code, name, parent_code, level FROM region WHERE code = '990102123001' UNION ALL SELECT r.code, r.name, r.parent_code, r.level FROM region r INNER JOIN region_path rp ON r.code = rp.parent_code ) SELECT * FROM region_path ORDER BY level;

递归部分拆开看就是两步:第一步查出起始节点的信息;第二步用INNER JOIN把当前节点的父级一层层接上来。r.code = rp.parent_code这个条件表示向上追溯,想要向下钻取,把条件反过来写即可。

参数说明:WHERE code = '990102123001'是递归的种子节点,可以是任意层级的代码。ORDER BY level保证路径从村到省排列,也可以改成ORDER BY level DESC得到从省到村的路径。由于递归可能产生重复节点,建议在最终结果上加上SELECT DISTINCT,防止异常父级链条导致死循环。

要注意的是,递归 CTE 在 MySQL 8.0 和 MariaDB 10.2+ 上支持,但很多云数据库默认关闭了cte_max_recursion_depth,数据没问题也可能会报错。使用前先确认变量状态,不然上线之后夜里突然报错,处理起来很被动。

4.3 级联下拉接口:后端怎么设计不卡顿

行政区划最常见的应用是省市区乡村级联下拉。前端每选一级就向后端要下一级列表,接口实现不能全量返回,几十万条记录一次性出完,页面直接卡死。

推荐接口设计是:接收一个parent_code参数,返回该节点下所有直接子级。

SELECT code, name, level FROM region WHERE parent_code = ? ORDER BY code;

前端第一级传空或传固定标识,后端返回省级;选中某个省后,把省的code传回来,继续查下一级。这个接口用到的就是之前建的idx_parent_code索引,单次查询返回几百条记录,性能没有任何压力。

需要注意的一点是,直辖市的区下面没有地市这一层,用户选完“北京市-海淀区”之后,第三级直接就是乡镇街道。接口层对层级变化要宽容,按level字段判断当前到达第几级,而不是假定“省下面一定是市”。

5. 避坑:行政区划数据最常见的五个翻车点

5.1 直辖市的层级链缺一级:乡镇的父级不是区

现象:用户选择直辖市的某个街道,前端联动的“地市”一栏没有数据,或者查询村到省路径时,中间有一级显示为 NULL。

原因:直辖市的行政区划本身就是“省-区-乡镇”结构,没有地市级。这份表里直辖市的区县节点,其parent_code直接指向省级代码,再往下挂乡镇街道,层级比普通省份少一级。

解决:业务代码里不要默认“省下面一定有市”。查询路径时,把“某级没有父级”当作正常情况处理,不要强行给直辖市的区找一个虚假的地市父节点。前端级联组件也要支持跳级,否则用户选完直辖市后下拉框就断档。

5.2 省直辖县、兵团和功能区代码不在常规父子链上

现象:查某个县的parent_code,发现它指向的是省而不是地市,导致五层 JOIN 出来的城市字段为空。

原因:全国有一批省直辖县级单位,它们不归属任何一个地市,行政上直接由省管理。另外兵团、农场、林场等功能区的代码,虽然挂在省级节点下面,但自身既不是标准镇也不是标准乡,region_type字段和普通区划不同。

解决:先接受数据本身有多样性,不要用单一规则去校验所有记录。平时做统计报表时,按 “省-省直辖县” 的路径建立一张映射表,把这类特殊情况单独维护,避免每次查询都走一遍完整五级链路。兵团这类节点,建议把parent_code为空的分支抽出来做特殊标记。

5.3 代码回收与更名:旧业务数据关联不上新表

现象:业务表里存着 2021 年的乡镇代码,2023 年去 join 新导入的行政区划表,大量记录匹配不上,订单区域的统计数字凭空少了一大截。

原因:行政区划代码不是永久不变的。撤乡并镇、街道拆分、县改区,都会导致代码被回收或重新分配。老代码在 2023 年版表里已经不存在,自然关联不上。

解决:不要用最新表直接覆盖历史上所有业务数据。建议保留三类数据:当前最新区划表、历史区划表、区划变更对照表。业务查询先按当前表关联,匹配不上的再用历史表兜底,并把匹配结果记录到日志里。每次导入新版 SQL 之前,先拿旧表和新表做一次 diff,生成变更对照,再决定是否需要更新业务表里的冗余地区字段。

5.4 生僻地名和繁体名乱码:导入就错,后面全错

现象:SQL 导入后,部分村名显示为问号或乱码,比如某个字显示成“锟斤拷”,直接把地址文本打废。

原因:SQL 文件是 UTF-8 编码,但 MySQL 客户端连接字符集不是 utf8mb4。导入时--default-character-set没指定,或者建库的时候用了latin1,让数据在写入中文时发生了不可逆的编码错误。

解决:导入前检查两处,一是文件编码,二是建库语句里的DEFAULT CHARSET。两者都确认是 utf8mb4 后再执行导入。如果已经导入到一半发现乱码,不要试图用ALTER TABLE改字符集来恢复,编码错误是字节级别的问题,改字符集救不回来,只能清空表重新导入。血泪教训:导入后第一件事就是随机查几条含生僻字的记录,别等业务跑起来才发现。

5.5 重复导入导致主键冲突:表清理顺序不能省

现象:同一份 SQL 文件执行两次,第二次导入时报Duplicate entry错误,导入中断在中间位置。

原因:区划代码是主键,重复导入时后一批数据与已有数据撞主键。很多开发者以为“再导入一遍会覆盖”,实际上默认INSERT遇到主键冲突会直接报错,不会自动更新。

解决:如果要全量刷新,先清空表再导入:

TRUNCATE TABLE region;

然后重新执行导入命令。如果只想增量更新,建议导入前把 SQL 里的INSERT INTO改成INSERT IGNORE INTO,或者在导入前先执行:

SET FOREIGN_KEY_CHECKS=0;

不过更稳妥的方案是维护一张快照表,把新旧数据做对比,只更新有变化的记录。全量清空虽然简单,但会把历史数据的血统抹掉,线上环境不建议这么干。

6. 把区划表当字典和校验工具来用

导入完成只是第一步,行政区划表真正的价值在于,把它当成一套基础字典持续使用。我一般会在项目里做两件事:一是地址文本校验,二是增量更新对比。

地址文本校验,用来判断用户填写的收货地址疑似错别字或非法地名。做法是把区划名称加载到内存字典,匹配地址字符串。核心逻辑是:拆出关键词,先在区划表里精确匹配,匹配不上的再尝试模糊匹配。

from pymysql import connect conn = connect(host='localhost', user='root', password='', db='mydb', charset='utf8mb4') cur = conn.cursor() cur.execute("SELECT code, name, level FROM region") region_map = {row[1]: row for row in cur.fetchall()} def check_address(address): for name, row in region_map.items(): if name and name in address: return row[0] return None

这段代码的作用是把所有区划名称放到内存里,逐条检查地址文本中是否包含区划名称。优点是不需要引入外部分词库,对五级表这种几万行级别的数据完全够用。缺点是名称可能有重名,比如“城关镇”很多省份都有,只靠名称反查代码不可靠,真实场景里还需要结合地址上下文去消歧。

增量更新对比,比直接全量覆盖安全得多。思路很简单:把上一次导入的数据保留在快照表region_old里,用NOT IN查出新增和停用代码,再决定业务表怎么处理。

-- 查找新增的区划代码 SELECT code, name FROM region WHERE code NOT IN (SELECT code FROM region_old); -- 查找停用的区划代码 SELECT code, name FROM region_old WHERE code NOT IN (SELECT code FROM region);

输出结果后,把新增代码插入业务映射表,把停用代码标记为失效,而不是直接删除。用户已经绑定的旧地址,宁可显示“已失效”也不要去匹配一个新的同名地区,否则历史订单归属会被改动。

从那以后,我每次接手地址类项目,第一件事就是确认行政区划版本和父级链完整率,运行时跑一遍完整性检查再动业务表,推进度之前先堵坑。这份 2023 年版的全国五级行政区划表,解决的不只是拉一个下拉菜单的问题,而是让地址字段从“看起来有数据”变成“真正能对上关系”,希望能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询