数据工程最佳实践合集:7 月所有文章中最值得收藏的 10 条原则

发布时间:2026/8/1 7:23:58
数据工程最佳实践合集:7 月所有文章中最值得收藏的 10 条原则 数据工程最佳实践合集7 月所有文章中最值得收藏的 10 条原则一、这 10 条原则是怎么来的7 月写了 30 天数据分析相关文章踩了不少坑也总结了不少经验。月末复盘时我发现有一些原则反复出现贯穿了数据处理、分析建模、指标治理等方方面面。这些原则不是理论推导出来的是每次翻车后拿时间换来的教训。我把它们提炼成 10 条每条都附上为什么重要和不这样做会发生什么。这份清单建议收藏每次开工新任务前扫一眼。二、10 条原则详解原则 1数据先看后算含义拿到数据先不要急着SELECT、GROUP BY先用DESCRIBE、COUNT、DISTINCT看一眼数据长什么样。-- ❌ 错误做法拿到数据直接聚合结果完全不可信 SELECT region, SUM(amount) FROM sales GROUP BY region; -- ✅ 正确做法先做数据探查 -- 第一步看表结构和数据量 DESCRIBE sales; -- 查看字段定义和类型 SELECT COUNT(*) AS total_rows FROM sales; -- 总行数 SELECT COUNT(DISTINCT user_id) FROM sales; -- 独立用户数 -- 第二步看数据范围和时间跨度 SELECT MIN(order_date) AS earliest, -- 最早订单时间 MAX(order_date) AS latest, -- 最晚订单时间 COUNT(DISTINCT DATE(order_date)) AS days -- 覆盖天数 FROM sales; -- 第三步看关键字段的空值和异常值分布 SELECT COUNT(*) AS total, SUM(CASE WHEN amount IS NULL THEN 1 ELSE 0 END) AS null_amount, SUM(CASE WHEN amount 0 THEN 1 ELSE 0 END) AS neg_amount, -- 负数金额 SUM(CASE WHEN amount 100000 THEN 1 ELSE 0 END) AS huge_amount -- 异常大额 FROM sales; -- 现在可以放心分析了不这样做会怎样一个region字段写的是中文华东另一个数据源写的是拼音 huadong你的GROUP BY region会统计出两个不同的区域结论全错。原则 2NULL 是一切 Bug 之母在 SQL 中NULL不等于任何值包括它自己任何与NULL的比较都返回NULL即未知。这是新人踩坑率第一的问题。-- NULL 的反直觉行为 —— 每个数据分析师都踩过的坑 -- 坑 1COUNT 的区别 SELECT COUNT(*) AS count_all, -- 统计所有行包括 NULL COUNT(amount) AS count_amount, -- 只统计 amount 非 NULL 的行 COUNT(DISTINCT user_id) AS count_uid -- DISTINCT 也会跳过 NULL FROM orders; -- 坑 2NOT IN NULL 灾难 -- 假设 sub_query 返回了 (100, 200, NULL) -- 以下查询的 WHERE 条件等价于 -- user_id 100 AND user_id 200 AND user_id NULL -- 最后一条永远返回 NULL未知所以整个条件永远不成立 -- ❌ 结果返回 0 行 SELECT * FROM users WHERE user_id NOT IN (SELECT id FROM bad_data); -- ✅ 正确做法使用 NOT EXISTS 或在子查询中排除 NULL SELECT * FROM users u WHERE NOT EXISTS ( SELECT 1 FROM bad_data b WHERE b.id u.user_id AND b.id IS NOT NULL ); -- 坑 3聚合函数自动跳过 NULL 但有时你需要 0 SELECT region, AVG(score) AS avg_score -- NULL 自动跳过没问题 FROM user_scores GROUP BY region; -- 但如果想算各区域用户的平均分没分数的当 0 分 SELECT region, AVG(COALESCE(score, 0)) AS avg_with_zero -- NULL 变 0 再求平均 FROM user_scores GROUP BY region;核心原则所有涉及 JOIN、WHERE 过滤、聚合的计算先想一遍如果这里有 NULL 会怎样。原则 3口径统一比口径正确更重要这句话我在 7 月说了至少 5 次。含义是一个指标即使定义不够完美只要所有人用的是同一个定义结论就是可比的。怕的是 A 部门用下单时间统计 GMVB 部门用支付时间两个数对不上就开始互相甩锅。# 口径字典 —— 防止团队各自定义导致指标打架 metric_dict { GMV: { definition: 用户在统计周期内成功支付含货到付款的订单金额总和, calculation: SUM(amount) WHERE status IN (paid, cod) AND payment_time BETWEEN {start} AND {end}, source_table: dwd_order_detail, owner: 数据团队, update_freq: 每日 T1, note: 含退款前金额不含运费 }, DAU: { definition: 统计当日有过任一活跃行为登录/浏览/点击/下单的去重用户数, calculation: COUNT(DISTINCT user_id) WHERE behavior_date {date} AND event_type IN (login, view, click, order), source_table: dwd_user_behavior, owner: 数据团队, update_freq: 实时, note: 不含机器人流量已通过安全策略过滤 }, 用户留存率: { definition: 第 N 天注册的用户中在第 ND 天仍有活跃行为的用户占比, calculation: COUNT(DISTINCT retained_user) / COUNT(DISTINCT new_user), source_table: dwd_user_behavior, owner: 增长团队, update_freq: 每日 T1, note: 次日留存(D1) / 7日留存(D7) / 30日留存(D30) } }原则 4所有 SQL 必须有注释不写注释的 SQL 等于加密文件。三个月后的你自己也看不懂。-- -- 功能计算 7 月各品类用户的复购率 -- 作者数据团队 -- 日期2026-07-30 -- 依赖表dwd_order_detail订单明细、dim_product商品维度表 -- 口径说明复购 统计月内下单 ≥ 2 次的用户 -- WITH monthly_orders AS ( -- 第一步筛选 7 月订单关联品类信息 SELECT o.user_id, p.category, -- 商品品类 COUNT(DISTINCT o.order_id) AS order_cnt -- 用户在该品类的下单次数 FROM dwd_order_detail o INNER JOIN dim_product p ON o.product_id p.product_id WHERE o.order_date 2026-07-01 -- 只统计 7 月数据 AND o.order_date 2026-08-01 AND o.order_status completed -- 只计入已完成订单 GROUP BY o.user_id, p.category ) SELECT category, COUNT(DISTINCT user_id) AS total_users, -- 该品类总用户数 COUNT(DISTINCT CASE WHEN order_cnt 2 THEN user_id END) AS repurchase_users, -- 复购率 复购用户 / 总用户 ROUND(COUNT(DISTINCT CASE WHEN order_cnt 2 THEN user_id END) * 100.0 / COUNT(DISTINCT user_id), 2) AS repurchase_rate_pct FROM monthly_orders GROUP BY category ORDER BY repurchase_rate_pct DESC;原则 5一次只改一个东西改了一个 JOIN 条件、调整了过滤逻辑、换了聚合方式——只做其中一件跑一遍看结果。如果你同时改了 3 个地方结果变了你根本不知道是哪个改动导致的。原则 6先小后大验证逻辑# 原则6 实践用 LIMIT / SAMPLE 先验证逻辑 def safe_big_query(sql: str, db_connector, sample_pct: float 0.01): 安全执行大数据查询先抽样验证再全量执行 Args: sql: 原始 SQL db_connector: 数据库连接 sample_pct: 抽样比例默认 1% # 第一步1% 抽样验证 —— 秒级出结果 sample_sql sql.replace(FROM, fFROM (SELECT * FROM, 1) # 这里简化假设实际需要更精确的 SQL 改写 print(正在对 1% 数据进行验证...) sample_result db_connector.query(fSELECT * FROM table SAMPLE {sample_pct}) # 第二步检查抽样结果是否合理 print(f抽样结果行数: {len(sample_result)}) print(f抽样结果预览:\n{sample_result.head(3)}) # 第三步人工确认后才跑全量 confirm input(抽样结果检查无误输入 yes 继续全量执行: ) if confirm.lower() yes: print(开始全量执行...) full_result db_connector.query(sql) return full_result else: print(已取消全量执行请修改 SQL 后重试) return None原则 7能分层不分块数仓设计时按数据加工层次ODS → DWD → DWS → ADS分层而不是按业务模块分块。分层的本质是控制复杂度每一层只做一件事出问题了只排查一层。原则 8冗余换性能是划算的宽表、预聚合表、物化视图——都是在用存储换查询速度。在 ClickHouse 这种列存数据库里这种 trade-off 几乎稳赚不赔。不要在查询性能崩溃后才想起做预聚合。高频查询的指标每天定时预计算好业务方打开看板秒出结果。原则 9报告先说结论数据分析报告的结构应该是结论 → 关键数据 → 分析过程 → 附录而不是我们分析了 A然后分析了 B最后发现……。你的受众——业务方、老板——不一定关心你的过程但他们一定关心所以呢。原则 10留好回滚的路改 ETL 逻辑前先CREATE TABLE xxx_backup_20260731 AS SELECT * FROM xxx。删表前先RENAME而不是DROP。旧数据至少保留 7 天。所有让你没关系直接删的冲动最后都会变成 3 天后完了数据能恢复吗的懊悔。三、自我检查清单每次正式开始一个新分析任务前对照这 5 条检查数据源确认用的是什么表、什么时间范围、过滤条件是什么NULL 检查关键字段是否有 NULLJOIN 后会不会丢失数据口径对齐分析指标的定义和团队口径字典一致吗小样验证先用少量数据跑一遍逻辑正确吗回滚方案如果我改了数据有备份吗四、把这些原则变成习惯# 一个简单的开工前检查脚本模板 def pre_analysis_checklist(task_name: str, db_connector, tables: list): 分析任务开工前自动检查清单 print(f {task_name} 开工前检查 ) for table in tables: # 检查数据量 row_count db_connector.query(fSELECT COUNT(*) FROM {table}) print(f[表] {table}: {row_count} 行) # 检查最新的数据时间 latest db_connector.query(fSELECT MAX(update_time) FROM {table}) print(f 最新数据时间: {latest}) # 检查磁盘空间是否有足够空间存备份 disk_free db_connector.query(SELECT disk_free FROM system.disks) print(f[磁盘] 剩余空间: {disk_free}) print(f 检查完成可以开始分析 ) return True五、总结这 10 条原则总结成一句话信任数据但核实数据信任自己但留好后路。数据分析师的职业素养不是会不会用高级算法而是对数据始终保持敬畏和严谨。这份清单我会维护一个在线版本随着实践持续更新。如果你也有踩过的坑想补充欢迎在评论区分享。7 月复盘系列第 3 篇完整系列请查看 22zhuling 博客首页。资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0731 资料来源索引并在发布前将具体来源贴到对应断言之后。