
简介面向地理信息系统开发、物流配送与地图服务等场景整理的一份全国行政区划邮编与经纬度坐标数据集覆盖省、市、区、县、乡、镇、街道各级区划合计约46090条记录可支撑地址解析、配送路径优化、区域市场细分、人口分布研究以及灾害定位等实际需求。压缩包内共1个文件为单个 SQL 脚本体积约942KB导入后即可按表结构查询各级区划名称、邮政编码及对应经纬度字段便于与业务库关联或做二次清洗。已有796人学习下载反映出中小型 GIS 项目与数据教学场景对基础地理数据的持续需求。数据以结构化表形式组织每行对应一个行政区域字段边界清晰读者可直接用 SQL 完成按省份聚合、坐标范围筛选、邮编反查等操作也可作为地图打点与地址库校验的底表省去零散采集、逐级核对与坐标纠偏的重复投入。1. 从一条对不上的收货地址说起五级区划 邮编 坐标这张表为什么值得自己建前阵子帮一个做同城配送的团队查下单失败日志里约 3% 的订单卡在地址解析这一步用户填的是「甲省乙市丙区丁街道」系统返回「未匹配到网点」。再查下去问题不在解析算法而在底层那张表区划快照还是三年前的两个县已经撤县设区邮编字段一大半是空的门店坐标是运营在地图上手工点选的实测偏了一公里多骑手按导航直接跑进隔壁小区。「全国省、市、区、县、乡、镇、街道邮编及经纬度坐标」这件事真正难的地方从来不是找一份数据下载下来而是把四层行政区划、一层村居、一套六位邮编、一套坐标系拼成一张能支撑地址解析、网点匹配、运费计算、报表归集的可信底表。它决定了你后面所有按行政区聚合的数字对不对得上。适合做本地生活、物流仓储、零售选址、政务数据归集的后端和数据同学也适合需要按行政区口径出报表的分析岗。2. 五级区划表怎么设计12 位代码做主键、邮编单独挂表绝大多数人的第一版都是把一份现成的表格导入数据库字段对齐、类型对上、能查出来就觉得完事了。真正出问题是在三个月后业务方要按街道统计单量你发现街道这一级根本没有稳定主键财务要按县归集你发现历史订单里的县名和现在的县名对不上。表设计阶段的偷懒后面要用几倍的清洗成本还回来。2.1 只用 12 位区划代码做主键名称永远当别名区划代码是分层拼出来的前 2 位省级第 3-4 位市级第 5-6 位县级第 7-9 位乡镇街道第 10-12 位村/社区。实际拿到的数据长度经常是混的——省级表给 2 位、市级表给 4 位、县级 6 位、乡镇 9 位、村居 12 位。入库第一件事就是统一右补零到 12 位之后所有连接、比较、前缀查询全部在这个 12 位口径上做。名称绝对不能当主键也不能当连接键。原因很实在全国叫「城关镇」的乡镇能凑出几十个叫「朝阳区」的区不止一个重复率高得离谱而且区划名称本身会变撤县设区、乡改镇、街道合并名称一变历史数据里的外键全断。代码相对稳定即使区划调整通常也是「旧代码失效 新代码启用」而不是原地改代码。CREATE TABLE ad_div ( code CHAR(12) PRIMARY KEY, -- 统一右补零到 12 位 parent_code CHAR(12) NOT NULL, -- 上级代码省级填 000000000000 level SMALLINT NOT NULL, -- 1省 2市 3县 4乡 5村居 name VARCHAR(64) NOT NULL, -- 标准全称 short_name VARCHAR(64), -- 去掉省/市/县/区/镇后缀的简称 is_placeholder BOOLEAN DEFAULT FALSE, -- 是否为市辖区这类占位节点 valid_from DATE NOT NULL, -- 生效日期快照口径 valid_to DATE, -- 失效日期NULL 表示现行 lng NUMERIC(10,6), -- 内点经度WGS84 lat NUMERIC(10,6), -- 内点纬度WGS84 geom GEOMETRY(MultiPolygon, 4326) -- 面边界仅到乡镇街道级 ); CREATE INDEX idx_ad_div_parent ON ad_div (parent_code); CREATE INDEX idx_ad_div_level ON ad_div (level) WHERE valid_to IS NULL; CREATE INDEX idx_ad_div_geom ON ad_div USING GIST (geom);is_placeholder这个字段是被坑出来的。市级节点里有一类叫「市辖区」的占位记录它不是一个真实的地理实体只是为了让层级完整而存在如果你把它当成一个真实的市去做聚合统计出来的数字会莫名其妙多一层。同理还有个「县」占位。这类节点在做报表时必须过滤掉但在做树形结构展示时又必须保留所以只标记、不删除。valid_to是双时态设计的核心。同一份数据里会同时存在「现行的丙区」和「三年前的丙县」两个 code 不同、valid_to一空一有值。业务查询默认带valid_to IS NULL历史对账则按订单发生日期落在valid_from和valid_to之间去匹配。没有这个字段撤县设区那一年你所有同比数据都是错的。2.2 层级归属邻接表存事实路径字段存索引层级关系的存法无非两种。邻接表就是上面那个parent_code只存父节点结构最干净改层级时只动一行路径枚举是把整条路径拼成字符串存在一列里比如100000000000/100100000000/100101000000查子树时一个LIKE 1001%就搞定不用递归。只存邻接表查「某市下所有区县的所有街道」要写递归 CTE数据量一大就慢只用路径一旦中间某一级发生调整比如某个县从甲市划到乙市整棵子树的路径串都要重写维护成本高。我一般的做法是两个都存parent_code作为事实来源保证正确性另外加一个物化的path字段专门扛查询。-- 用递归 CTE 从省级一直展开到街道顺便把路径拼出来 WITH RECURSIVE tree AS ( SELECT code, parent_code, level, name, code::text AS path FROM ad_div WHERE code 100000000000 -- 起点某个省 UNION ALL SELECT c.code, c.parent_code, c.level, c.name, t.path || / || c.code FROM ad_div c JOIN tree t ON c.parent_code t.code WHERE c.valid_to IS NULL -- 只展开现行区划 ) SELECT level, code, name, path FROM tree WHERE level 4 ORDER BY path;这段的关键参数是level 4只展开到乡镇街道村居那一级动辄几十万行绝大多数业务用不上展开进去只会把结果集撑爆。path用code拼接而不是用名称是因为code定长排序天然就是层级顺序拿来做前端树形组件的数据源无需二次排序。有一个反直觉的点要提醒code LIKE 1001%这种前缀查询看起来能直接代替递归但遇到省直辖的县级行政单位就会断——它的市级两位并不是常规的递增序列前缀规则不成立。所以前缀只能当索引用判定层级归属还是要老老实实看parent_code和level。2.3 邮编是投递局维度不能当行政区划的属性新手最容易犯的错是给区划表加一列zip_code然后填一个值进去。跑起来没报错等业务方拿用户填的六位邮编来做校验全线崩。六位邮编的构成是前 2 位对应邮区大致对齐省级第 3 位细化到邮区内的分拣中心第 4 位对应县市级投递范围后 2 位是具体的投递局。它的划分依据是邮政网络的投递路径不是行政区划边界。所以现实里是一对多的一个县级单位往往对应好几个邮编尤其面积大的县反过来一个邮编也可能横跨两个乡镇乡镇合并之后这种情况特别多。正确做法是单独一张映射表把邮编挂在它能覆盖到的最细层级上并标注优先级和来源。CREATE TABLE ad_zip ( zip CHAR(6) NOT NULL, code CHAR(12) NOT NULL, -- 绑定到 9 位乡镇街道或 12 位村居 priority SMALLINT DEFAULT 9, -- 数字越小越优先1村居 5乡镇 9县级兜底 source VARCHAR(32), -- 数据来源标记便于回溯 PRIMARY KEY (zip, code) );查询时的策略是「由细到粗、取到即止」先按村居级精确匹配命中就直接用没命中退到乡镇街道级还没有就退到县级返回该县下出现频次最高的邮编作为建议值。priority字段就是给这个回退逻辑用的别指望调用方自己判断。3. 数据从哪来、怎么搓到一起三源交叉与乡镇街道补全区划数据不是一个来源能搞定的。每年都有一批区划调整公告任何一份公开数据都有滞后同时坐标和边界又不可能从代码表里拿。实际干下来比较稳的组合是三类源头交叉代码快照做骨架地理数据做血肉自有业务样本做校验和别名。3.1 三类数据源的取舍代码表定骨、地图数据补形、地址样本补别名第一类是年度区划代码快照通常是民政或统计口径发布的表格。它的优势是权威、层级完整、代码稳定缺点也很明确只有名称没有坐标字段极简而且更新频率是年度的年中调整得等到下一年才反映出来。这张表在整条链路里定的是主键和父子关系不能动摇。第二类是地图服务提供的行政区划边界和逆地理编码接口。它有面、有坐标、能算距离但名称口径和代码表经常对不上——同一地方一边叫「某某自治县」一边叫「某某县」边界数据做过抽稀简化沿海和一些复杂边界的形状会失真。它负责补geom和lng/lat不能负责定主键。第三类是自有业务里沉淀的地址样本比如历史订单、门店登记、用户填写的收货地址。这东西看着脏价值却最高它反映真实书写习惯能挖出「某某开发区」「某某新区」这类不在标准区划表里但用户天天在用的别名也能反查覆盖率——你那三十多万条区划记录里到底有多少是真的被业务用到的。三类数据合起来的原则只有一句连接只认代码名称只用来比对和建别名。用名称做 join 是这类项目里最经典的翻车点merge 之后行数从三十万涨到四十多万还查不出哪条错了。3.2 用 Python 把三种格式拉齐成一张宽表原始数据通常是三种形态代码表是 CSV 或 XLSX边界是 GeoJSON 或 Shapefile地址样本是数据库导出的文本。第一步统一成 DataFrame第二步把代码列统一成 12 位字符串第三步按代码左连接。import re import pandas as pd def norm_name(s: str) - str: 全角转半角、去空白、统一括号只用于比对不覆盖原值 if pd.isna(s): return s str(s).strip() s s.translate(str.maketrans(, 0123456789())) s re.sub(r\s, , s) return s # 三份源区划代码表、地图边界属性表、业务地址样本 div pd.read_csv(div_code_snapshot.csv, dtype{code: str}) geo pd.read_csv(geo_admin_attrs.csv, dtype{adcode: str}) addr pd.read_csv(addr_samples.csv, dtypestr) # 关键一统一右补零到 12 位杜绝 6/9/12 位混用 div[code] div[code].str.strip().str.ljust(12, 0) geo[adcode] geo[adcode].str.strip().str.ljust(12, 0) # 关键二只按 code 连接并强制 1:1行数一膨胀立刻报错 wide div.merge( geo[[adcode, lng, lat, boundary_wkt]], left_oncode, right_onadcode, howleft, validate1:1, indicatorTrue ) # 关键三统计坐标覆盖率低于阈值说明地图源的口径对不上 hit (wide[_merge] both).mean() print(f坐标命中率: {hit:.2%}) assert hit 0.95, 命中率过低先检查 adcode 口径而不是继续往下跑逻辑说明几个点。ljust(12, 0)而不是zfill是因为区划代码的高位有实际含义补零只能补在低位zfill补在左边会把1001变成000000001001语义直接反了。validate1:1是这套流程里最值钱的参数它会在连接键重复时直接抛异常而不是默默做笛卡尔积——少了它你很可能拿着一份膨胀了三倍的表跑完整条链路才发现不对。最后那个assert是血泪经验的产物。坐标命中率低于 95%八成不是数据缺失而是两个源的编码口径不同一边是完整的 12 位一边只给到 6 位县级。这时候正确的做法是把两边都截断到 6 位再连接补完县级坐标后再用乡镇级的边界去切。# 口径不一致时的降级连接截到 6 位县级对齐 div[code6] div[code].str[:6] geo[code6] geo[adcode].str[:6] county_geo (geo.groupby(code6)[[lng, lat]] .first().reset_index()) # 一个县只取一个代表点 wide wide.merge(county_geo, oncode6, howleft, suffixes(, _county)) wide[lng] wide[lng].fillna(wide[lng_county]) wide[lat] wide[lat].fillna(wide[lat_county])groupby().first()这里取的是该县下第一条记录虽然粗糙但可控县级代表点本来就只是个兜底值精度要求不高真要精确到乡镇还得靠后面的面数据。别在这上面花太多时间调优收益极低。3.3 乡镇街道这一级怎么补全从村居代码回滚最稳乡镇街道是整个表里最容易缺的一层。很多公开数据只到县级6 位代码一给往下就没了。但这一层恰恰是业务最需要的——配送网点、社区网格、门店归属基本都落在街道乡镇这一级。最可靠的办法是「从下往上回滚」只要手上有村居级12 位的数据直接code[:9]去重就能得到完整的乡镇街道清单不需要另外找源。因为 12 位代码的第 7-9 位本来就是乡镇街道编码code[:9]天然就是它的主键。village pd.read_csv(village_level.csv, dtype{code: str}) village[code] village[code].str.ljust(12, 0) town (village.assign(code9village[code].str[:9]) .groupby(code9) .agg(name(town_name, first), parent_code(code, lambda s: s.iloc[0][:6].ljust(12, 0))) .reset_index() .rename(columns{code9: code})) town[level] 4 print(f回滚出乡镇街道 {len(town)} 条)parent_code取的是任意一个子节点前 6 位再补零因为同一个乡镇下所有村居的前 6 位必然相同取第一条就够了。这比去县级表里按名称模糊匹配靠谱得多——名称匹配遇到重名就得人工核几百条人工核一天就没了。如果连村居级数据都没有只剩三条退路地图 POI 按坐标反查所属街道、邮政网点地址反推、业务地址样本做文本聚类后人工确认。三条路都有误差我的习惯是先用第一条把覆盖率拉到 90% 以上剩下的 10% 挂着不动等业务方真正用到那一片时再单独处理不做无谓的全量死磕。4. 经纬度落到乡镇街道质心、面边界与坐标系转换坐标这一块坑比区划本身还密。同一批数据里坐标可能来自 GPS 采集、地图点选、接口返回三种渠道背后对应三种不同的坐标系混在一起用整体偏移三五百米是常态而且偏得还很一致——所有点都往同一个方向漂肉眼看地图根本发现不了。4.1 质心、内点还是面三种几何精度各自适合什么场景给一个行政区配坐标至少有三种选择。质心是几何中心算法简单但有个致命问题对于形状不规则的区域质心可能落在区域外面。一个 C 形的区、一个有飞地的街道质心掉到隔壁行政区的正中这种事在沿海和矿区特别常见。内点point on surface保证落在多边形内部算法会从质心出发找一个真正在面内的点。它比质心靠谱得多成本也就多一次计算是我默认给乡镇街道配代表点时用的方案。缺点是它不保证位置「居中好看」有时候会贴到边界上前端展示时略显别扭但业务正确性优先。面边界精度最高能直接做点在面内判定和面积计算代价是数据量。全国乡镇街道级的面数据抽稀后几十到上百兆是常态直接塞进业务库并不合适。我的分层策略是省级和市级存面用于大范围筛选和地图渲染县级存内点加面乡镇街道存内点面数据单独放一张几何表只在需要做点在面内校验时才查。飞地和插花地是这块最容易被忽略的细节。某个区在另一个区内部有一小块地这种行政上的历史遗留非常普遍。质心法在这里必错只有面数据能救。如果你的业务涉及网格化管理或者社区归属判定乡镇街道这一级的面数据不能省。4.2 WGS84、GCJ-02、BD-09坐标转换写错一步就差五百米三个坐标系的关系要理清楚。WGS84 是 GPS 原始的球面坐标国际通用GCJ-02 是在 WGS84 基础上做过非线性偏移的坐标国内主流地图服务返回的多是这一种BD-09 又是在 GCJ-02 上再叠一层偏移。三者之间偏几百米且偏移量随经纬度变化不是一个固定常数能补的。工程上的铁律是入库统一 WGS84出图前统一转换。数据库里只存一种谁要用谁转绝不混存。混存之后你永远不知道某条记录的坐标到底是哪个系的排查一次得半天。import math A 6378245.0 # 椭球长半轴 EE 0.00669342162296594323 # 第一偏心率平方 def _tlat(x, y): r -100.0 2.0*x 3.0*y 0.2*y*y 0.1*x*y 0.2*math.sqrt(abs(x)) r (20.0*math.sin(6.0*x*math.pi) 20.0*math.sin(2.0*x*math.pi)) * 2.0/3.0 r (20.0*math.sin(y*math.pi) 40.0*math.sin(y/3.0*math.pi)) * 2.0/3.0 r (160.0*math.sin(y/12.0*math.pi) 320*math.sin(y*math.pi/30.0)) * 2.0/3.0 return r def _tlng(x, y): r 300.0 x 2.0*y 0.1*x*x 0.1*x*y 0.1*math.sqrt(abs(x)) r (20.0*math.sin(6.0*x*math.pi) 20.0*math.sin(2.0*x*math.pi)) * 2.0/3.0 r (20.0*math.sin(x*math.pi) 40.0*math.sin(x/3.0*math.pi)) * 2.0/3.0 r (150.0*math.sin(x/12.0*math.pi) 300.0*math.sin(x/30.0)*math.pi) * 2.0/3.0 return r def wgs84_to_gcj02(lng, lat): WGS84 - GCJ-02用于把库里的坐标投到国内底图上 if not (72.004 lng 137.8347 and 0.8293 lat 55.8271): return lng, lat # 范围外不偏移 dlat _tlat(lng - 105.0, lat - 35.0) dlng _tlng(lng - 105.0, lat - 35.0) rad lat / 180.0 * math.pi magic 1 - EE * math.sin(rad) ** 2 sq math.sqrt(magic) dlat (dlat * 180.0) / ((A * (1 - EE)) / (magic * sq) * math.pi) dlng (dlng * 180.0) / (A / sq * math.cos(rad) * math.pi) return lng dlng, lat dlat def gcj02_to_wgs84(lng, lat, tol1e-7, max_iter10): 反向没有解析解用迭代逼近tol 单位是度1e-7 约等于 1 厘米 wlng, wlat lng, lat for _ in range(max_iter): glng, glat wgs84_to_gcj02(wlng, wlat) dlng, dlat glng - lng, glat - lat if abs(dlng) tol and abs(dlat) tol: break wlng - dlng wlat - dlat return wlng, wlat参数说明tol设到1e-7度换算下来大约一厘米对配送场景够用了max_iter一般 3 到 4 次就收敛设 10 只是保险。范围判定那段不能删——境外坐标做这套偏移会算飞历史上有人拿它处理海外门店坐标结果全落到海里。BD-09 与 GCJ-02 之间的换算是另一组常数量级小得多通常在地图 SDK 里已经处理掉除非你要直接对接某家地图的瓦片服务否则不用单独实现。4.3 用 PostGIS 做点在面内校验和最近邻兜底有了面数据最实用的一个能力是「给一个坐标告诉我它属于哪个街道」。这比任何文本地址解析都准因为地址可以写错、可以写简称坐标不会撒谎。-- 点在面内判断坐标落在哪个乡镇街道 SELECT code, name FROM ad_div WHERE level 4 AND valid_to IS NULL AND ST_Contains(geom, ST_SetSRID(ST_Point(:lng, :lat), 4326)) LIMIT 1;ST_Contains走的是 GiST 索引单点查询在几十万面的表上通常几毫秒到几十毫秒。注意必须先用ST_SetSRID(..., 4326)把点显式标成 WGS84否则 PostGIS 会拿 SRID 0 去做比较直接报错或者给出荒谬结果。但ST_Contains有个必须兜底的情况坐标刚好落在边界上、或者落在两个面的缝隙里边界数据抽稀后缝隙很常见会返回空。这时候不能直接判失败要退到最近邻。-- 最近邻兜底取距离最近的两个乡镇附带米级距离 SELECT d.code, d.name, ST_Distance(d.geom::geography, p.pt::geography) AS dist_m FROM ad_div d, (SELECT ST_SetSRID(ST_Point(:lng, :lat), 4326) AS pt) p WHERE d.level 4 AND d.valid_to IS NULL ORDER BY d.geom - p.pt -- 走 GiST 索引的 KNN 排序 LIMIT 2;-是 PostGIS 的距离操作符能直接利用 GiST 索引做 K 近邻排序比写ORDER BY ST_Distance(...)快一两个数量级。但-返回的是球面度数不能直接当米用所以要在 SELECT 里再套一层::geography转成米。业务侧的判定规则一般是最近距离小于 500 米就认为归属可信500 到 2000 米标记为「疑似」进人工复核队列超过 2000 米直接判为坐标有问题。5. 避坑五级地址表落地最常见的 5 个翻车点5.1 用名称做连接行数悄悄翻倍现象两份表 merge 之后从 32 万条变成 41 万条代码不报错下游报表数字全错。 原因乡镇街道名称不唯一全国叫「城关镇」「新华街道」的重名单位极多名称 join 天然产生笛卡尔积。 解决所有连接只准用代码列并且强制加validate1:1或validatem:1确实需要按名称匹配的单独建一张别名表一条 code 对多条别名然后按 code 回连。5.2 坐标系搞混门店整体漂移三百米现象门店在地图上显示的位置在马路对面小区所有门店偏的方向还都一样。 原因底图用 GCJ-02入库数据是 WGS84中间少了一步转换或者一部分数据转了、一部分没转。 解决入库统一 WGS84 并显式写进字段注释写一个自检脚本随机抽 100 个点转成 GCJ-02 后和底图做叠加偏移量超过 50 米就告警。这个脚本我一般放在数据流水线的最后一步跑一次几秒钟能省掉无数次线下核店。5.3 把邮编当区划属性一个区查出七个邮编现象给区划表加了zip_code列收货地址邮编校验大面积失败。 原因邮编是投递局维度一个县级单位对应多个邮编一个邮编也可能跨乡镇。 解决拆成独立的映射表按「村居 乡镇 县级」优先级回退别在区划表上硬塞一列。5.4 只存现行快照跨年报表对不上现象去年报表里某县贡献了三成单量今年同样的查询里这个县消失了数字全跑到市辖区名下。 原因只保存了现行区划撤县设区之后旧代码被物理删除或覆盖。 解决上双时态valid_from/valid_to一个都不能少订单落库时同时记录「下单时的区划代码」和「下单时间」做历史归集时按订单时间反查当时有效的区划而不是用现在的表去 join。5.5 边界抽稀过度沿海的点判不出归属现象ST_Contains对沿海街道的一部分坐标返回空明明地址就在那里。 原因公开边界数据做过抽稀海岸线和复杂边界被简化实际陆地落到了简化后的多边形外面。 解决面数据在入库前统一做一次ST_Buffer(geom, 0.0005)的小幅外扩约 50 米再用ST_Contains同时保留最近邻兜底逻辑。外扩会有轻微的归属误判但对配送这种场景误判到隔壁街道远比判不出归属要好处理。6. 增量对账一份能自动发现「撤县设区」的自检脚本区划数据不是一次性工程。每年都有一批调整你不可能靠人盯着公告一条条改。我的做法是每年拉一次全量快照存成带日期后缀的版本每月跑一次 diff把变更分成四类落表人工只需要审核「父级变更」这一类其余自动合并。变更类型判定条件处理动作新增新快照有、旧快照无的 code自动入库valid_from设为快照日期撤销旧快照有、新快照无的 code不删除回填valid_to改名code 相同但name不同自动更新name旧名写入别名表父级变更code 相同但parent_code不同人工审核涉及跨市划转子树路径要重算import pandas as pd def diff_snapshot(old: pd.DataFrame, new: pd.DataFrame) - dict: o old.set_index(code) n new.set_index(code) common o.index.intersection(n.index) return { added: n.index.difference(o.index).tolist(), removed: o.index.difference(n.index).tolist(), renamed: common[o.loc[common, name] ! n.loc[common, name]].tolist(), moved: common[o.loc[common, parent_code] ! n.loc[common, parent_code]].tolist(), } res diff_snapshot(snap_2024, snap_2025) print({k: len(v) for k, v in res.items()}) # 典型输出{added: 214, removed: 87, renamed: 156, moved: 12}removed这一类的处理方式最值得说一句绝对不要物理删除。区划撤销之后历史订单、历史报表、外部系统里存的老代码仍然指向它。物理删了那些数据就变成孤儿做同比的时候你连「这个代码以前叫什么」都查不到。正确做法是回填valid_to让它留在表里但不再参与现行查询。moved的数量通常最小但杀伤力最大。跨市划转会导致整棵子树的路径串失效必须重算path字段还要检查有没有外部系统缓存了旧的层级关系。我吃过一次亏一个县级单位划到隔壁市代码没变、名称没变只有parent_code变了脚本没有监控这个字段结果按市聚合的报表整整错了一个季度直到业务方拿着两个数字对不上来问才发现。从那以后moved这类变更我一律打上告警宁可多响几次也不要静默通过。顺带提一个习惯每次跑完 diff把结果连同快照日期一起写进一张变更日志表保留至少三年。等到某天有人问「为什么去年这个区的数字和今年对不上」你翻日志五分钟就能给出答案而不是从头再推一遍。这套东西做扎实了后面所有的地址解析、网点匹配、区域报表都省心。希望帮到你。本文还有配套的精品资源点击获取