资讯详情

资讯详情

省市县街道四级地区表:xlsx与SQL导入、联动查询与缓存实践

简介面向行政区划数据开发、后台管理系统搭建与地址清洗场景整理的四级地区表覆盖国内省、市、县、街道乡镇四级地址适合需要实现省市区街道联动选择、区域数据初始化或地址标准化处理的开发者与数据人员。压缩包共6个文件以xlsx表格和sql脚本为核心前者便于直接查看、筛选与比对后者可导入数据库快速建表另附png层级示意和txt说明整体约2.48MB体量轻便。表结构包含ID、父ID、名称、联动ID、层级及是否末级标识既能按父子关系还原树形结构也能借助联动ID完成路径匹配末级字段可用于判断是否已细化到街道乡镇。已有607人学习下载可作为级联下拉、区域编码映射和地址库搭建的基础数据参考。1. 四级城市地区表xlsx 与 sql 双份的省市县街道数据怎么落地产品在地址表单上加了第四级“街道/乡镇”下拉框的那一刻很多后端才意识到手里那份省市区三级的地区表不够用了。这里要拆的是一份四级城市地区表资源一份xlsx、一份sql把国内省市县街道乡镇四级地址按「名称、联动ID、层级、是否末级」四列摊平导入数据库就能直接驱动联动控件。它解决的是“数据从哪来、父子关系怎么存、前端怎么一级一级往下问”这三件事不需要自己再爬一遍、再对一遍重名区县。适合做后台管理系统、小程序地址选择、订单收货地址、以及需要做地址数据清洗的从业者如果你只是画个静态表单这份东西对你来说偏重。2. 拆开四列字段名称、联动ID、层级、是否末级各自管什么拿到一份地址表多数人第一反应是SELECT * FROM area WHERE parent_id ?就完事了。真正上线之后出问题的几乎都不是 SQL 写错而是对这四个字段的职责理解偏了拿名称当关联键、把层级写死在代码里、把“是否末级”当成可有可无的装饰列。这一章先把字段的边界划清楚再落到建表和父子关系补全后面几章的导入和查询都建立在这个结构上。2.1 四个字段的职责边界先把每个字段谁在用、能不能改、改了会波及什么列出来后面写代码时就不容易拍脑袋。字段建议类型取值与约束主要使用方名称varchar(64)文本同一父级下不应重复前端展示、模糊搜索、导出报表联动IDbigint 或 varchar(12)全表唯一一旦对外发布就不再变更前后端传参、父子关联、缓存 key层级tinyint1 省 / 2 市 / 3 县区 / 4 街道乡镇决定渲染第几个下拉、限定查询范围是否末级tinyint1 是 / 0 否前端判断还要不要继续发请求「名称」只负责展示这一点必须刻在脑子里。国内同名区县、同名街道并不罕见用名称做关联键短则联调时串数据长则线上出现“选了 A 市的某街道回填成了 B 市的同名街道”这种极难复现的问题。名称可以重复联动ID 不行所以任何跨表、跨接口的关联一律走 ID。「层级」的价值不只是告诉你这是第几级。它更大的作用是把查询范围锁死——同一个parent_id在不同层级下含义不同如果你的 ID 体系是自增式省级记录的 ID 和某个街道记录的 ID 完全可能撞车只按parent_id查会把别的分支捞出来。加上level ?这个条件等于给每次查询加了一道护栏代价是一个联合索引。「是否末级」是最容易被忽略、也最能省事的一列。前端渲染联动控件时如果不知道下一级还有没有数据常规做法是每次选择后都发一次请求返回空数组再把下拉框置灰。用户体验上是“点完之后卡一下再变灰”服务端则多了一堆必然返回空结果的查询。有了这一列前端拿到当前节点就知道要不要让用户继续点请求数能直接砍掉一截。还有一个细节名称里带不带“省/市/区/县/街道”后缀同一份数据里必须统一。有的数据源省级带“省”、市级不带“市”前端拼展示文案时就得写一堆判断。导入前统一清洗一遍比在三个端各写一套兼容逻辑划算得多。2.2 建表把约束和索引一次做对结构定下来之后建表语句尽量一次到位别指望后面再补索引——地址表通常是只读为主、查询频繁索引加晚了就是一次全表扫描的事故。CREATE TABLE area ( id BIGINT UNSIGNED NOT NULL COMMENT 联动ID全表唯一, parent_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 父级联动ID省级固定为 0, name VARCHAR(64) NOT NULL COMMENT 名称统一带后缀, level TINYINT UNSIGNED NOT NULL COMMENT 层级 1省 2市 3县区 4街道乡镇, is_leaf TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 是否末级 1是 0否, full_name VARCHAR(255) DEFAULT NULL COMMENT 全路径冗余字段可选, PRIMARY KEY (id), KEY idx_parent_level (parent_id, level), KEY idx_level_leaf (level, is_leaf), KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci;几个参数值得单独说一下。字符集用utf8mb4而不是utf8是因为地名里存在生僻字和异体字三字节的utf8存不下四字节字符导入时会直接报错或者被截断这类问题在测试库上往往因为数据量小而漏过去。id用BIGINT UNSIGNED如果联动ID 是分段编码式的长数字十二位以内完全放得下同时避免用字符串做主键带来的索引体积膨胀但如果你的 ID 里带字母或者有前导零语义就老老实实改成VARCHAR(12)别硬转。idx_parent_level这个联合索引的字段顺序不能反。查询永远是“给我某个父级下的某一级子节点”parent_id是等值条件且区分度高放前面level放后面做二次过滤。反过来建索引优化器大概率用不上。idx_level_leaf服务于另一类查询——预热缓存时按层级批量捞数据或者统计“有多少个末级街道”。full_name是可选冗余列存“省/市/区/街道”的完整路径。它不参与联动查询只在列表展示、导出、日志排查时省掉一次递归。要不要留这一列取决于你的业务里“把一条街道还原成完整地址”的频率频率高就留纯联动场景可以砍掉。2.3 只有一列联动ID时怎么补出父子关系资源里给的是「联动ID」但联动查询真正需要的是parent_id。如果你的 ID 是分段编码式前几位代表上级父子关系可以直接由前缀推出来如果 ID 是纯自增、彼此之间没有编码规律那就必须靠数据本身的顺序或者额外的一列父子关系来补——这种情况下不要自己猜先翻一遍源文件确认。分段编码式的补全逻辑用一段 Python 就能跑完关键在于每一级的编码长度必须和源数据核对过再用import pandas as pd # 每一级的编码前缀长度必须对照源文件实际编码规则核对后再用 PREFIX_LEN {1: 2, 2: 4, 3: 6, 4: 9} def fill_parent_id(df: pd.DataFrame) - pd.DataFrame: df df.copy() df[id] df[联动ID].astype(str).str.strip() # 省级没有父级统一写 0避免 NULL 参与后续比较 df[parent_id] 0 for lv in (2, 3, 4): child df[level] lv # 按本级前缀长度截断得到上一级的前缀 parent_prefix df.loc[child, id].str[: PREFIX_LEN[lv - 1]] # 用上一级记录的完整 ID 做映射表前缀 - 真实父级 ID upper df[df[level] lv - 1] mapping dict(zip(upper[id].str[: PREFIX_LEN[lv - 1]], upper[id])) df.loc[child, parent_id] parent_prefix.map(mapping).fillna(0) # 匹配不到父级的记录单独捞出来人工看不要静默丢弃 orphan df[df[parent_id] 0] if len(orphan): print(未能匹配父级的记录数, len(orphan)) print(orphan[[id, name, level]].head(20)) return df这段代码的逻辑是逐级向下建映射先把第 1 级的 ID 按前缀截成 key再拿第 2 级记录的前缀去查查到就把父级的完整 ID 填进去第 3、4 级同理。PREFIX_LEN是这个函数唯一的“外部知识”它必须来自你对源数据的核对写错了整棵树都会错位而且不会报错——只会安静地把街道挂到隔壁市下面。最后那段孤儿检查不能省。凡是匹配不到父级的记录要么是源数据本身有缺口要么是你的前缀规则不对无论哪种都必须人工确认而不是fillna(0)之后当作省级记录混进去。血泪经验是这类静默失败在数据量大的时候极难定位等到前端反馈“某个市的区县是空的”才回头查已经绕了很大一圈。3. 从 xlsx 到库两条导入链路和它们的参数数据进了库才算真正可用。这一步看着最无聊翻车率却最高——编码、类型推断、行尾符、local_infile开关每一个都能让你对着“导入成功 0 行”发呆半小时。下面按“先体检、再入库”的顺序走MySQL 和 SQLite 两条链路都给出来按你的部署形态选一条。3.1 导入前的三分钟体检不要拿到 xlsx 就直接to_sql。先用 pandas 读一遍把空值、重复 ID、层级分布、末级标记一致性这四项过一遍五分钟能省掉后面两小时。import pandas as pd # dtype 显式指定为字符串避免长数字被推断成浮点变成科学计数法也避免前导零丢失 df pd.read_excel(四级城市地区表.xlsx, dtype{联动ID: str}) print(总行数, len(df)) print(字段, list(df.columns)) print(各层级条数\n, df[层级].value_counts().sort_index()) print(ID 重复条数, df[联动ID].duplicated().sum()) print(名称空值条数, df[名称].isna().sum()) # 末级标记自检有子节点的记录不应标为末级没有子节点的记录应标为末级 has_child set(df[parent_id].astype(str)) df[_calc_leaf] (~df[联动ID].astype(str).isin(has_child)).astype(int) mismatch df[df[_calc_leaf] ! df[是否末级].astype(int)] print(末级标记与子节点不一致条数, len(mismatch)) print(mismatch[[联动ID, 名称, 层级, 是否末级]].head(10))dtype{联动ID: str}这一句是整段里最重要的。Excel 单元格如果被设成数值格式十位以上的 ID 会被 pandas 读成float再转字符串就变成1.10101e11这种东西导入之后全表 ID 全错而且错得很有规律很容易被误认为是源数据问题。末级标记自检那段是在做交叉验证把“出现过在别人 parent_id 里”的 ID 收集起来凡是出现过的理论上就不该是末级。不一致的记录不一定是错的比如某些城市确实没有第四级第三级本身就是末级但必须看一眼心里有数。如果这类记录数量很大说明源数据的末级标记口径和你理解的不一样先搞清楚再往下走。3.2 MySQL优先走 sql 文件其次走 LOAD DATA资源里已经带了 sql 文件最省事的做法是直接灌进去注意带上字符集参数# 指定 utf8mb4否则连接层会把四字节字符截断 mysql -h127.0.0.1 -P3306 -uroot -p \ --default-character-setutf8mb4 \ area_db 四级城市地区表.sql导入之后立刻验证条数别只看命令有没有报错SELECT COUNT(*) AS total FROM area; SELECT level, COUNT(*) AS cnt FROM area GROUP BY level ORDER BY level; SELECT COUNT(*) FROM area WHERE name IS NULL OR name ;如果 sql 文件里的表结构和你要的不一样字段名、类型、是否带full_name或者你只想导入部分层级那就走 xlsx 转 CSV 再LOAD DATA的路线可控性更好# 导出 CSV 供 LOAD DATA 使用 df_out df.rename(columns{ 联动ID: id, parent_id: parent_id, 名称: name, 层级: level, 是否末级: is_leaf, })[[id, parent_id, name, level, is_leaf]] # utf-8-sig 带 BOMExcel 打开不乱码MySQL 侧用 CHARACTER SET utf8mb4 消掉 BOM 影响 df_out.to_csv(/tmp/area.csv, indexFalse, encodingutf-8-sig, lineterminator\n)LOAD DATA LOCAL INFILE /tmp/area.csv INTO TABLE area CHARACTER SET utf8mb4 FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY LINES TERMINATED BY \n IGNORE 1 LINES (id, parent_id, name, level, is_leaf);CHARACTER SET utf8mb4写在LOAD DATA里是告诉 MySQL“这个文件是按 utf8mb4 编的”它覆盖客户端的默认字符集中文乱码十有八九是漏了这一行。LINES TERMINATED BY \n要和 pandas 侧的lineterminator\n对上——Windows 上默认导出的是\r\n导入后每行最后一个字段会多一个\r表现出来就是“is_leaf 明明填的 1查出来是 1 加一个不可见字符”这种坑排查起来非常费神。LOCAL关键字要求服务端开了local_infile同时客户端也要允许。连不上就先查这两个开关不要怀疑文件SHOW VARIABLES LIKE local_infile; -- 需要为 ON SHOW VARIABLES LIKE secure_file_priv; -- 非 LOCAL 导入时文件必须在这个目录下secure_file_priv是另一个高频卡点不加LOCAL的LOAD DATA INFILE只能读该参数指定目录下的文件你放在/tmp里会直接被拒报错信息还不太点题。3.3 SQLite单文件库更适合桌面端和本地检索如果这个地址表要跟着客户端一起分发或者你只是想在本地快速跑查询验证SQLite 比 MySQL 合适得多——一个文件、零依赖、可以直接只读挂载。import sqlite3 import pandas as pd df_db df.rename(columns{ 联动ID: id, parent_id: parent_id, 名称: name, 层级: level, 是否末级: is_leaf, })[[id, parent_id, name, level, is_leaf]] conn sqlite3.connect(area.db) # if_existsreplace 便于反复重建正式分发时改成一次性构建 df_db.to_sql(area, conn, if_existsreplace, indexFalse) # 索引在数据写入之后再建比边写边维护快 conn.execute(CREATE INDEX IF NOT EXISTS idx_parent_level ON area(parent_id, level)) conn.execute(CREATE INDEX IF NOT EXISTS idx_level_leaf ON area(level, is_leaf)) conn.commit() # 顺手验证一下四级是否都在 for row in conn.execute(SELECT level, COUNT(*) FROM area GROUP BY level ORDER BY level): print(row) conn.close()to_sql默认会把整张表包在一个事务里几万行以内问题不大条数再往上就建议分批写或者干脆先用csv导入再建索引。索引放在写入之后创建是因为批量插入时维护索引的开销远大于重建一次。打包分发时把数据库以只读方式打开能避免客户端误写sqlite3.connect(file:area.db?modero, uriTrue)。地址表本质上是字典数据只读是最合理的状态。4. 联动查询逐级取、全量建树、路径还原三种写法结构清楚了、数据进库了接下来是前端真正会打到的那几个接口。四级联动的查询形态其实只有三种点一级查一级、一次拉全量在内存里建树、以及从最末级反查完整路径。三种写法各有适用场景选错了不是错是慢。4.1 逐级查询把 level 带上把结果缓存住最常见的接口形态是“传父级 ID返回子级列表”SELECT id, name, level, is_leaf FROM area WHERE parent_id ? AND level ? ORDER BY id;level ?这个条件不是可有可无的。如果 ID 体系是自增的不同分支的 ID 空间是重叠的只按parent_id查理论上存在把其他层级记录捞出来的可能即便当前的 ID 体系不会撞带上level也能让优化器更稳定地走idx_parent_level。参数用占位符而不是字符串拼接地址接口的入参来自用户输入拼接就是给自己挖坑。ORDER BY id而不是ORDER BY name是因为按名称排序需要额外的拼音字段直接按name排出来的是字符编码序展示效果对中文不友好。要按拼音或者字母序展示正确做法是加一列sort_key存拼音首字母或全拼在导入阶段一次性算好而不是每次查询现算。省级查询比较特殊它的parent_id固定是 0SELECT id, name, is_leaf FROM area WHERE level 1 ORDER BY id;这类查询的结果几乎不变可以放心缓存。我一般的做法是按(parent_id, level)做 key 缓存子级列表省级和市级这种小结果集长时间缓存第三、四级的结果集大一些、但也基本不变缓存时间可以略短。地址数据不像订单它一年也未必改一次缓存的性价比很高。4.2 一次拉全量在内存里建树给前端如果前端是单页应用用户可能反复切换省市区逐级请求虽然简单但交互上有等待感。另一种做法是一次性把全量数据下发前端在内存里建树、本地联动体验最顺滑。import json from collections import defaultdict def build_tree(rows): rows 为 (id, parent_id, name, level, is_leaf) 元组列表返回嵌套结构 children defaultdict(list) for rid, pid, name, level, is_leaf in rows: children[str(pid)].append({ id: str(rid), name: name, level: level, leaf: bool(is_leaf), children: [], }) # 第二遍挂子节点先按 parent_id 分组避免在递归里反复扫描 for pid, nodes in children.items(): for node in nodes: node[children] children.get(node[id], []) return children.get(0, []) with sqlite3.connect(area.db) as conn: rows conn.execute( SELECT id, parent_id, name, level, is_leaf FROM area ORDER BY level, id ).fetchall() tree build_tree(rows) print(省级节点数, len(tree)) # ensure_asciiFalse 保证中文不被转义成 \uXXXX体积能小一大截 payload json.dumps(tree, ensure_asciiFalse, separators(,, :)) print(JSON 字节数, len(payload.encode(utf-8)))这里用两遍扫描而不是递归原因是数据本身已经是扁平的父子表按parent_id分组建索引之后再挂引用复杂度是线性的递归写法在层级固定为四层时也不会错但每层都要重新遍历一遍原始列表白白多做几次全表扫描。ensure_asciiFalse和separators这两个参数对下发体积影响很直接。默认的json.dumps会把所有中文转成\uXXXX一个汉字变六个字节体积翻好几倍。压缩传输当然也能解决但没必要先把自己撑大再压回去。全量下发适不适合取决于你的记录条数和首屏预算。估算方法很简单拿上面打印出来的 JSON 字节数除以 gzip 后的实际大小一般能压到三到四分之一再和你的首屏预算比一比。如果街道一级特别多导致体积超标退一步的做法是省市区三级全量、第四级按需请求——大部分用户的地址落不到街道这个折中通常够用。4.3 从街道反查完整路径订单详情、日志排查、导出台账这些场景需要的是“反查”手上只有一个最末级的联动ID要还原出“省/市/区/街道”。MySQL 8.0 及以上可以用递归 CTEWITH RECURSIVE up AS ( SELECT id, parent_id, name, level FROM area WHERE id ? -- 起点末级 ID UNION ALL SELECT a.id, a.parent_id, a.name, a.level FROM area a JOIN up ON a.id up.parent_id -- 向上找父级 WHERE up.parent_id 0 ) SELECT level, name FROM up ORDER BY level;递归的终止条件是parent_id 0也就是走到省级为止。这里要特别注意WHERE up.parent_id 0的位置——写在递归项里能正常停漏写就是无限递归MySQL 会直接报cte_max_recursion_depth超限。低版本 MySQL 或者 SQLite 老版本不支持递归 CTE用代码循环更稳妥def backfill_path(conn, leaf_id): 从末级 ID 逐级向上返回 [某省, 某市, 某区, 某街道] path, cur [], str(leaf_id) while cur and cur ! 0: row conn.execute( SELECT parent_id, name, level FROM area WHERE id ?, (cur,) ).fetchone() if not row: break # ID 不存在直接返回已拼出的部分 path.append(row[1]) cur str(row[0]) return list(reversed(path)) # 得到的是从下往上反转成从省到街道如果反查频率很高比如每次列表查询都要拼地址更好的办法是在导入阶段就把full_name算好写进表里。代价是存储多一列、导入时多一次遍历收益是查询时零递归。用一条自连接就能批量刷出来-- 前提表中已有 full_name 列且已按层级从小到大顺序存在 UPDATE area c JOIN area p ON c.parent_id p.id SET c.full_name CONCAT(p.full_name, /, c.name) WHERE c.level 2; -- 省级单独初始化父级为 0 的记录 full_name 就是自身名称 UPDATE area SET full_name name WHERE level 1;三级、四级重复上面的语句即可注意必须按层级从小到大依次执行否则父级的full_name还没算出来子级拼出来就是半截路径。这个依赖顺序是这类批量更新的唯一前提写脚本时要显式保证。5. 避坑与排查五条真实翻车记录地址表这类“看起来很简单”的数据出问题的地方往往不在业务逻辑而在导入和字符处理这些边缘环节。下面五条是我在实际项目里踩过或者帮人排查过的按“现象、原因、解决”记下来能省掉不少来回。5.1 五条高频故障的现象、原因与解决现象一导入后查询出来中文全是问号但用客户端工具打开表看着又正常。原因基本锁定在连接层字符集。表是utf8mb4文件也是 UTF-8但客户端的character_set_client还是latin1或gbk数据在传输过程中被转了一次。解决连接串显式带上character_set_clientutf8mb4命令行加--default-character-setutf8mb4LOAD DATA语句里补CHARACTER SET utf8mb4三处任缺一处都可能复发。现象二导入提示成功但总行数比源文件少了一截且少的集中在第四级。原因通常不是导入丢行而是源数据里本来就没有这部分街道——很多地区的第四级只给到部分城市或者第三级本身就是末级。先别改代码拿源 xlsx 的行数和库里COUNT(*)对一遍一致就说明数据本身如此。解决接受这个事实在业务侧用「是否末级」兜住——末级节点前端不再请求下一级用户看到的就是“选到区县为止”而不是一个永远空着的街道下拉。现象三前端选了市之后区县下拉一直是空白接口返回 200 但数据是空的。原因多半是level被写死在代码里比如区县查询固定传level 3而某些直辖或者特殊结构的记录层级并不是 3或者父级 ID 传的是名称而不是 ID。解决level不要写死在前端由接口返回的当前节点level 1决定父级参数一律传联动ID且入参做一次类型转换防止123和123在类型严格的语言里匹配不上。现象四两个不同城市的同名区县选了 A 的结果回填成 B。原因是用了name做关联或者做缓存 key。地址名称不具备唯一性这一点在数据量大的时候尤其明显。解决所有关联、缓存、埋点上报统一用联动ID如果历史代码里已经用了名称加一个(parent_id, name)的唯一性校验先把脏数据挡住再逐个改成 ID 关联。现象五xlsx 里的联动ID 变成1.10101E11导入后全表对不上。原因是 Excel 把这一列存成了数值型读出来是浮点转字符串就带上了科学计数法。解决读取时dtype{联动ID: str}如果源文件已经损坏让提供方把该列单元格格式设成文本重新导出导入后跑一次SELECT COUNT(*) FROM area WHERE id LIKE %E%兜底能立刻发现有没有漏网的。5.2 上线前的一张自检清单代码写完、数据导完别急着发。下面这张表里的项挨个打勾能挡掉绝大部分返工。检查项判断标准不过怎么办总行数与源 xlsx 行数一致查LOAD DATA的IGNORE行数与报错日志层级分布1~4 级都有记录且逐级递减缺第四级多为源数据本身如此确认后再放行ID 唯一COUNT(*) COUNT(DISTINCT id)有重复先回源查不要靠导入时去重末级一致有子节点的记录is_leaf 0按 3.1 的自检脚本重新刷一遍孤儿记录非省级记录都能找到父级见 2.3 的孤儿检查人工确认反查连通随机取 20 条末级 ID能还原出四级路径逐条比对源文件定位断在哪一级字符集生僻地名不报错、不乱码表、连接、导入三处统一 utf8mb4清单里最容易跳过的是「反查连通」。正向查询只要父子关系建对了就一定能查出来反查却依赖每一级的parent_id都正确是发现孤儿记录最直接的手段。随机抽二十条走一遍通常十分钟内就能暴露问题。6. 进阶把「是否末级」和缓存串成一条完整链路数据落地之后还有个容易被当成装饰的字段值得单独拿出来做点事is_leaf。它真正的价值不在于告诉你“这是最后一级”而在于让前端在不请求的情况下就知道该不该让用户继续点。把这个判断前置一次地址选择能少发两到三次请求交互上也就没有那种“点完卡一下再变灰”的顿感。我一般会把三级接口直接做成返回结构里带leaf布尔值前端拿到就渲染末级节点直接禁用下一级下拉而不是先发请求、拿到空数组再禁用。配合 4.2 里的全量建树效果更明显——整棵树一次性下发前端连第一次点击都不用等接口。缓存这块地址数据是典型的“读多写零”缓存策略可以放得很开。按(parent_id, level)做 key 缓存子级列表省级和市级长期缓存三四级给个较短的过期时间兜底即可。要注意的是缓存 key 里必须带level理由和查询里一样光靠parent_id不足以唯一定位一次查询。上线时如果做了数据更新记得按前缀批量失效别让新旧两版数据在缓存里混着。验证方法我习惯用两条一是数量校验各省记录数之和等于总行数各级记录数逐级递减且不会出现某一级为 0二是抽样走链随机取二十条末级 ID 反查到省逐条和源 xlsx 比对。前者能发现成片的导入问题后者能发现零散的父子错位两条一起跑基本覆盖了这份数据可能出的岔子。还有一个提速的小技巧把full_name在导入阶段算好之后前端展示和导出都可以直接用它省掉每次反查。代价是表宽了一点但地址表的行数摆在那里多一列的开销远小于每次查询都递归一遍。前提是计算顺序必须按层级从小到大这个依赖关系写进导入脚本的注释里免得下次有人重跑的时候顺序搞反。血泪教训是第一次做四级联动时我图省事用名称做关联测试环境数据量小、同名区县没撞上上线第二天客服就转来一堆“地址显示错了”的反馈回头改了两天。从那以后我每次接地址类数据导入前都强制走一遍 5.2 那张清单尤其是孤儿记录和反查连通这两条宁可多花十分钟也不想再经历一次线上回滚。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →