☰
MySQL导入三版国民经济行业分类码表与跨版本映射实战
2026/9/25 4:48:41 网站建设 项目流程

简介:这份资源面向从事大数据清洗与行业维度标准化工作的技术人员,提供2002、2011、2017三个年度国民经济行业分类与代码的MySQL数据文件,对应GB/T4754-2002、GB/T4754-2011、GB/T4754-2017三版国家标准。每个代码均按“门类·大类·中类·小类”四级结构组织,例如“A0111”对应“农、林、牧、渔业·农业·谷物及其他作物的种植·谷物的种植”,便于直接入库做行业映射与口径对齐。压缩包共3个文件,均为sql脚本,整体约44KB,体量轻便,导入即可使用。资源已有3034人学习下载,适合需要跨年度行业代码对照、数据标准化或维度建模的开发者参考,可省去手工整理分类层级与代码映射的时间。

1. 从一份 rar 说起:2002/2011/2017 三版国民经济行业分类怎么落进 MySQL

手里拿到一个叫2002_2011_2017国民经济行业分类与代码mysql数据四级分类文件.rar的压缩包,第一反应往往不是解压,而是犯嘀咕:三个年份的国标分类,为什么有人要打包成一份 MySQL 数据?答案藏在业务里。做企业征信、税务开票、统计上报、供应链主数据治理的人都知道,行业分类代码是绕不开的字典表,而 2002、2011、2017 这三版恰好是近二十年影响最广的三次修订——2011 版把门类从 20 个压到 20 个但大类结构大改,2017 版又新增了若干新兴服务业大类。历史数据用老版、新系统用新版,做数据对齐时就得把三套码表同时装进库里做映射。这份 rar 的价值就在这:它把三个年份的四级分类(门类、大类、中类、小类)整理成可直接导入 MySQL 的结构化数据,省去你从 PDF 或 Excel 里手工扒的功夫。这篇笔记就按「拿到 rar 之后怎么建库、怎么导、怎么查、怎么避坑」的顺序讲清楚,适合做数据中台、主数据、报表开发的同行照着复现。

2. 先搞懂四级分类的编码结构,再决定表怎么建

2.1 国标编码的层级规则与长度差异

国民经济行业分类采用层次码,门类用一位大写字母表示(如 A 农林牧渔业、C 制造业),大类用两位数字,中类用三位数字,小类用四位数字。2017 版的完整小类代码形如C3912,其中 C 是门类、39 是大类、391 是中类、3912 是小类。2002 和 2011 版结构一致,但具体码值有增删改。这里有个容易翻车的点:门类是字母,其余层级是数字,如果建表时把code字段统一设成CHAR(5)没问题,但如果设成INT,门类那一位字母直接存不进去,导入时要么报错要么被截断成 0。我一般会把代码字段定为VARCHAR(10),留出余量,同时单独存一个level字段标记层级,避免每次查询都靠LENGTH(code)去猜。

2.2 三版并存时的表结构设计取舍

三版数据放一张表还是三张表?这是建库前必须拍板的。放一张表加version字段,好处是跨版本映射查询方便,坏处是索引选择性下降,且三版同名代码含义可能不同,容易误关联。分三张表(如industry_2002、industry_2011、industry_2017)结构清晰,但做版本对照时要写 UNION。我的做法是:主表按版本分三张,另建一张industry_mapping映射表专门存「2011 码 → 2017 码」的对应关系。下面给出建表语句,字段设计兼顾四级层级和版本隔离。

-- 单版本行业分类表,三版各建一张,此处以 2017 为例 CREATE TABLE `industry_2017` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键', `code` VARCHAR(10) NOT NULL COMMENT '行业代码,门类为字母,其余为数字', `name` VARCHAR(120) NOT NULL COMMENT '行业名称', `parent_code` VARCHAR(10) DEFAULT NULL COMMENT '父级代码,门类为 NULL', `level` TINYINT NOT NULL COMMENT '层级:1门类 2大类 3中类 4小类', `version` SMALLINT NOT NULL DEFAULT 2017 COMMENT '版本年份', `is_active` TINYINT NOT NULL DEFAULT 1 COMMENT '是否现行有效', PRIMARY KEY (`id`), UNIQUE KEY `uk_code` (`code`), KEY `idx_parent` (`parent_code`), KEY `idx_level` (`level`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='2017版国民经济行业分类四级码表';

这段建表语句里,code用VARCHAR(10)而不是CHAR(5),是因为部分历史版本存在带后缀的临时码;parent_code允许 NULL 是为了让门类记录能独立存在;level单独冗余存储,查询「所有大类」时直接WHERE level=2即可,比字符串截取快得多。utf8mb4字符集是必须的,行业名称里偶尔出现生僻字,用utf8三字节会在导入时报Incorrect string value。索引方面,uk_code保证同版本内代码唯一,idx_parent支撑树形递归查询,idx_level支撑按层级筛选。

2.3 从 rar 到 SQL:解压后先看清文件格式

解压 rar 后,常见的内容形态有三种:.sql转储文件、.csv/.xlsx数据文件、或者按年份分目录的多个文件。先别急着导入,用file命令或直接打开看头部几行,确认编码是 UTF-8 还是 GBK。国标数据从官方 Excel 转出来经常是 GBK,直接LOAD DATA会乱码。如果是.sql文件,检查它是否包含CREATE DATABASE和USE语句,避免导到错误的库。下面这段命令用于快速探查文件情况。

# 查看解压后目录结构 ls -lh ./国民经济行业分类/ # 探查文件编码,gb2312 或 utf-8 会直接显示 file -i ./国民经济行业分类/industry_2017.csv # 预览前 5 行,确认分隔符和表头 head -n 5 ./国民经济行业分类/industry_2017.csv

file -i输出的charset字段是关键,如果是iso-8859-1或gb2312,导入前必须用iconv转成 UTF-8,否则中文名称全是问号。head看的是分隔符,国标导出常用逗号或制表符,如果字段里本身含逗号(比如「农、林、牧、渔专业及辅助性活动」),就得用制表符或引号包裹,这直接决定后面LOAD DATA的FIELDS TERMINATED BY怎么写。

3. 把三版码表导进 MySQL:LOAD DATA 与批量 INSERT 两条路

3.1 用 LOAD DATA LOCAL INFILE 快速导入 CSV

数据量大时(三版合计约 4000 多条小类,加上中间层级近万行),逐条 INSERT 太慢,首选LOAD DATA LOCAL INFILE。前提是 MySQL 客户端和服务端都开启了local_infile。先确认参数,再执行导入。

-- 确认 local_infile 是否开启,ON 才能用 LOCAL 导入 SHOW VARIABLES LIKE 'local_infile'; -- 若为 OFF,在会话级临时开启(需服务端也允许) SET GLOBAL local_infile = 1;

服务端开启需要改my.cnf里的local_infile=1并重启,或者用mysql --local-infile=1启动客户端。导入语句如下,注意字段顺序要和 CSV 列对齐。

LOAD DATA LOCAL INFILE '/path/industry_2017.csv' INTO TABLE `industry_2017` CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (code, name, parent_code, level, version);

CHARACTER SET utf8mb4显式声明源文件编码,避免继承库默认值导致乱码;OPTIONALLY ENCLOSED BY '"'处理名称里含逗号的情况;IGNORE 1 LINES跳过表头。如果导入后level全是 0,说明 CSV 里没有这一列,需要导入后用UPDATE根据code长度回填:门类LENGTH(code)=1为 1,两位为 2,依此类推。这一步别偷懒,层级字段错了后面所有树形查询都废。

3.2 用 Python 脚本做清洗与批量 INSERT

如果 rar 里给的是 Excel 或格式不规整的文本,用 Python 做一层清洗再入库更稳。pandas读 Excel,pymysql批量插入,executemany比循环单条快一个数量级。

import pandas as pd import pymysql # 读取 Excel,指定 dtype 防止代码列被识别成数字丢失前导零 df = pd.read_excel('./industry_2017.xlsx', dtype={'code': str, 'parent_code': str}) df = df.fillna('') # 空父级填空串,入库时转 None conn = pymysql.connect(host='127.0.0.1', user='root', password='yourpass', database='industry_db', charset='utf8mb4') cursor = conn.cursor() sql = """INSERT INTO industry_2017 (code, name, parent_code, level, version) VALUES (%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE name=VALUES(name), parent_code=VALUES(parent_code)""" rows = [(r.code, r.name, r.parent_code or None, r.level, 2017) for r in df.itertuples()] cursor.executemany(sql, rows) conn.commit() print(f'导入 {cursor.rowcount} 行') cursor.close() conn.close()

dtype={'code': str}是血泪经验:行业代码0131这种,pandas 默认读成整数 131,前导零没了,和父级代码对不上。ON DUPLICATE KEY UPDATE让脚本可重复执行,重跑不会因唯一键冲突中断。parent_code or None把空串转成 NULL,和建表时的 NULL 语义一致。executemany一次提交,几千行秒级完成,比逐条execute快得多。

3.3 导入后的完整性校验

导完别急着用,先跑几条校验 SQL。检查各层级数量是否符合预期,检查孤儿节点(父级代码在表中不存在),检查代码长度和层级是否匹配。

-- 各层级数量分布 SELECT level, COUNT(*) FROM industry_2017 GROUP BY level; -- 孤儿节点:父级代码找不到对应记录 SELECT c.code, c.name, c.parent_code FROM industry_2017 c LEFT JOIN industry_2017 p ON c.parent_code = p.code WHERE c.parent_code IS NOT NULL AND p.code IS NULL; -- 层级与代码长度不匹配的异常记录 SELECT code, name, level FROM industry_2017 WHERE (level=1 AND LENGTH(code)<>1) OR (level=2 AND LENGTH(code)<>2) OR (level=3 AND LENGTH(code)<>3) OR (level=4 AND LENGTH(code)<>4);

第一条看分布,2017 版门类 20 个、大类约 97 个、中类约 473 个、小类约 1380 个,数量对不上说明导入漏行。第二条查孤儿,正常情况应为空,有结果说明父级代码写错或缺失。第三条查层级错位,是导入时列错位最常见的症状。这三条跑完没问题,码表才算真正可用。

4. 四级分类的树形查询与跨版本映射怎么写

4.1 用自连接查某大类下的全部小类

业务里最常见的需求是「给一个门类或大类,列出其下所有小类」。四级结构用三次自连接就能拉平,比递归 CTE 兼容性好(MySQL 5.7 不支持 CTE)。

-- 查询 C 制造业下所有四级小类 SELECT l4.code AS small_code, l4.name AS small_name, l3.name AS mid_name, l2.name AS big_name FROM industry_2017 l4 JOIN industry_2017 l3 ON l4.parent_code = l3.code JOIN industry_2017 l2 ON l3.parent_code = l2.code WHERE l2.parent_code = 'C' AND l4.level = 4 ORDER BY l4.code;

l4.parent_code = l3.code把中类和小类关联,l3.parent_code = l2.code把大类和中类关联,l2.parent_code = 'C'锁定门类。这种写法在parent_code和code都有索引时性能很好,万行级表毫秒返回。如果只要某一层,直接WHERE level=4 AND code LIKE 'C39%'更快,但前缀匹配依赖代码规则,跨版本时规则可能变,自连接更稳。

4.2 2011 与 2017 的映射表怎么建和怎么查

跨版本映射是这份数据最核心的价值。2017 版修订时,部分 2011 小类被拆分、合并或改名,映射关系有一对一、一对多、多对一三种。映射表设计要能表达这三种关系。

CREATE TABLE `industry_mapping` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `code_2011` VARCHAR(10) NOT NULL COMMENT '2011版代码', `code_2017` VARCHAR(10) NOT NULL COMMENT '2017版代码', `map_type` TINYINT NOT NULL COMMENT '1一对一 2一对多 3多对一', `remark` VARCHAR(255) DEFAULT NULL COMMENT '拆分合并说明', PRIMARY KEY (`id`), KEY `idx_2011` (`code_2011`), KEY `idx_2017` (`code_2017`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='2011到2017行业代码映射';

map_type字段让查询方能判断是否需要聚合。查一个 2011 代码对应哪些 2017 代码:

SELECT m.code_2011, m.code_2017, n.name AS name_2017, m.map_type FROM industry_mapping m JOIN industry_2017 n ON m.code_2017 = n.code WHERE m.code_2011 = '3912';

如果返回多行且map_type=2,说明该行业在 2017 版被拆成多个小类,做统计口径转换时要把金额按规则分摊或整体归入某一类,这个业务规则得和业务方确认,不能拍脑袋。反向查 2017 对应 2011 同理,把WHERE条件换成code_2017即可。

4.3 用递归 CTE 查任意节点的完整路径

MySQL 8.0 支持递归 CTE,查某个小类的完整四级路径比自连接灵活,不用预先知道层级数。

WITH RECURSIVE path AS ( SELECT code, name, parent_code, level, CAST(name AS CHAR(500)) AS full_path FROM industry_2017 WHERE code = 'C3912' UNION ALL SELECT p.code, p.name, p.parent_code, p.level, CONCAT(p.name, ' > ', path.full_path) FROM industry_2017 p JOIN path ON p.code = path.parent_code ) SELECT full_path FROM path WHERE parent_code IS NULL;

CAST(name AS CHAR(500))是必须的,递归 CTE 里字符串拼接会不断增长,不显式声明长度会报Data too long。UNION ALL从叶子往上找父级,最后parent_code IS NULL的那行就是门类,full_path即完整路径。这个查询在给报表加「行业全称」列时特别有用,一次查出「制造业 > 金属制品业 > 结构性金属制品制造 > 金属门窗制造」这样的路径,省去应用层多次查询。

5. 导入和使用中最容易翻车的五个坑

5.1 中文乱码:现象是名称显示问号,原因是字符集链路不一致

现象:导入后name字段全是???或乱码方块。原因:CSV 文件是 GBK,而表是 utf8mb4,LOAD DATA没指定源字符集,MySQL 按默认字符集解析。解决:导入前用iconv -f GBK -t UTF-8 source.csv > target.csv转码,或在LOAD DATA里加CHARACTER SET gbk。建库建表统一用utf8mb4,连接串也指定charset=utf8mb4,整条链路一致才不会翻车。

5.2 前导零丢失:现象是代码 0131 变成 131,原因是字段类型或读取方式把代码当数字

现象:导入后两位、三位代码的前导零没了,和父级代码对不上,树形结构断裂。原因:CSV 被 Excel 打开过并保存,或 Python 读取时未指定dtype=str,或建表时code用了INT。解决:建表用VARCHAR,Python 读取显式dtype={'code': str},CSV 导入时确认源文件未被 Excel 二次保存。已经丢零的,用LPAD(code, 期望长度, '0')回补,但前提是知道每行该有的长度。

5.3 孤儿节点:现象是树形查询查不到子级,原因是父级代码缺失或版本混用

现象:查某大类下的小类返回空,或完整性校验报出大量孤儿。原因:三版数据混在一张表里,2011 的父级代码在 2017 数据里不存在;或导入时漏了中间层级。解决:分版本建表,跨版本查询走映射表;导入后必跑孤儿校验 SQL,发现孤儿先查是漏行还是版本混用,漏行补导,混用则隔离。

5.4 LOAD DATA 权限报错:现象是 ERROR 1148,原因是 local_infile 未开或 secure_file_priv 限制

现象:执行LOAD DATA LOCAL INFILE报ERROR 1148 (42000): The used command is not allowed。原因:客户端或服务端local_infile为 OFF,或 MySQL 8.0 默认禁用 LOCAL。解决:客户端启动加--local-infile=1,服务端my.cnf设local_infile=1并重启。若用非 LOCAL 的LOAD DATA INFILE,文件必须放在secure_file_priv指定目录下,用SHOW VARIABLES LIKE 'secure_file_priv'查看路径。

5.5 版本字段写死:现象是同一代码在不同年份含义不同却查出错数据,原因是查询没带版本条件

现象:查代码3912返回的名称和预期不符。原因:三版数据同表时没加WHERE version=2017,命中了 2011 版的同名代码。解决:分表方案天然隔离;同表方案则所有查询必须带version条件,或在应用层封装查询方法强制传版本参数。这是最隐蔽的坑,数据看着对,口径已经错了。

6. 把码表用活:一个跨版本口径对齐的实战技巧

码表导入只是起点,真正体现价值的是跨版本统计口径对齐。我做过一个企业营收按行业汇总的报表,历史数据用 2011 码,新数据用 2017 码,直接合并会漏统或重统。我的做法是在映射表基础上建一个「口径桥接视图」,把 2011 小类统一折算到 2017 小类,一对多的按预设比例分摊,多对一的直接合并。下面这个视图把映射逻辑固化,应用层查询不用再关心版本。

CREATE VIEW v_industry_bridge AS SELECT m.code_2011, m.code_2017, n.name AS name_2017, m.map_type, CASE WHEN m.map_type = 1 THEN 1.0 WHEN m.map_type = 2 THEN 1.0 / (SELECT COUNT(*) FROM industry_mapping x WHERE x.code_2011 = m.code_2011) ELSE 1.0 END AS split_ratio FROM industry_mapping m JOIN industry_2017 n ON m.code_2017 = n.code;

split_ratio字段是一对多时的分摊系数,一对多拆成 N 个新类,每个分1/N。这个系数是简化处理,实际业务里拆分比例可能按营收分布定,那就把比例存进映射表的remark或单独字段,视图里读出来即可。多对一的情况split_ratio为 1,因为多个老类合并进一个新类,各自全额归入。用这个视图做汇总:

SELECT b.code_2017, b.name_2017, SUM(f.amount * b.split_ratio) AS amount_2017 FROM fact_revenue f JOIN v_industry_bridge b ON f.industry_code = b.code_2011 GROUP BY b.code_2017, b.name_2017;

这样历史数据自动折算到 2017 口径,和新数据 UNION 后口径一致。验证方法是拿几个已知拆分案例手工核对金额,比如某老类拆成三个新类,三个新类金额之和应等于老类原值(浮点误差除外)。我一般会跑一个总量校验:折算前后总金额差异应小于 0.01%,超过就说明映射表有遗漏或比例写错。

最后说个习惯:每次拿到新的国标码表,我都会先建一个_staging临时表导入原始数据,跑完层级、孤儿、长度三项校验,确认无误再INSERT INTO ... SELECT进正式表。多这一步,比事后修数据省心得多。希望帮到你。

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

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

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

立即咨询