SQL GROUP BY中的功能依赖

SQL标准知道一个有趣的特性,你可以投射在GROUP BY 子句中列出的主键(或唯一键)的任何功能依赖,而不需要明确地将该功能依赖添加到GROUP BY 子句中。

这意味着什么呢?考虑一下这个简单的模式:

CREATE TABLE author (
  id INT NOT NULL PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE book (
  id INT NOT NULL PRIMARY KEY,
  author_id INT NOT NULL REFERENCES author,
  title TEXT NOT NULL
);

为了按作者计算书籍的数量,我们倾向于写:

SELECT a.name, count(b.id)
FROM author a
LEFT JOIN book b ON a.id = b.author_id
GROUP BY 
  a.id,  -- Required, because names aren't unique
  a.name -- Required in some dialects, but not in others

在这种情况下,我们必须通过一些独特的东西来分组,因为如果两个作者都叫John Doe,我们仍然希望他们能产生不同的组。因此,GROUP BY a.id 是一个必然的结果。

我们习惯于同时使用GROUP BY a.name ,特别是在这些需要这样的方言中,因为我们在SELECT 子句中列出了a.name

  • Db2
  • Derby
  • Exasol
  • Firebird
  • HANA
  • Informix
  • Oracle
  • SQL Server

但这真的是必须的吗?根据SQL标准,它不是,因为在author.idauthor.name 之间存在着功能依赖。换句话说,对于author.id 的每一个值,恰好有一个author.name 的可能值,或者说author.name 是一个函数。author.id

这意味着,如果我们GROUP BY 两列,或者仅是主键,这并不重要。两种情况下的结果必须是相同的,因此这是有可能的:

SELECT a.name, count(b.id)
FROM author a
LEFT JOIN book b ON a.id = b.author_id
GROUP BY a.id

哪些SQL方言支持这个?

至少有以下SQL方言支持这个语言特性:

  • CockroachDB
  • H2
  • HSQLDB
  • MariaDB
  • MySQL
  • PostgreSQL
  • SQLite
  • Yugabyte

值得注意的是,在有GROUP BY 的情况下,MySQL曾经简单地忽略了一个列是否可以被明确地预测。虽然下面的查询在大多数方言中被拒绝,但在MySQL中,在引入ONLY_FULL_GROUP_BY模式之前,它没有被拒绝:

SELECT author_id, title, count(*)
FROM author
GROUP BY author_id

如果一个作者写了不止一本书,我们应该为author.title ,显示什么?这没有意义,但MySQL仍然曾经允许它,并且会从组中投射任何任意的值。

今天,MySQL只允许投射与GROUP BY 子句有功能关系的列,这是SQL标准所允许的。

扫描二维码关注公众号,回复: 14422175 查看本文章

优点和缺点

虽然避免额外列的较短语法可能更容易维护(如果需要的话,很容易投射额外的列),但在生产中存在一些查询中断的风险,即当基础约束被禁用时,例如为了迁移。虽然不太可能在一个实时系统中禁用主键,但仍有可能出现这种情况,如果没有主键,以前有效的查询将不再有效,原因与MySQL的旧解释无效相同。不再有功能依赖性的保证。

其他语法

从jOOQ 3.16和#11834开始,将有可能在GROUP BY 子句中直接引用表,而不是单个列。比如说:

SELECT a.name, count(b.id)
FROM author a
LEFT JOIN book b ON a.id = b.author_id
GROUP BY a

语义将是:

  • 如果表有一个主键(无论是否复合),在GROUP BY 子句中使用该主键。
  • 如果表没有主键,则列出该表的所有列。

由于jOOQ支持的RDBMS目前都不支持这种语法,所以它是一个纯粹的 合成jOOQ功能

猜你喜欢

转载自juejin.im/post/7126362182908018724
今日推荐