我希望现代关系查询语言具备的那些东西

查看原文 HN 讨论

文章摘要

这篇文章是作者 cb 压了好几年的旧草稿,因为最近关于 Acadia 等新查询语言的讨论重新翻出来修订发表。他的核心论点开门见山:NoSQL 之所以兴起,很大一部分原因在于——SQL 背后的思想本身很强大,但它的实现方式往往笨拙而陈旧。一门从 SQL 汲取教训的语言,可以让关系型数据对程序员来说好用得多。他自称的背景是 MySQL 和 Db2 为主,SQLite、SQL Server、Oracle、Postgres 也都在真实项目里用到过(熟悉度递减)。

第一件事是语法。作者说自己对美学并不挑剔,但很多人挑剔——「程序员就像小孩,只想吃通心粉不想吃西兰花」。SQL 的语法源自 PL/I,那是 1970 年代 IBM 的选择,放到今天恐怕过不了关。他猜想一门新语言多半会带上 C 或 Python 的观感,或许再掺一些 ML 和 Prolog 的影响(就像 Rust 那样)。与语法一同该改进的是解析器:他特别厌恶 MySQL 的解析器,因为它从不告诉你问题出在哪里、是什么,除非是 DELIMITER 那种语法荒诞剧;相比之下 Oracle 在报错时会告诉你它期待什么,做得出奇地好。文中的示例语法他强调只是示意,灵感来自 F#、Erlang 和 Elixir。

第二件是让函数式风格真正被支持。SQL 最强大的武器正是它的第四代语言特质——你描述想要什么数据,而不是手工循环去取,这与惰性求值等函数式范式非常接近。但绝大多数 SQL 方言的标准库在这方面相当贫瘠,是为 1980 年代的过程式程序优化的;最后各家都加上了存储过程,而存储过程本质上是过程式的,与 SQL 的声明式本性背道而驰。结果反映在用户代码里:人们模仿语言和标准库让他们最容易写的风格,于是大量代码在摆弄可变状态(游标之类)和过程,而不是函数。作者的结论是一句朴素的道理:默认值很重要

第三件是不那么不透明的查询规划器。4GL 的强大依赖于编译器和优化器,但你很容易不小心写出一个昂贵得多的查询,而规划器的输出对非 SQL 优化专家来说往往像天书(他再次点名 MySQL 的 EXPLAIN 工具有多糟)。这一点虽然不严格属于编程语言理论范畴,但确实是现有实现的短板,而计算机科学界对此其实已经积累了很多认识。

第四件是更好的用户自定义类型。一些数据库提供 domain 概念(也是 SQL 规范的可选部分)用于定义用户自定义数据类型,但能力通常很有限,多半只是范围或检查约束的语法糖。他发现 Postgres 大概是唯一像样支持的,Oracle 是最近才加上(看起来可能比 Postgres 更灵活)。他指出 domain 是 Codd 的《The Relational Model》里就讲过的东西——那是关系数据库的奠基文本;考虑到 Postgres 的血统来自 Ingres、Ingres 又基于 QUEL、而 QUEL 比 SQL 更接近 Codd 的原始构想,Postgres 走到这一步是说得通的。

第五件也是全文篇幅最长的一节:和类型、可辨识联合与模式匹配。他举的例子是 IBM i 系统里一个返回调用栈帧信息的 SQL 函数(IBM i 在「Services」这一伞形概念下提供了大量系统管理用的 SQL 函数)。这个函数对于身兼系统管理员的 DBA 在排障时非常有用,却也非常笨拙:本质上存在若干组互斥的列,因此产生大量可空字段,还有一堆实际上是枚举却用字符串表示的列。部分问题是纯粹的模式设计问题(也可能是因为结果必须放进单张表里——他顺带提到「能返回多张表」也是个有趣的方向),字符串枚举可以用外键指向一张充当枚举的表来修;但另一部分确实源于实现语言的表达力不足。

他随后给出了一段示意代码:先定义一个共享字段的记录类型 MachineInterfaceInfo,然后把 FrameType 定义成一个和类型,包含 ILE、OPM、AIX、Java 等几个分支——分别对应 IBM i 支持的多种程序模型(Java 程序、旧的 OPM 程序 ABI、新的 ILE 程序 ABI、通过系统调用模拟运行的 AIX 程序、以及内核 LIC),而一次调用栈里可能同时出现每种类型的栈帧。这个和类型支持从别的记录类型继承字段、内联定义枚举(如 32/64 位)、以及 Option 类型表示可空。有了它,查询就可以直接在 WHERE 子句里做模式匹配:筛出 64 位的 AIX 帧、筛出库名为 QSYS 的机器接口帧(会同时返回 ILE 和 OPM)、筛出 LibArchive 为某值或为 None 的帧。函数体内同样能用 match 表达式按分支解构并格式化程序全名,而且必须穷尽所有可能分支,否则要显式用通配符丢弃。他还展示了基于模式匹配的函数重载:两个同名函数分别匹配「Java 帧且签名为空」和「Java 帧且签名存在」,用非 Java 帧调用会直接报错,因为没有模式能匹配。作者补了一个很实际的好处:把互斥的列组折叠起来之后,可视化也会容易得多——不用再左右横向滚动,可以把它们做成大列里按行显示的子列,或者按类型用不同方式渲染字符串。

最后一件是能匹配多种类型的外键。他举了自己博客上写过的一个 WEMI 式层级:Software、Version、Download 三张表,每张都可能关联图片,而图片因为自带元数据所以是一张 Picture 表而非一个列。常规做法是为每种关系建一张多对多表——SoftwarePicture、VersionPicture 等等——他认为这是毫无意义的重复。他设想的替代方案是让多对多表的外键本身就是一个可辨识联合:ObjectID 可以关联到 Software.SoftwareIDVersion.VersionIDDownload.DownloadID 中的任意一个,插入时用构造器标明具体是哪一种,查询时既可以按类型解构取出,也可以按带类型的值精确过滤。

HN 评论精华

这条帖子拿到 115 分,但只有 13 条评论,讨论规模远小于分数所暗示的热度。更值得注意的是讨论的走向:几乎没有人逐条评价作者提出的具体特性(和类型、模式匹配、多态外键这些),而是迅速集体转向了一个更宏观也更悲观的元问题——在 LLM 时代,改良 SQL 语法这件事还有没有意义。此外原文博客自己的评论区反而出现了几条更对口的技术回应。