探索 Raymond Boyce 在早期 SQL 中的作用,以及使 SQL 在组织中可用的实用设计决策——连接、分组、NULL 语义与性能等。

Raymond Boyce 是 1970 年代 IBM System R 项目中的关键研究员之一——这个项目把关系数据库理论变成了人们在工作中可以真正使用的东西。如果你曾经写过 SELECT 查询、受益于 GROUP BY,或者依赖数据库来保证更新的一致性,那么你就在使用那个时期形成的思想。
容易被忽视的是,SQL 的成功不仅仅因为关系模型本身优雅。它成功还因为早期设计者(包括 Boyce)不断追问一个务实问题:如何让关系查询对有真实数据、截止日期和约束的组织来说可行? 本文集中讨论那些务实的选择:让分析师、开发者和业务团队能在不需要数学博士的情况下共享同一系统的特性。
关系理论承诺很多:把数据存放在表中,用声明式问题来查询,避免手工的记录导航。但组织需要的不只是承诺。他们需要一种语言,能够:
Boyce 的重要性在于这类“翻译”工作:把一个强大的概念变成适合普通工作流的工具。
你会读到一篇以历史为背景、用通俗语言解释早期 SQL 设计决策的文章——为什么语言长成现在这个样子,以及为了保持可用性做出的取舍。我们会把连接、聚合、视图、事务和优化等特性,和它们为组织解决的问题联系起来。
这不是一个英雄传奇,也不是“单一发明者”的神话。SQL 的形成受多个人和多重约束影响,其演进涉及妥协。我们也不会试图写一篇完整的 Boyce 传记或 System R 的学术全史。目标更简单:理解那些奏效的实用选择——以及现代团队还能从中学到什么。
关系理论带来了一个干净的承诺:把事实存放在表格中,逻辑地描述关系,并让系统决定如何检索正确答案。纸面上,它把数据管理简化成类似数学的规则。但在实践中,组织并不按纸面生活。它们有工资文件、库存清单、混乱的编码、不完整的记录,以及不断的压力:在问题改变时不要每次都重写程序。
这种优雅想法与可用系统之间的差距,就是早期 SQL 赢得一席之地的地方。研究者们不仅要证明关系数据库能存在;他们还得证明这些系统在面对真实工作负载和真实用户时能幸存下来。
IBM 的 System R 项目是试验场。它把关系模型当成要实现、基准测试并在共享机器上运行的东西。这意味着要构建一整套链条:存储结构、查询处理器、并发控制,以及——关键的一点——一种可以教授、输入并重复运行的语言。
早期的 SQL 最初被称为 SEQUEL(Structured English Query Language)。这个名字传达了目标:一种查询语法,听起来更接近业务用户描述问题的方式,同时还能映射到系统可以执行的精确定义操作上。
System R 在实际限制下构建,这些限制强制设计者保持严谨:
这些约束把 SQL 推向了一种在可读性与可强制执行规则之间取得平衡的风格——为像连接、分组和事务安全这样的特性奠定了基础,使关系查询在实验室之外也能工作。
早期 SQL 的成功不仅来自它符合关系理论,而在于它把“可用”作为核心要求:查询应该是人们可以读、写、审查并在时间中安全维护的东西。Raymond Boyce 和 System R 团队把“可用”放在了设计首位。
SQL 被设计来服务需要围绕同一数据协作的多类人:
这种组合把 SQL 推向了一种看起来像结构化请求的风格(“select 这些列 from 这些表 where …”),而不是低级过程式代码。
实用的查询语言必须能在交接中存活:一条报表查询会变成审计查询;一条操作性查询会成为仪表盘的基础;几个月后有人接手它。SQL 的声明式风格支持这种现实。你描述 想要什么,而不是描述 如何逐步获取行,数据库来决定执行计划。
为了让 SQL 更易接近,必须接受一些权衡:
这个目标体现在 SQL 让例行工作变得常规化的能力:定期 报表、可追溯的 审计,以及驱动应用的可靠 操作性查询。重点不是为优雅而优雅——而是让负责数据的人能把关系数据用起来。
早期 SQL 成功不仅仅在于巧妙的查询语法——还在于它给组织提供了一种简单的方式来描述数据的“是什么”。表模型易于解释、易于在白板上画图,并易于团队之间共享。
一个 表 类似于关于某类事物的命名记录集合:客户、发票、发货等。
每一 行 是一条记录(一个客户、一张发票)。每一 列 是该记录的属性(customer_id、invoice_date、total_amount)。这种“网格”隐喻很重要,因为它与很多业务用户的思维方式一致:列表、表单和报表。
模式 是围绕这些表的约定结构:表名、列名、数据类型与关系。这区别了“我们有一些销售数据”和“这里严格定义了一笔销售的含义以及我们如何存储它”。
一致的命名和类型不是繁文缛节——它们是团队避免细微不匹配的方式。如果一个系统把日期当文本存储而另一个使用真实日期类型,报表就会不一致。如果三个部门对“状态”有不同含义,仪表盘就会变成政治争论而不是共享事实。
因为模式是显式的,人们可以无需不断翻译就协调工作。分析师可以写出产品经理能审查的查询。财务可以与运营对账。当新团队接手系统时,模式就是让数据可用的地图。
早期 SQL 的选择受现实影响:数据质量参差不齐,字段会随时间增加,需求会在项目中途变化。模式提供了一个稳定的契约,同时允许受控改变——添加列、收紧类型或引入约束以防止坏数据扩散。
约束(如主键与检查约束)强化了这个契约:它们把“我们希望这样”变成数据库可以强制执行的规则。
SQL 最持久的思想之一是:大多数问题可以用一致的句子形式提出。早期 SQL 的设计者(包括 Raymond Boyce)偏好一种人们能快速学习和识别的查询“形状”:SELECT … FROM … WHERE …。
这种可预测的结构比看上去更重要。当每个查询都以相同方式开始时,读者可以按相同顺序扫描它们:
这种一致性有助于培训、代码审查和交接。一个财务分析师通常能理解运营报表在做什么,即便不是作者,因为心理步骤是稳定的。
两种简单操作支持大量日常工作:
例如,销售经理可能会问:“列出本季度开通的活跃账户。”在 SQL 中,这个请求干净地映射为选择几个字段、标明表并应用日期与状态过滤——无需写自定义的循环去搜索和打印记录。
因为核心形式既可读又可组合,它成为了更高级特性的基础——连接、分组、视图与事务——而不会迫使用户写复杂的过程式代码。你可以从简单报表查询开始,逐步构建,同时仍使用相同的基本语言。
组织很少把所有业务信息存放在一个巨大的表里。客户信息与订单或发票的变化节奏不同。把信息拆成多张表可以减少重复(和错误),但也带来一个日常需求:当你想要答案时,如何把这些片段重新合并。
想象两张表:
如果你想要“带客户名称的所有订单”,你需要一个 join:把每个订单和具有相同标识符的客户行匹配起来。
SELECT c.name, o.id, o.order_date, o.total
FROM orders o
JOIN customers c ON c.id = o.customer_id;
这条语句捕捉了一个常见的业务问题,而无需你在应用代码中手动拼接数据。
连接也会暴露现实世界的混乱。
如果一个客户有 很多订单,客户的名字会在结果中出现 很多次。这在存储上并不是“重复数据”——只是关系在合并视图时的一种自然表现。
如果存在 缺失的匹配?如果某个订单的 customer_id 在客户表中不存在(坏数据),INNER JOIN 会默默丢掉该行。LEFT JOIN 会保留订单并让客户字段为 NULL:
SELECT o.id, c.name
FROM orders o
LEFT JOIN customers c ON c.id = o.customer_id;
这就是数据完整性重要的地方。键与约束不仅满足理论;它们能防止导致报表不可靠的“孤立”行。
早期 SQL 的一个关键选择是鼓励基于集合的操作:你描述想要的关系,数据库决定如何高效地生成它们。你不需要逐条遍历订单并为每条订单查找匹配客户,你只需声明匹配条件一次。这种思维方式使关系查询在组织规模上可行。
组织不仅仅存储记录——它们需要答案。我们本周发出了多少订单?按承运商计算的平均交付时间是多少?哪些产品带来最多收入?早期 SQL 成功的部分原因是把这些日常“报表问题”视为一等工作,而非事后补充。
聚合函数把多行变成单一数字:COUNT 用于计量、SUM 求总和、AVG 求平均,还有 MIN/MAX 获取范围。单独看这些函数可以汇总整个结果集。
GROUP BY 使得汇总更有用:它让你按类别(按店铺、按月、按客户段)产生一行——而无需写循环或自定义报表代码。
SELECT
department,
COUNT(*) AS employees,
AVG(salary) AS avg_salary
FROM employees
WHERE active = 1
GROUP BY department;
WHERE 在分组 之前 过滤行(哪些行被包含)。HAVING 在聚合 之后 过滤分组(哪些汇总被保留)。SELECT department, COUNT(*) AS employees
FROM employees
WHERE active = 1
GROUP BY department
HAVING COUNT(*) >= 10;
大多数报表错误本质上是“粒度”错误:在错误的级别上分组。如果你把 orders 连接到 order_items 后再 SUM(order_total),你可能会按每个 item 把订单总额乘多次——这是典型的重复计数。一个好的习惯是问:“我的连接后每一行现在表示什么?”并仅在那个级别上做聚合。
另一个常见错误是选择未在 GROUP BY 中出现的列(也未聚合)。这通常表明报表定义不清:先决定分组键,然后选择与之匹配的指标。
真实的组织数据充满空白。客户记录可能缺少邮箱,某个发货可能还没有送达日期,或者遗留系统根本没有采集某字段。把每个缺失值都当作“空”或“零”会悄悄地破坏结果——因此早期 SQL 为“我们不知道”引入了明确的空间。
SQL 引入 NULL 来表示“缺失”(或不适用),而不是“空”或“假”。这个决定带来了一个关键规则:很多涉及 NULL 的比较既不为真也不为假——它们是未知。
例如,当 salary 为 NULL 时,salary > 50000 是未知。并且 NULL = NULL 也是未知,因为系统不能证明两个未知是相等的。
使用 IS NULL(和 IS NOT NULL)来做检查:
WHERE email IS NULL 用于查找缺失的邮箱。WHERE email = NULL 并不会像人们预期的那样工作。在报表中使用 COALESCE 提供安全的后备值:
SELECT COALESCE(region, 'Unassigned') AS region, COUNT(*)
FROM customers
GROUP BY COALESCE(region, 'Unassigned');
注意那些会意外丢弃未知值的过滤条件。WHERE status <> 'Cancelled' 会排除 status 为 NULL 的行(因为比较结果是未知)。如果你的业务规则是“未取消或缺失”,请显式写明:
WHERE status <> 'Cancelled' OR status IS NULL
NULL 的行为会影响总数、转化率、合规检查和“数据质量”仪表板。那些有意识处理 NULL 的团队——在何时排除、标记或默认缺失值上做出选择——能够得到与业务含义一致的报表,而不是被查询的意外行为误导。
视图是一个保存的查询,表现得像虚拟表。你不是复制数据到新表,而是保存如何产生某个结果集的定义——然后任何人都可以用熟悉的 SELECT–FROM–WHERE 模式查询它。
视图让常见问题重复变得简便,而无需重写(或重新调试)复杂的连接和过滤。财务分析师可以查询 monthly_revenue_view,而不需要记住发票、贷项和调整分别在哪些表中。
它们还有助于团队标准化定义。“活跃客户”就是一个完美例子:它是指最近 30 天购买过、签有开放合同,还是最近登录过?用视图,组织可以在一个地方编码这个规则:
CREATE VIEW active_customers AS
SELECT c.customer_id, c.name
FROM customers c
WHERE c.status = 'ACTIVE' AND c.last_purchase_date >= CURRENT_DATE - 30;
现在仪表盘、导出和即席查询都可以一致地引用 active_customers。
通过提供经过策划的接口,视图可以在较高层面支持访问控制。与其对包含敏感列的原始表授予广泛权限,不如授予能只暴露角色所需字段的视图访问。
真正的运营性收益在于维护。当源表演化——新增列、重命名字段、更新业务规则——你可以在一个地方更新视图定义。这减少了“许多报表同时坏掉”的问题,让基于 SQL 的报表变得可靠而不是脆弱。
SQL 不仅要优雅地读取数据——它还必须在多人(和程序)同时操作时,让写入变得安全。在真实组织中,更新持续发生:下单、库存变化、发票入账、座位预订。如果这些更新可能部分成功或相互覆盖,数据库就不再是可信来源。
一个 事务 是数据库视为一个工作单元的多项变更:要么所有变更都发生,要么都不发生。如果在中途出现故障——断电、应用崩溃、验证错误——数据库可以回滚到事务开始前的状态。
这种“全或无”的行为重要,因为许多业务动作本质上是多步的。支付发票可能会减少客户余额、记录付款分录并更新总账。如果只有部分步骤生效,会导致会计不一致。
即便每个用户的更改都是正确的,两个用户同时操作也可能产生坏结果。想象一个简单的预订系统:
没有隔离规则,两次更新都有可能成功,产生双重预订。事务与一致性控制帮助数据库协调并发操作,使每个事务看到一致的视图并可预测地处理冲突。
这些保证支持 会计准确性、可审计性 和日常可靠性。当数据库能证明在高并发下更新仍然一致时,它就足够可靠,可以用于工资、计费、库存和合规模块,而不仅仅是即席查询。
SQL 早期的承诺之一不是你可以随便提问数据,而是组织可以在数据库增长时持续提问。Raymond Boyce 与 System R 团队认真对待性能,因为只在小表上工作的语言并不实用。
从 5,000 行表返回 50 行的查询可能很快,即便数据库“只是扫描了全部内容”。但当表变为 5,000 万行时,全表扫描会把一次快速查找变成数分钟的 I/O。
SQL 文本可能完全相同:
SELECT *
FROM orders
WHERE order_id = 12345;
变化的是数据库找到 order_id = 12345 的“方式”的成本。
索引就像书后的索引:你无需翻每一页,可以直接跳到相关页面。在数据库中,索引让系统在不读取整个表的情况下定位匹配行。
但索引也有代价。它们占用存储、使写操作变慢(因为索引也必须更新),并非能加速所有查询。如果你请求的是大部分表,扫描可能仍然比多次通过索引更快。
早期 SQL 系统中的一个关键实用选择是让数据库决定执行策略。优化器估算成本并选择计划——用索引、扫描表、选择连接顺序——而不要求每个用户都像数据库工程师一样思考。
对于运行夜间或周报的团队来说,可预测的性能比理论优雅更重要。索引加上优化器使得安排报表窗口、保持业务仪表盘响应和避免“上个月可以,但现在慢”的问题变得现实可行。
Raymond Boyce 在早期 SQL(System R 时代)的工作取得成功,是因为它偏好那些团队能接受的选择:一种可读、声明式的语言;一种与组织已有数据描述方式匹配的表与模式模型;以及一种愿意处理现实混乱(如缺失值)而不是等到理论完善再行动的态度。这些决策经受住了时间考验,因为它们不仅在技术上可扩展,也在社会层面上可扩展。
SQL 的核心思想——描述你想要的结果,而不是如何一步步得到它——仍然有助于混合团队协作。视图让团队在不复制查询的情况下共享一致定义。事务创建了“这个更新要么发生要么不发生”的共同期望,这对信任仍然是基础。
一些早期的妥协仍然出现在日常工作中:
就重要问题达成一致以减少歧义:命名规范、连接风格、日期处理,以及“活跃”“收入”“客户”等术语的定义。把重要查询当作产品代码:同行评审、版本控制和轻量测试(行数、唯一性检查与“已知答案”示例)。使用共享定义——通常通过视图或经策划的表——以防指标分裂。
如果你把这些查询变成内部工具(管理面板、仪表盘、操作工作流),同样原则适用:共享定义、受控访问和回滚方案。像 Koder.ai 这样的平 台反映了这种“务实 SQL”的传承:它让团队从聊天驱动的工作流生成 Web、后端或移动应用,同时仍依赖传统基础(前端 React、后端 Go + PostgreSQL、移动端 Flutter)和映射数据库年代纪律的特性,例如规划模式、快照与回滚。
Raymond Boyce 是 IBM 在 1970 年代 System R 项目中的关键研究员之一,该项目把关系数据库理论变成了组织可以实际使用的共享系统。他的重要性在于把 SQL 变得可用:更可读的查询、对脏数据的务实处理,以及支持多用户可靠性与性能的特性——而不仅仅是纯粹的理论优雅。
System R 是 IBM 在 1970 年代的研究项目,证明了关系模型可以在端到端的真实系统中运行:包含存储、查询处理、并发控制以及可教会的语言。它迫使 SQL 的设计面对有限计算资源、共享工作负载和不完美的业务数据等现实约束,因此成为一个重要的试验场。
早期的 SQL 名称是 SEQUEL,代表 “Structured English Query Language”,强调可读性和接近自然语言的句式结构,让业务用户和开发者更容易上手。这个“类英语”的表述表明了目标:在保持可执行性的同时让关系查询更易接近。
一致的查询“形状”让人们更容易扫读、审查和维护查询:
SELECT:你想返回什么FROM:数据来自哪里WHERE:哪些行符合条件这种可预期性支持培训、交接和复用——当查询从临时报表演变为长期运行的业务逻辑时,这一点尤为重要。
连接(joins)允许你组合已正则化的表(例如 customers 和 orders),在不需要在应用代码中拼凑数据的情况下回答日常问题。实际影响包括:
INNER JOIN 中会被丢弃,在 LEFT JOIN 中会保留并以 NULL 表示相关字段GROUP BY 把原始行聚合成报告就绪的摘要——计数、总和、平均值等——并按选定的维度(按月、按部门、按客户段)输出每一行。实用规则:
WHERE 过滤行HAVING 过滤分组常见错误来自于粒度选择不当或在连接后造成的重复计数。
NULL 表示缺失或未知数据,而不是“空字符串”或“零”。这引入了三值逻辑(真 / 假 / 未知)。实用建议:
IS NULL / IS NOT NULL 检查,而不是 = NULLCOALESCE 提供友好的默认值视图是一个保存的查询,表现得像虚拟表,能帮助团队:
视图通常是保持指标在不同仪表盘和团队间一致性的最简单方式。
事务把多个变更捆绑为一个工作单元:要么全部生效,要么全部回滚。这一点在许多业务动作天然是多步操作时至关重要(例如记账时同时记录付款并更新余额)。在并发场景下,隔离性还能防止冲突(例如重复预定),保证每个事务看到一致的状态并可预测地处理冲突。
索引通过避免全表扫描来加速查找,但它们并非免费的:索引占用存储并使写入变慢。查询优化器负责选择执行计划(扫描还是用索引、连接顺序等),让用户可以写声明式 SQL 而不必为每个查询做微调。这个特性是随着数据增长仍能保持报表窗口和仪表盘可预测性的重要原因。
... OR status IS NULL)