news 2026/9/26 1:49:41

全国省市县镇乡五级行政区划SQL:编码规则、导入落地与业务应用全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
全国省市县镇乡五级行政区划SQL:编码规则、导入落地与业务应用全攻略

简介:2018年最新全国省市、区、县、镇、乡五级行政区域完整SQL数据包,面向数据库开发者、GIS分析与数据分析人员,提供可直接落地的行政区划基础数据,解决地址选择、多级联动筛选、区域统计等场景下缺乏统一编码与层级关系的问题。压缩包内共2个SQL文件,主文件包含完整建表语句和全国五级行政单位明细,辅助文件可用于数据校验或补充导入,整体约8.63MB,轻量易导入MySQL等关系型数据库,适合快速初始化区域表并支持后续扩展。目前已有535人学习下载,尤其适合开发省市区县镇乡五级联动组件、区域数据看板或教学演示项目。数据覆盖地区编码、父地区编码、行政等级等关键字段,遵循国家标准编码规则,可直接用于地理信息展示、区域经济统计、物流配送区域划分、人口分布分析等场景,省去从零整理全国区划数据的繁琐工作,有效提升开发效率与数据准确性。

1. 全国省市、区、县、镇、乡5级完整SQL:这份数据到底解决什么问题

“2018最全国省市、区、县、镇、乡5级完整SQL最新版”这一类文件,不是一张普通的字典表,而是一套带行政区划代码的省、市、区县、镇乡层级主数据脚本。电商下单要五级联动地址,物流分拣要按区县划分路由,报表要按省市区县逐级汇总,缺了标准代码,光靠业务表里叫做 province、city、district 的字段名是撑不住的。这套SQL能直接解决三件事:新项目不用再手搓地址库;老系统可以把散落在各处的地址归一到统一代码;做数仓的人能拿它当维度表做层级下钻。适合快速搭后台地址库、维护存量订单地址清洗、做BI区域维度的三类从业者。下面按编码规则、建表选型、导入落库、业务场景和典型坑的顺序讲,目标是拿到当天就能用起来。

2. 五级区划的编码规则与建表选型:6位、9位和12位代码怎么存

2.1 区划代码的层级拆解:为什么代码比地名可靠

先把底层规则讲清楚,因为很多人在建表这一步就把 code 字段的类型选错了,后面全盘出错。标题里的“省、市、区、县、镇、乡”其实是五级:省是一级,市是一级,区县是一级(区、县、县级市、旗都算这一级),镇乡街道是一级,再往下还有村级,但这份标题说的“5级完整SQL”通常到镇乡为止。

国标 GB/T 2260 只覆盖到区县,6 位代码,例如省码 32、市码 3205、区县码 320505。镇、乡、街道这类基层单位不在国标里,而在国家统计部门公布的“统计用区划代码和城乡划分代码”里,编码是 9 位,规则是“区县6位 + 3位乡镇编码”,形如 320505001。再往下一级的村级是 12 位。所以一张表里要容纳 6 位、9 位、12 位三种长度,code 字段至少留到 CHAR(12)。

为什么强调“代码比地名可靠”?全国叫“城关镇”的镇有上百个,“建设路街道”在多个城市同时存在。名称必须带上完整的上级路径才能唯一,而 code 天然唯一。跨系统同步、报表 JOIN、接口传参,用 code 都不会产生歧义。地名只是给人看的,代码才是给机器用的。

2.2 自关联一张表还是五张表:按查询方式选

常见的建表方案有两种。方案 A 是省表、市表、区县表、镇乡表各建一张,字段清晰,后端同学一眼就能看懂。但做联动和任意级下钻时,要么跨表 JOIN 四次,要么在业务代码里写死多段查询。方案 B 是只建一张 region 表,字段为 code、name、level、parent_code,靠 parent_code 指向上级。

我一般选方案 B。原因很实际:行政层级并不是所有地方都完整。直辖市下面没有地级市这一层,省直辖县级市、直筒子市(如中山、东莞)下面也没有区县。自关联表里这类行的 parent_code 直接指向省或市,查询天然兼容跳级,不会因为层级缺失报错。另一个好处是查询模板统一:所有层级都是同一套“按 parent_code 取子级”的 SQL,只是参数不同,后端接口可以复用。

如果业务只关心某一个固定层级,比如只做省市区县四级下拉,方案 A 也够用。但只要涉及跨层统计、地址反查、历史区划回溯,自关联一张表省心得多。数据量到乡镇一级也就是三四万行的量级,一张表完全扛得住,没必要拆。

2.3 建表脚本与字段设计:主键、索引、字符集一次定对

原始 SQL 文件如果自带建表语句,优先看它给出的字段定义,再用这套标准去核对。很多流传的脚本建表比较随意,我一般会重建一张,把字段和索引按下面的方式定好:

CREATE TABLE region ( code CHAR(12) NOT NULL COMMENT '行政区划代码,6/9/12位', name VARCHAR(50) NOT NULL COMMENT '名称,不含省市县镇后缀', level TINYINT NOT NULL COMMENT '1省 2市 3区县 4镇乡街道', parent_code CHAR(12) DEFAULT NULL COMMENT '上级区划代码,一级为NULL', is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '0有效 1已撤销', valid_from DATE DEFAULT NULL COMMENT '生效日期', valid_to DATE DEFAULT NULL COMMENT '失效日期,NULL为当前有效', PRIMARY KEY (code), KEY idx_parent (parent_code), KEY idx_level (level), KEY idx_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五级行政区划表';

主键直接用 code,不用自增 id。省市区县的代码本身唯一且稳定,跨库同步、对接老系统时不会出现 id 映射错乱。parent_code 必须建索引,所有联动下拉都是“按父亲查孩子”,没有这个索引就是全表扫描。level 配合统计 GROUP BY 使用,name 的索引是给地址清洗用的模糊匹配场景。

短码存储是这套方案的核心:省码存 2 位,市码存 4 位,区县存 6 位,镇乡存 9 位,不做补零。这样每次查询结果更直观,CHAR_LENGTH 可以直接判断数据落在哪个层级。字符集用 utf8mb4,不要用 utf8,MySQL 的 utf8 是 utf8mb3,遇到生僻地名字会丢字符存成问号。数据样式大致是:省一行 code='32' name='江苏' level=1 parent_code=NULL;市一行 code='3205' name='苏州' level=2 parent_code='32';区县 code='320505' name='虎丘' level=3 parent_code='3205'。

3. 把SQL脚本落到MySQL与SQL Server:导入、验证、下钻查询

3.1 导入前的文件体检:编码、分隔符与大字段限制

拿到 SQL 文件,第一步不是双击执行,而是体检。我先检查三样东西:文件编码、文件头部有没有 DROP TABLE、有没有超长 INSERT。

file region_2018.sql head -n 30 region_2018.sql grep -c ';$' region_2018.sql

file 命令看编码,head 看前面几十行有没有 DROP TABLE。很多老脚本开头会直接 DROP TABLE IF EXISTS,你要是跑在已经有业务数据的库上,表会被整个清掉。grep 统计分号数量,后面和导入后的行数做对比,可以判断有没有半截入库。

另一个隐性问题是大 INSERT。有的脚本把几千行 VALUES 并成一条 INSERT 语句,比如“INSERT INTO region VALUES (...),(...),...”这种写法。MySQL 默认的 max_allowed_packet 是 4MB,一条超大 INSERT 会直接报“Packet too large”。先确认当前值和文件大小:

mysql -uroot -p -e "SHOW VARIABLES LIKE 'max_allowed_packet';" ls -lh region_2018.sql

如果文件超过几十 MB 或者用了合并 INSERT,导入前把限制调大。运行时调整加配置文件持久化两件事都要做,不然 MySQL 一重启又回到默认值。

SET GLOBAL max_allowed_packet = 256 * 1024 * 1024;

3.2 source导入MySQL与行数核对

体检没问题后开始导入。推荐直接用 mysql 客户端的重定向方式:

mysql -uroot -p --default-character-set=utf8mb4 --max_allowed_packet=256M < region_2018.sql

--default-character-set=utf8mb4 必须显式指定。客户端默认字符集可能不是 utf8mb4,文件里的中文在传输过程中会被错误转码,导入后全是乱码。--max_allowed_packet 这里再一次指定,双保险。

导入完成后不要直接上线,先做行数核对:

USE biz_db; SELECT level, COUNT(*) AS cnt FROM region GROUP BY level; SELECT code, name, parent_code FROM region LIMIT 10;

正常的行数量级是:省级三四十行,市级三百多行,区县两三千行,乡镇三四万行。如果 level=4 只有几百行,说明导入被截断了,典型表现是文件尾部的 INSERT 因为报错没执行。这时 DROP 掉重导,不要原地补数据,原地补很难保证没有漏行。

3.3 五级下钻查询:自连接、递归CTE与逐级查的取舍

落库之后最常用的查询就两类。第一类是联动下拉,按 parent_code 取子级:

SELECT code, name FROM region WHERE parent_code = '320000' ORDER BY code;

注意这里有个细节:如果原始文件里一级省份的 parent_code 存的是空字符串而不是 NULL,查询条件会不统一。导入后先跑一条更新,把空串统一改成 NULL,省得每条 SQL 都写 WHERE parent_code = '' 这种脏判断。

第二类是已知一个基层 code,反查出它的省市区县完整路径。五级层级固定,用自连接最直白:

SELECT p.name AS province, c.name AS city, d.name AS district, t.name AS town FROM region t JOIN region d ON t.parent_code = d.code JOIN region c ON d.parent_code = c.code JOIN region p ON c.parent_code = p.code WHERE t.code = '320505001';

每加一级就多一次 JOIN,执行计划可控,索引能走 idx_parent,性能没问题。不建议一上来就用递归 CTE 炫技,层级固定时自连接的可读性最好,后来维护的人不会骂人。

递归 CTE 真正有用的场景是“任意节点到根路径拼接”,比如给定一个镇,把省市区镇拼成一段完整地址。MySQL 8.0 和 SQL Server 都支持 WITH RECURSIVE:

WITH RECURSIVE r AS ( SELECT code, name, parent_code, level, name AS path FROM region WHERE level = 1 UNION ALL SELECT child.code, child.name, child.parent_code, child.level, CONCAT(r.path, '>', child.name) FROM region child JOIN r ON child.parent_code = r.code ) SELECT * FROM r WHERE code = '320505001';

递归写法的优点是路径拼接只写一次,缺点是递归层数不受控、全量展开时有额外的内存开销。实际项目里我一般把递归结果缓存成一张路径表,避免每次查询现场递归。

3.4 在SQL Server里导入的兼容处理:反引号、BOM与GO

同一个 SQL 文件拿到 SQL Server 里跑,最常见的翻车点是三个。第一是中文乱码,SSMS 打开一个无 BOM 的 UTF-8 文件时,中文几乎必乱。解决办法是用编辑器把文件另存为“带 BOM 的 UTF-8”,SSMS 才能正确识别。第二是反引号,MySQL 习惯用反引号包标识符,SQL Server 不认,批量替换成空字符串即可。第三是建表语句尾巴上的 ENGINE=InnoDB DEFAULT CHARSET=utf8mb4,SQL Server 不认识,删掉或换成分号。

SSMS 直接打开文件执行的话,还要把文件里的批量 INSERT 拆成适合 SQL Server 的批次。SQL Server 本身对单条超大 INSERT 没有 MySQL 那种 packet 限制,但 SSMS 的默认执行超时和内存占用会卡在大事务上。我一般先把文件里的 INSERT 语句过一遍,确认是不是一条语句包含几千行 VALUES,是的话就拆成几百行一批,靠事务保证原子性。SQL Server 2008R2 到 2022 这套处理逻辑都适用,差异只在窗口函数和递归 CTE 的支持,导入阶段用不到。

4. 业务场景怎么用:地址清洗、联动下拉与层级聚合

4.1 地址清洗:按名称反向定位省市区县

订单表里存的是“江苏省苏州市虎丘区枫桥街道XX路”这种自由文本,要抽出标准省市区县,就得拿 region 表倒着匹配。不能直接拿整串地址和 name 比对,因为重名太多,比如“枫桥”在苏州有、在别的城市也可能有。我的做法是先匹配最具体的层级,命中后再顺着 parent_code 回溯上级。

SELECT r.code, r.name, r.level, p.name AS province, c.name AS city, d.name AS district FROM region r LEFT JOIN region d ON r.parent_code = d.code LEFT JOIN region c ON d.parent_code = c.code LEFT JOIN region p ON c.parent_code = p.code WHERE INSTR('江苏省苏州市虎丘区枫桥街道XX路', r.name) > 0 AND r.level = 4 ORDER BY CHAR_LENGTH(r.name) DESC LIMIT 1;

INSTR 判断地址文本里是否包含镇级名称,ORDER BY 按名称长度倒序,是为了优先命中更长、更具体的名称,避免“江苏”和“江苏省”同时命中时取到短的那个。匹配到镇级 r 之后,沿 parent_code 向上回溯区县、市、省,一路都是等值 JOIN,索引稳定。

这条 SQL 的性能没问题,但不要在用户点击查询的接口里跑。地址清洗是重活,应该在离线 ETL 或者写入订单时做一次,把解析出的省市区县 code 落到订单表的标准字段里。用户的地址文本是外部输入,拼这种 LIKE 或 INSTR 查询时用参数化,不要直接拼接字符串,不然等于把 SQL 注入的洞开在线上。

4.2 联动下拉:全量缓存与按parent_code取子级

省市区县下拉联动,接口背后的 SQL 就是前面那句“SELECT code,name FROM region WHERE parent_code=?”。但我不建议每一级下拉都打一次数据库,尤其是高并发的前台页面。区划数据整体也就三四万行,应用启动时一次性全量加载到内存是常见做法。

SELECT code, name, parent_code, level FROM region ORDER BY code;

加载后按 parent_code 分组,构建一个“父级代码 -> 子级列表”的 Map。Java 里大概是这样的逻辑:

Map<String, List<Region>> childrenMap = regions.stream() .collect(Collectors.groupingBy( r -> r.getParentCode() == null ? "" : r.getParentCode() )); // 取江苏省下辖市级列表 List<Region> cities = childrenMap.getOrDefault("32", Collections.emptyList());

parent_code 为 NULL 的一级省份统一归到空字符串键下,避免 Map 的 null key 带来空指针和 get 时的歧义。数据量到乡镇级也就几 MB 内存,换来的是一次数据库查询全搞定,后端接口只做 Map 读操作。更新策略很简单:区划数据一年变不了几次,应用发布时顺带刷新缓存就行。如果非要做成实时更新,就在导入后主动清一次 Redis 或应用本地缓存,不要让客户端等 TTL 过期。

4.3 层级聚合报表:JOIN region与订单表冗余两种路线

报表要按省汇总订单量,订单表里只存了区县 code,就需要从区县一路 JOIN 到省:

SELECT p.name AS province, COUNT(*) AS order_cnt FROM orders o JOIN region r ON o.region_code = r.code JOIN region c ON r.parent_code = c.code JOIN region p ON c.parent_code = p.code WHERE o.created_at >= '2025-01-01' GROUP BY p.code, p.name;

orders 表一上百万行,这个查询的瓶颈不在 JOIN 本身,而在 orders.region_code 有没有索引。最有效的优化是在订单表上建一个组合索引 (region_code, created_at),让过滤条件下推到索引树里,避免先全表扫再 JOIN。跑之前用 EXPLAIN 看一眼:

EXPLAIN SELECT /* 同上SQL */;

执行计划里 type 至少要是 ref 或 eq_ref,出现 ALL 就说明索引没走到,先解决索引再谈优化。

另一个更彻底的办法是订单表直接冗余省市区县名称和 code。写入订单时就把地址解析成标准区划,省、市、区县三个 code 落成三个字段,报表查询零 JOIN,直接从 orders 表 GROUP BY。代价是写入时多几条更新语句,换来的是查询端永远不用碰 region 表。订单量到千万级以后我基本都走这条路,维度表只用于后台管理端的维护和展示。

5. 导入区划SQL的5个典型坑:现象、原因与解决方案

5.1 导入后中文全部乱码,部分行缺失

现象:SELECT 出来中文全是问号或者乱码,行数比预期少,文件靠后的数据没进来。

原因有两个。一是文件是 UTF-8,但客户端连接字符集是 GBK,MySQL 在传输层做了错误转码。二是文件里含超大 INSERT,超过了 max_allowed_packet,连接被断开,后面的语句全部没执行。

解决:导入命令里显式加 --default-character-set=utf8mb4,导入前用SHOW VARIABLES LIKE 'character_set%'核对服务端、客户端、连接三处字符集一致。被截断就别补导,DROP 掉表重新来一遍,补导很容易漏掉中间行。

5.2 直辖市和省直辖县让“五级”变“四级”

现象:按“省->市->区县”下钻,北京、上海这些直辖市少了市这一层;查 level=2 的市列表,出来一堆省直辖县级市。

原因:行政区划本身就不是所有地方都满五级。直辖市下辖区直接挂省,省直辖县级市直接挂省,直筒子市下面没有区县。这是数据的事实,不是脚本的 bug。

解决:把联动逻辑从“固定省->市->区县”改成“按 parent_code 取子级”,用自关联表天然兼容跳级。写统计 SQL 时也注意,东莞这类城市订单可能直接挂在市级,汇总省报表时 JOIN 链会少一层,COUNT 别算重。

5.3 用INT存区划代码:前导0消失

现象:报表关联不到区划名称,查 code 发现本该是六位的区划代码变成了四位数或五位数。

原因:建表时把 code 字段定义成了 INT,区划代码里的前导 0 被数值类型吃掉。市面上流传的脚本里,直辖市辖区、部分短编号会出现以 0 开头的代码,INT 一存就废。

解决:code 字段一律 CHAR(12) 或 VARCHAR(12),查询参数、Java 实体、接口传参全部用字符串接收,不要用 Long 或 Integer 承接之后再去查。凡是遇到“区划代码查不到”的报错,先怀疑是不是 0 丢了,SELECT CHAR_LENGTH(code) 看一下长度分布就能定位。

5.4 2018版区划与当前业务口径不一致

现象:2018 年还是“县”的地方现在已经撤县设区,老 code 在新片区里查不到;新注册地址用的新区划在老表里没有对应行。

原因:区划调整一直在发生,撤县设区、乡镇合并、街道析置年年都有。2018 年这套数据到了今天必然有部分失效,这是时间造成的,不是数据质量问题。

解决:历史区划不要删,也不要原位 UPDATE 覆盖。给 region 表加上 valid_from、valid_to、is_current 三列,新版数据以增量方式导入,旧行标记 is_current=0。关键原则是报表维度对齐订单发生时间:订单存的是下单当时的 code,就要用当时的区划版本去 JOIN,不能用最新版回溯历史订单,否则会出现“历史订单挂在已撤销乡镇上”的错配。

5.5 重复数据与同名不同码:去重只认code不认name

现象:SELECT COUNT(*) 和 SELECT COUNT(DISTINCT code) 不一致,下拉列表里同一个名称出现两遍。

原因:一部分是脚本本身带了重复 INSERT,另一部分是你导入时用了没有主键约束的表,重复数据没有被挡掉。还有一种情况是名称相同但 code 不同,比如多个城市的“城关镇”,这本身是合法数据。

解决:导入前先按 code 查重:

SELECT code, COUNT(*) FROM region GROUP BY code HAVING COUNT(*) > 1;

查出重复后用窗口函数保留一条,写入临时表再删除多余行:

CREATE TABLE tmp_region AS SELECT code, name, parent_code, level, ROW_NUMBER() OVER (PARTITION BY code ORDER BY id) AS rn FROM region; DELETE r FROM region r JOIN tmp_region t ON r.id = t.id WHERE t.rn > 1;

如果表本来就以 code 为主键,重复 INSERT 会在导入阶段报错中断,不会造成重复数据,那这条可以直接跳过。但“同名不同码”是常态,查询时不要拿 name 当关联键,name 只能用于展示和地址清洗的模糊定位。

6. 导入完成之后:验证代码完整性、留好版本更新的后路

6.1 三分钟完整性校验:父子闭环与层级数量

导入完别急着接业务,先跑三个校验。第一个是父子闭环,所有非一级记录的 parent_code 必须能在表里找到,有孤儿记录说明数据有断档:

SELECT COUNT(*) FROM region r LEFT JOIN region p ON r.parent_code = p.code WHERE r.level > 1 AND p.code IS NULL;

第二个是校验 code 长度和 level 是否匹配,省码 2 位、市码 4 位、区县 6 位、乡镇 9 位,这一步能发现原始文件里某些行是否缺失了层级信息:

SELECT level, MIN(CHAR_LENGTH(code)), MAX(CHAR_LENGTH(code)) FROM region GROUP BY level;

第三个是行数量级对比,省级三四十行、市级三百多、区县两三千、乡镇三四万,差距超过量级就要怀疑导入被截断。

6.2 版本管理与更新:给区划数据留后路

这套数据不是导一次就完事。我现在的习惯是建一张 region_version 表,记录每次导入的版本号、来源文件名、导入时间和校验和。原始 SQL 文件归档保存,文件名带上日期和口径,不要叫“最新版”,要叫“2018_statistical_version.sql”这种能追溯的名字。

后续更新走 staging 流程:新文件先导入一张 region_staging 临时表,跑一次新旧对比,明确增量是什么、废掉了哪些、改名的有哪些,再正式写主表并把旧行标记失效。对比 SQL 基于 code 做外连接,能直观看出变更集:

SELECT '新增' AS change_type, s.code, s.name FROM region_staging s LEFT JOIN region r ON s.code = r.code WHERE r.code IS NULL UNION ALL SELECT '废止' AS change_type, r.code, r.name FROM region r LEFT JOIN region_staging s ON s.code = r.code WHERE s.code IS NULL;

我第一次做这套数据时,直接在订单表上 JOIN 没加索引,慢查询日志半天刷屏;后来把维度版本化、code 改成 CHAR、连接走索引,报表才稳定下来。区划数据看着简单,处理不当就是脏数据的源头,花半天时间把这两件事做好,后面能省很多排查的功夫。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 1:48:46

EDU教育邮箱申请全攻略:5分钟搞定JetBrains、GitHub学生认证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:48:29

I2C多主机仲裁与时钟延展:从电气原理到驱动调试实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:48:20

异步RL与Agentic信用分配:大模型强化学习训练成本透明化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:48:20

Word多级列表编号错乱的根因与修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 1:48:04

Edge浏览器隐藏冲浪游戏:edge://surf入口、玩法与HTML5技术解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华