NULL值问题背后可能是上游格式变更导致JOIN失效,AI回答了正确的问题却未触及真正原因。强调提问时提供上下文(数据是否新出现)的重要性。
报告里出现了 null。你问如何处理它们。
你得到了一个很好的回答。用 Coalesce 处理,或者过滤掉,或者标记出来待审查,并且附有清晰的说明,解释在什么情况下哪种方式更合适。你选了其中一种方案,代码上线了,报告看起来也正确了。
两天后,有人发现总数比上季度少了约 3%,这时你才发现了真正的问题。上游某个关键字段改了格式,导致原本能匹配上的 JOIN 失效了,这个问题已经悄无声息地产生了六周的 null。你的修复只是把它隐藏起来了。
🔍 你问的问题是可以回答的
在那次交流中没有什么出岔子的地方,这恰恰是它难以被察觉的原因。
你问了一个关于 null 处理的形式良好的问题,得到了一关于 null 处理的良好回答。你的问题里没有任何信息暗示这些 null 是新出现的、意料之外的,或者集中在某些曾经有匹配结果的行上。所以回答把 null 当作一种正常的数据情况来处理,这本身没有错——通常它们也确实是正常的数据情况。
如果有人站在你身后旁观,可能会问一句"这些 null 一直都在吗?"但 prompt 没有办法主动追问,除非你让它有这个能力。
这类问题在数据工作中频繁出现,而且它们其实是两种不同类型的失败,值得区分清楚。
新出现的 null。JOIN 键多了尾部空格,或者大小写变了,或者上游被转型成了另一种类型。原先能匹配的 LEFT JOIN 现在匹配不上了,未匹配的行以 null 的形式透传过来。症状是 null。原因是三步之外的一个键格式变化。
数字略有偏差。原本一对一匹配变成了多对一匹配,导致行被复制了。计数虚增了,平均值也向被复制行所包含的值靠近。症状是一个看起来偏差了几个百分点的指标。原因是产生了扇出的 JOIN。
在这两种情况下,你问一个关于症状的合理问题,就能得到一个合理的回答——同时让真正的原因继续潜伏在系统里。
🧠 为什么职业早期容易犯这个错
这方面有扎实的研究支撑,而且它跟编程无关。
1981 年,Chi、Feltovich 和 Glaser 让物理专业学生和物理学教授对问题进行分类。学生们按照问题的外观分类:滑轮题放一起,斜面题放一起。教授们则按照解题所需的原理分类,所以一道滑轮题和一道斜面题可能相邻而坐,因为它们都与动量守恒有关。这个发现在反复验证后已被广泛接受,同类研究还指出:新手比专家更依赖可见的线索。
这个现象跟上面描述的问题如出一辙。报告里的 null 是滑轮,JOIN 行为才是原理。
这并不是说初学者不小心观察。他们的眼睛只看得到表象,在有足够的经验让深层结构浮现之前,可见的东西确实是组织问题的唯一可依靠的东西。一位资深工程师看到"原本有值的列现在出现了 null",脑子里会想到 JOIN,因为他以前踩过这个坑,这个问题已经被归档进了某个认知节点。
这意味着"寻找根本原因"这个建议其实没法执行。如果你已经能看到根本原因,你早就问出来了。你需要的是一种写出问题的方式,它能在你事先不知道答案的前提下,打开通往答案的路径。
✅ 在问任何问题之前,先写清楚差距
两行字,放在问题之前:
Expected: every order row has a matching customer name
Actual: about 4% of rows have a null customer name, starting some time this month
这就是整套技巧的全部内容。写下你期望的结果。写下实际发生的情况。然后再问。
注意,只是写下来这个动作就带来了什么变化。"有 null 存在"是一种状态。"原本应该匹配的行没有匹配上,而且这是新出现的"是一个变化,而变化必然有原因。第二个版本无法用"来教你如何处理 null"来回答,因为它问的已经不是 null 的问题了。
有三个要点让这个技巧生效:
Expected 是信息所在之处。你在陈述系统应该做什么,而这是别人都不知道的知识。就这一行,就能排除掉大多数错误的答案。
"从本月开始"把一种状态变成了一件事。任何有起始时间的事物,背后都有发生了变化的东西。这是你能说出的最有用的信息,也是人们最容易遗漏的信息。
百分比比"一些"更精确。4% 的行提示存在共享某个属性的子集。90% 则提示某种结构性问题。两者指向不同的方向。
你不需要知道原因才能写出这两行。这是这两行的意义所在。
⚖️ 值得认真对待的一条反对意见
较弱的版本是:"我不知道原因在哪里,所以才来问。"这没问题,这两行也不要求你知道原因。
更有力的反对意见是这样的:很多数据工作中,你其实也不知道该期望什么。你在看一个你不负责的指标,一个你入职前别人搭的 pipeline,诚实的答案是你完全不知道正确的数字应该是多少。"这应该是多少?"才是真正的问题。Expected vs. Actual 的框架假设你掌握了比较的一方,但实际上你往往并没有。
这是一个真实的局限,而且它出现的频率比我希望的要高。当你真的不知道期望值时,这个技巧帮不了你,假装有一个你并不拥有的期望也比什么都不说更糟糕。
但它改变了你应该首先做的事。如果你写不出 expected 行,那么建立期望本身就是任务,而且它和修复症状是不同的任务。找到谁是这个指标的负责人。找到它六个月前是什么样子。找到是否有人曾经验证过它。这些工作毫不起眼,但它们才是真正需要做的事,再多的 prompt 技巧也替代不了。
这个技巧适用于你确实有一个被违反的期望但还没有写下来的常见情况。这涵盖了大多数 bug 报告。它不适用于探索性工作,而且在你开始之前认清自己处于哪种情况是有意义的。
两行字,放在问题之前:
Expected: [what the system is supposed to do]
Actual: [what it is doing, with a rough size and a rough start date]
Expected 是只有你能写的那一行。它承载着排除错误答案的知识。
说明什么时候开始的。状态招来处理。变化招来调查。
给出一个大致比例。4% 和 90% 指向不同的方向。
如果你写不出 expected 行,这也是有价值的信息。它意味着任务还不是修复这个问题,而是弄清楚这个数字应该是什么样的。