📈 第12章 关系数据库
📄 一句话概括

电子表格只有二维,要把两张表放一起,只能把销量抄到每一行。 12 期 24 篇,销量这一列被写 24 遍,实际只有 12 个不同的值。 直接对这一列求和得 1,285,200,正确值是 642,600——差 2 倍,正好是每期的文章数。

📝 本章和前面几章的关系

上一篇把路线走通了。这一篇把它落到电子表格上,看结构本身会带来什么问题。 摊平不是错,错在把摊平的结果当成数据本来的样子。

一、只能摊平

电子表格是一张二维表:行和列。它没法表达「一期里面有两篇文章」这种嵌套。 要在一张表里同时放主题和销量,唯一办法是把销量抄到每一行。 于是 24 篇文章变成 24 行,每行后面跟上该期的销量。

前两行是同一期的两篇文章,销量一模一样,因为本来就是同一个数。 这不是错误,是二维表格表达一对多的唯一方式。问题在后面。

二、代价一:重复

12 期、每期 2 篇,24 行里销量被写了 24 遍,而实际只有 12 个不同的值。 如果一期有 5 篇文章,销量就要写 5 遍。期数一多,重复量成倍涨。 重复占地方还是小事。真正的问题是它会引诱你算错。

任何「一行一个事实」的表,一旦某列被重复,那个列就不能直接求和。

三、代价二:算错

把「本期销量」这一列直接求和:1,285,200。 把期号表的销量求和:642,600。 差了 2 倍,正好是每期的文章数。因为每一期的销量都被算了两次。

这种错很常见,而且看起来很合理——你只是在求一列的和,Excel 也没报错。 摊平之后,同一个事实变成了多行,任何「整列运算」都会重复计数。

四、代价三:换个问题就白摊

现在想比「文章字数和销量」,得再抄一遍销量,换成字数。 再想比「作者和销量」,又抄一遍。每换一个问题,摊平动作重来一次。 因为摊平的本质是「为某一个特定问题准备的数据形状」。

问题一变,形状就不对了。这是电子表格真正的极限,不是容量。 关系数据库不做这件事:数据按事实分开放,问题来了现接。

五、但图还是画得出来的

话说回来,摊平之后画图很方便:一行就是图上的一点,一列就是一条轴。 按封面主题分组求平均,画成柱状图,四根柱子清清楚楚。 所以电子表格不是没用,它擅长「一次性的、形状固定的」比较。

判断标准是:这个问题会不会问第二遍、换个角度再问一遍。会,就别用摊平表。

六、动手 Demo:电子表格走到极限

把关系摊平成 24 行,看销量列被重复了多少遍。 再演示摊平之后直接求和会错成什么样,以及按主题分组的图。

ISSUES = [
    (1, 52400, "EFF"), (2, 49100, "MONEY"), (3, 58300, "AI"),
    (4, 47800, "MONEY"), (5, 61200, "AI"), (6, 53600, "EFF"),
    (7, 45900, "CAREER"), (8, 46800, "CAREER"), (9, 59700, "AI"),
    (10, 55100, "EFF"), (11, 50300, "MONEY"), (12, 62400, "AI"),
]

# 摊平:一篇文章一行,销量抄到每行
flat = []
for issue, copies, topic in ISSUES:
    for k in range(2):
        flat.append((issue, k, topic if k == 0 else "CAREER", copies))

print("摊平后 %d 行" % len(flat))
print("销量列里 %d 个数,%d 个不同的值"
      % (len(flat), len(set(r[3] for r in flat))))

print()
print("整列求和    %s" % format(sum(r[3] for r in flat), ","))
print("正确求和    %s" % format(sum(r[1] for r in ISSUES), ","))
print("差 %.2f 倍" % (sum(r[3] for r in flat) / float(sum(r[1] for r in ISSUES))))

实跑输出:

--------------------------------------------------------------------
电子表格走到极限
--------------------------------------------------------------------

  ① 把关系摊平成一张表
  --------------------------------------------------------------------
  电子表格只有二维。要在一张表里同时放主题和销量,只能把销量抄到每一行。
  于是 24 篇文章变成 24 行,每行后面跟上该期的销量。

| 文章号 | 期号 | 主题 | 本期销量 |
|:---:|:---:|---:|---:|
| 1 | 1 | 效率工具 | 52,400 |
| 2 | 1 | 效率工具 | 52,400 |
| 3 | 2 | 理财 | 49,100 |
| 4 | 2 | 职场 | 49,100 |
| 5 | 3 | AI | 58,300 |
| 6 | 3 | AI | 58,300 |

  注意第 4 列:同一期的两篇文章,销量写了两遍。

  ② 摊平的代价:重复
  --------------------------------------------------------------------
  12 期 × 2 篇 = 24 行,销量这一列被写了 24 遍,实际只有 12 个不同的值。
  如果一篇期里有 5 篇文章,销量就要写 5 遍。期数一多,重复量成倍涨。
  重复不只是占地方,它会引诱你算错。

  ③ 算错是怎么发生的
  --------------------------------------------------------------------
  把「本期销量」这一列直接求和:1,285,200。
  把期号表的销量求和:642,600。
  两者差了 2 倍——正好是每期的文章数。摊平之后直接求和,等于把销量算了两次。
  ! 这不是粗心,是表格结构本身在骗人:它让同一个事实出现在多行里。

  ④ 想换个比较角度,就得重摊一次
  --------------------------------------------------------------------
  现在问「文章字数」和销量的关系,得再抄一遍销量,换成字数。
  再问「作者」和销量的关系,又抄一遍。每换一个问题,摊平动作重来一次。
  电子表格只能摊平一次,问题一变就白摊。这是它真正的极限。
  摊平本身没错,错在把摊平当成数据本来的样子。

  ⑤ 图还是画得出来的
  --------------------------------------------------------------------
  按封面主题分组求平均,画成柱状图,存成 PNG:15390 字节。
  四个主题的平均销量从 46350 到 60400,最高是最低的 1.30 倍。

摊平表里销量是 24 个数,去重后只剩 12 个。 「去重后剩几个」是个很好的自查动作:少于行数,这一列就有重复。 求和差 2 倍这件事,Excel 不会提醒你。它只能算,不能判断这一列该不该求和。

30 秒实验

  1. 如果按「每期两篇」摊平,并且改成「求销量平均值」,会得到正确的平均值吗?为什么?
  2. 什么样的摊平表是安全的?找一个「每行一个独立事实」的例子。
  3. 想一想:为什么关系数据库把「一条事实只存一处」当成第一原则?

常见误区

  1. 对重复列直接求和:一列里如果有重复值,求和结果必然偏大。
  2. 以为摊平只是麻烦:摊平改变的是数据的结构,不只是排版。
  3. 把摊平表当原始数据:它是一次性的视图,原始数据还在那两张表里。
  4. 每换一个问题重新摊一次:这正是该换工具的明确信号。
自测三问(点开对答案) 1. 摊平表为什么会让求和出错? —— 因为摊平把一期的销量复制到了该期每一篇文章的行里。求和时每一期被算了「文章数」次。 2. 怎么快速发现一列有重复? —— 数一下这列有几个不同的值,比行数少就说明有重复。 3. 什么时候该放弃摊平表? —— 当同一个问题会被反复问、或需要换不同角度比较时。摊平是为单一问题准备的一次性形状。

摊平还有一个更贵的代价:每次数据一变,所有抄过的地方都得跟着改。下一篇算这笔账。

相关笔记