SQL 中的前缀别名

Author Avatar
Hannes Mühleisen
2025-02-25 · 4 分钟

简而言之:现在您可以在 DuckDB 的 SQL 方言中使用冒号将别名放在前面,例如:SELECT a: 42;

语法

“也许我们应该让大自然顺其自然,保留它那简单的、一根筋的模式。”
— Dr. Alphonse Mephesto,南方公园第五集

在我们心爱的 SQL 中,通常有不止一种方法可以做某事。 例如,您可以在 WHERE 子句中隐式(且危险地)定义连接条件,或者使用(更好的)JOIN ... ON ... 语法。 一般来说,拥有“不止一种做事方式”可能会让人困惑,甚至可能完全 有时很危险

话虽如此,我们在 DuckDB 更友好的 SQL 为荣。有太多人手工输入这些东西,尤其是在更加临时的分析世界中。

如果有一个好的理由来扩展 SQL 语法,使其更有用,我们至少会考虑它。 其他人似乎正在密切关注。 例如,我们的 GROUP BY ALL 语法现在已被 几乎 所有 SQL 系统 (以及它们的兄弟)采用。

别名

在 SQL 中,用户可以为许多事物定义别名,例如 SELECT 表达式、表名、子查询等。这有时只是为了在结果中具有可读的列名,有时需要在 ORDER BY 子句中引用复杂的表达式,而无需重复它并祈祷优化器。 别名在使用 AS定义,但实际上输入 AS 术语是可选的。 例如,这两个语句是等价的

SELECT 42 AS fortytwo;
SELECT 42 fortytwo;

我们可以看到别名跟随表达式 (42),并且 AS 是可选的。让别名放在它所描述的事物之后在编程中实际上有些罕见,更典型的是首先定义别名,然后提供表达式。 例如,在 C 语言中

int fortytwo = 42;

似乎首先声明别名是一个好主意,毕竟,这是我们稍后要引用这个东西的方式。 像 SQL 那样强制采用相反的方式只会增加心理负担。 此外,如果查询中有几个复杂的表达式,将别名放在最后会使它们很难找到。 例如,这里是臭名昭著的 TPC-H 查询 1 的前几行

SELECT
    l_returnflag,
    l_linestatus,
    sum(l_quantity) sum_qty,
    sum(l_extendedprice) sum_base_price,
    sum(l_extendedprice * (1 - l_discount)) sum_disc_price,
    sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) sum_charge,
    avg(l_quantity) avg_qty,
    avg(l_extendedprice) avg_price,
    avg(l_discount) avg_disc,
    count(*) count_order
    ...

很难在这里找到所有的别名,这甚至还不是一个复杂的例子。 如果你可以将别名放在 SQL 的前面呢? 好吧,别再等待了。

DuckDB 中的前缀别名

在最新的 DuckDB 版本 1.2.0 中,我们悄悄地发布了另一个有用的(我们认为)语法扩展,允许使用冒号 (:) 语法将别名放在它命名的事物之前。 事实证明,这比我们想象的要容易得多,“所有”需要做的就是修改我们从 Postgres 继承的 Bison 解析器(但我们正在替换它)。 这是之前的例子

SELECT fortytwo: 42;

前缀别名也适用于表名,例如

SELECT *
FROM my_table: some_other_table;

如果需要,可以使用双引号引用别名

SELECT "forty two": 42;

前缀别名几乎可以用于命名所有事物,例如 SELECT 子句中的表达式、函数调用和子查询

SELECT 
    e: 1 + 2, 
    f: len('asdf'), 
    s: (SELECT 42);

它们也可以应用于 FROM clause 中的函数调用和子查询,例如

SELECT *
FROM
    r: range(10),
    v: (VALUES (42)),
    s: (FROM range(10))

请注意,VALUES 子句和带有 FROM 的子查询需要额外的括号才能在此处工作,这是为了安抚邪恶的 Bison 解析器生成器所必需的。 让我们看看之前的 Q1 示例,但这次使用前缀别名

SELECT
    l_returnflag,
    l_linestatus,
    sum_qty:        sum(l_quantity),
    sum_base_price: sum(l_extendedprice),
    sum_disc_price: sum(l_extendedprice * (1-l_discount)),
    sum_charge:     sum(l_extendedprice * (1-l_discount) * (1+l_tax)),
    avg_qty:        avg(l_quantity),
    avg_price:      avg(l_extendedprice),
    avg_disc:       avg(l_discount),
    count_order:    count(*) 
    ...

使用 :AS 之间没有语义差异,它们会产生相同的查询结构。

鸣谢

这个想法归功于 Looker 的资深人士 Michael Toy。 我们感谢他的建议。 我们还要感谢传奇人物 Lloyd Tabb 向我们推荐 Michael 的想法。 还可以看看 Mark Needham 的 关于 DuckDB 前缀别名的视频