
简介《国家地名数据库代码编制规则》是一份面向民政地名管理、地理信息系统开发及数据治理从业者的指导性文档用于解决地名编码不统一、信息检索困难等问题。资源包内含1个PDF文件大小约229KB内容完整呈现20位地名数据库代码的四段结构6位县级以上行政区划代码、3位县级以下行政区划代码、5位地名属性类别代码以及6位附加码并逐层说明各段码位的取值规则与编制依据。文档同时梳理了代码管理职责、更新维护流程与适用范围附录部分收录《地名分类与类别代码编制规则》国家标准涵盖分类原则、分类方法及术语定义。读者可据此掌握GB2260、GB/T10114-2003、GB/T18521-2001等标准的衔接方式理解街道、镇、乡、政企合一单位等类别的编号区间以及撤销区划代码不再复用的唯一性要求。目前已有73人学习适合需要规范地名数据采集、存储与交换的技术人员参考。1. 一份让行政区划代码不再“拍脑袋”的规则文档做数据治理的同行大概都遇到过这种场景业务方甩过来一张表字段叫“地区代码”里面既有 6 位数字也有 9 位、12 位的混合体还有带字母后缀的。你问对方按什么标准编的回答往往是“历史遗留”“前任留下的”“系统里就这么存的”。等到要做跨表关联、地址清洗、统计汇总时才发现这些代码根本对不上只能靠地名模糊匹配硬扛匹配率惨不忍睹。这份《国家地名数据库代码编制规则》解决的正是这个源头问题。它规定了地名数据库里各类地名实体代码的结构、位数、编码方法和校验方式是行政区划、地名普查、地址库建设、统计用区划代码维护等工作的底层依据。适合谁看做政务数据治理、GIS 空间数据、地址标准化、统计口径对齐的工程师以及需要自建地名库或对接上级区划代码的开发者。它不教你写代码但它决定了你代码里那些常量表该怎么建、怎么校验、怎么扩展。2. 代码结构拆解从 12 位到分段语义2.1 为什么不是简单的行政区划六位码很多人第一反应是行政区划代码不就是身份证前六位那套吗省 2 位、市 2 位、县 2 位加起来 6 位够用了。但地名数据库要管的远不止行政区划——自然地理实体、非行政区域、群众自治组织、甚至消失的历史地名都要入库。六位码只能覆盖到县级乡镇和村级根本没有国家标准码而地名库必须给每一个地名实体一个唯一标识。所以这份规则采用的是分段组合编码把不同层级的语义压进一个固定长度的字符串里。常见做法是 12 位主码加若干位扩展码主码里再按“大类 中类 小类 顺序号”切分。这样既保留了行政区划的层级关系又能给非行政区划地名分配独立号段避免和行政区划码冲突。2.2 分段结构与参数含义规则里最核心的是代码的段位定义。以常见的 12 位主码为例结构大致如下段位位数含义取值说明第 1 段2 位省级行政区划代码按 GB/T 2260 执行如 11、31第 2 段2 位地级行政区划代码同上省内地级市顺序第 3 段2 位县级行政区划代码同上县区顺序第 4 段2 位地名类别码按规则附录的地名分类表取值第 5 段4 位顺序号同一县级区域内同类地名的流水号这里有个容易翻车的地方第 4 段类别码不是随便定的它和地名分类体系绑定。比如“自然地理实体”下的“山峰”和“河流”类别码不同顺序号也是分开排的。如果你把山峰的顺序号拿去编河流虽然位数对得上但语义完全错了后续按类别统计时就会串数据。提示不同版本或不同地区的实施细则可能在段位长度上有微调比如有的把顺序号扩到 6 位有的在末尾加校验位。拿到规则后先确认你手头这份的段位定义表不要直接套用记忆里的位数。2.3 类别码的取值逻辑类别码是整份规则里最需要对照表操作的部分。规则一般会附一张地名分类与代码表按“大类—中类—小类”三级展开。大类可能包括行政区划、非行政区划、自然地理实体、交通水利设施、纪念地与旅游景点等。每个小类对应一个两位或三位的类别码。实际操作中我一般会把这张分类表先落成一张数据库字典表字段包括类别码、大类名称、中类名称、小类名称、是否可编顺序号、备注。这样在写编码生成逻辑时直接查表取值而不是在代码里写一堆 if-else。字典表建好后后续规则版本更新也只需要改数据不用改代码。-- 地名类别字典表结构示例 CREATE TABLE geo_name_category ( cat_code VARCHAR(8) NOT NULL PRIMARY KEY, -- 类别码 major_name VARCHAR(64) NOT NULL, -- 大类名称 medium_name VARCHAR(64), -- 中类名称 minor_name VARCHAR(64), -- 小类名称 seq_required SMALLINT DEFAULT 1, -- 是否需要顺序号1 是0 否 remark VARCHAR(255) -- 备注 ); -- 插入示例自然地理实体下的山峰类别 INSERT INTO geo_name_category (cat_code, major_name, medium_name, minor_name, seq_required) VALUES (03, 自然地理实体, 地貌, 山峰, 1);上面这段 SQL 建的是类别字典表cat_code是规则里定义的类别码seq_required用来标记该类地名是否需要分配顺序号。有些类别如“行政区划”本身已有国家标准码就不需要再编顺序号这个字段能帮你在生成逻辑里做分支判断。remark字段建议用来记录该类别在规则里的特殊说明比如“仅限县级及以上”“历史地名专用号段”等避免时间久了忘记边界条件。3. 编码生成与校验把规则落成可执行逻辑3.1 顺序号分配策略顺序号是编码里最容易出问题的部分。规则通常要求同一县级区域内、同一类别下的地名顺序号连续或不重复但实际数据往往来自多个批次、多个来源。常见做法是维护一张“顺序号分配表”按县级代码 类别码维度记录当前最大顺序号每次新增地名时先查最大值再加一。import pymysql def next_sequence(conn, county_code, cat_code): 获取指定县级区域、指定类别下的下一个顺序号 :param conn: 数据库连接 :param county_code: 6 位县级行政区划代码 :param cat_code: 类别码 :return: 下一个可用顺序号4 位整数 with conn.cursor() as cur: # 锁定当前行防止并发冲突 cur.execute( SELECT max_seq FROM geo_seq_alloc WHERE county_code %s AND cat_code %s FOR UPDATE , (county_code, cat_code)) row cur.fetchone() if row is None: # 首次分配从 1 开始 cur.execute( INSERT INTO geo_seq_alloc (county_code, cat_code, max_seq) VALUES (%s, %s, 1) , (county_code, cat_code)) return 1 else: new_seq row[0] 1 cur.execute( UPDATE geo_seq_alloc SET max_seq %s WHERE county_code %s AND cat_code %s , (new_seq, county_code, cat_code)) return new_seq这段代码的关键点在于FOR UPDATE行锁。顺序号分配是典型的并发敏感操作如果两个线程同时查到同一个最大值就会生成重复顺序号。加行锁后第二个请求会等第一个事务提交后再读保证不重号。geo_seq_alloc表的主键应该是(county_code, cat_code)联合主键这样查询和更新都能走索引。返回的顺序号还需要在应用层格式化成 4 位不足补零。3.2 完整代码拼接与校验位拿到各段值后拼接顺序不能错。常见做法是按“省 市 县 类别码 顺序号”依次拼接中间不加分隔符。如果规则里有校验位通常放在最后一位用前几位做模运算得出。def build_geo_code(province, city, county, cat_code, seq): 拼接完整地名代码 :param province: 2 位省级代码 :param city: 2 位地级代码 :param county: 2 位县级代码 :param cat_code: 类别码 :param seq: 顺序号整数 :return: 12 位主码字符串 seq_str str(seq).zfill(4) # 顺序号补零到 4 位 main_code f{province}{city}{county}{cat_code}{seq_str} # 如果规则要求校验位在此计算并追加 # check_digit calc_check_digit(main_code) # return main_code check_digit return main_code def validate_geo_code(code, category_dict): 校验地名代码的基本合法性 :param code: 待校验代码 :param category_dict: 类别码集合 :return: (是否合法, 错误信息) if len(code) ! 12: return False, 长度不是 12 位 if not code.isdigit(): return False, 包含非数字字符 cat_code code[6:8] if cat_code not in category_dict: return False, f类别码 {cat_code} 不在字典中 return True, build_geo_code里用zfill(4)保证顺序号始终占 4 位这是拼接类编码的通用做法。validate_geo_code做的是基础校验长度、纯数字、类别码是否在字典里。实际业务中还可以加更多校验比如省级代码是否在有效范围内、县级代码是否属于该地级市等。校验函数建议返回错误信息而不是只返回布尔值排查数据问题时能省很多时间。注意如果规则版本里顺序号不是 4 位而是 6 位zfill的参数和切片位置都要同步改。不要硬编码位数最好把段位定义抽成配置。3.3 批量生成时的性能考量当你要给几十万条地名批量生成代码时逐条查数据库分配顺序号会非常慢。我一般会改成批量预分配先按县级代码 类别码分组统计待编数量一次性把号段预留出来再在内存里分配。这样能把数据库交互从 N 次降到分组数次。from collections import defaultdict def batch_allocate(conn, records): 批量预分配顺序号 :param conn: 数据库连接 :param records: 待编码记录列表每条含 county_code 和 cat_code :return: 分配结果字典 # 按县级代码 类别码分组计数 group_count defaultdict(int) for r in records: key (r[county_code], r[cat_code]) group_count[key] 1 allocation {} with conn.cursor() as cur: for (county_code, cat_code), cnt in group_count.items(): cur.execute( SELECT max_seq FROM geo_seq_alloc WHERE county_code %s AND cat_code %s FOR UPDATE , (county_code, cat_code)) row cur.fetchone() start (row[0] if row else 0) 1 end start cnt - 1 # 更新最大顺序号 if row is None: cur.execute( INSERT INTO geo_seq_alloc (county_code, cat_code, max_seq) VALUES (%s, %s, %s) , (county_code, cat_code, end)) else: cur.execute( UPDATE geo_seq_alloc SET max_seq %s WHERE county_code %s AND cat_code %s , (end, county_code, cat_code)) allocation[(county_code, cat_code)] list(range(start, end 1)) return allocation这个批量分配函数先统计每个分组的待编数量然后一次性把整个号段预留出来。allocation字典里存的是每个分组可用的顺序号列表后续在内存里逐条弹出即可。这样做的好处是数据库只交互分组数次而不是逐条交互。代价是如果批量任务中途失败已预留的号段会出现空洞但顺序号本身不要求连续空洞可以接受。4. 避坑与排查那些年我们编错的代码4.1 现象代码长度对不上有的 12 位有的 15 位原因不同来源的数据混用了不同版本的规则或者把主码和扩展码拼在一起存了。有些系统把乡镇级扩展码直接追加在主码后面导致长度不一致。解决先确认你手头这份规则的段位定义把主码和扩展码分开存储。数据库里建两个字段main_code存 12 位主码ext_code存扩展部分。查询和关联时用主码需要精确定位时再拼扩展码。如果历史数据已经混在一起写一个清洗脚本按长度和段位规则拆分。4.2 现象类别码取错山峰编成了河流的号原因类别码对照表没落库开发人员凭记忆或从旧代码里复制导致类别码和实际地名类型不匹配。解决把规则附录的分类表完整录入字典表编码时强制查表。在数据入库前加一道校验根据地名名称或业务类型反查应有类别码和实际填入的类别码比对不一致就拦截。我一般会在 ETL 流程里加这个校验节点宁可入库失败也不要脏数据。4.3 现象顺序号重复两条不同地名用了同一个号原因并发写入时没有加锁或者批量导入时多个线程各自查最大值。也可能是历史数据迁移时没做去重。解决顺序号分配表加联合主键和行锁批量导入走单线程或按分组串行。历史数据用 SQL 查重复SELECT county_code, cat_code, seq FROM ... GROUP BY ... HAVING COUNT(*) 1查到后人工确认哪条该改号改完再补唯一约束。4.4 现象行政区划调整后旧代码失效但数据还在用原因行政区划撤并后国家标准码会变但地名库里的旧代码没有同步更新导致新旧数据对不上。解决维护一张行政区划变更对照表记录旧码、新码、变更类型、生效日期。查询时用COALESCE或CASE WHEN做映射。对于已入库的地名代码不要直接改主码而是加一个status字段标记“历史码”保留可追溯性。新数据一律用新码。4.5 现象校验位算不对导入时被上级系统打回原因校验算法实现有误或者规则版本里校验位算法变了但代码没更新。解决找一份上级系统认可的样例数据用你的校验函数跑一遍比对结果。如果对不上逐位检查权重和模数。常见做法是把校验算法写成独立函数并加单元测试用已知正确样例做断言。规则版本升级时先跑测试再上线。5. 进阶把规则变成可维护的编码服务前面讲的都是单次编码或批量编码。如果你所在的环境需要长期维护地名库建议把编码逻辑抽成一个独立的编码服务对外提供“申请代码”“校验代码”“查询代码”三个接口。这样业务系统不用各自实现一套规则更新时只改服务不改调用方。我一般会这样设计编码服务内部维护类别字典、顺序号分配表、行政区划变更对照表三张核心表。申请代码时传入地名类型、所属县级代码、名称等信息服务返回完整代码。校验接口接收代码字符串返回是否合法及错误详情。查询接口支持按代码反查地名信息。# 编码服务核心接口伪代码 class GeoCodeService: def apply_code(self, county_code, cat_code, name): 申请新代码 # 1. 校验类别码是否存在 # 2. 校验该类别是否需要顺序号 # 3. 分配顺序号 # 4. 拼接主码 # 5. 持久化并返回 pass def validate(self, code): 校验代码合法性 # 1. 长度和字符校验 # 2. 段位拆分 # 3. 类别码字典校验 # 4. 行政区划码有效性校验 # 5. 返回结果 pass def query(self, code): 按代码查询地名信息 # 1. 拆分主码和扩展码 # 2. 查地名主表 # 3. 关联类别字典和区划表 # 4. 返回结构化信息 pass这个服务化思路的好处是边界清晰编码规则的变化被隔离在服务内部业务系统只依赖接口契约。规则升级时服务内部改字典表和分配逻辑接口签名不变调用方无感知。坏处是引入了一次网络调用开销对于超大批量导入场景可以保留批量接口或本地缓存字典表来降低延迟。还有一个实用技巧把规则里的分类表和段位定义做成配置文件或数据库表而不是硬编码在代码里。这样当规则版本更新时运维人员改配置就能生效不用重新发版。我见过太多项目把类别码写死在代码常量里规则一更新就要改代码、走测试、重新部署周期长还容易漏改。从那以后我每次接手地名库相关的项目第一件事就是确认规则版本第二件事就是把分类表和段位定义落成配置第三件事才是写编码逻辑。这个顺序能帮你省掉后面大量的返工。希望帮到你。本文还有配套的精品资源点击获取