跳转到主内容
跳转到主内容

常规函数

函数至少有*两种类型 —— 常规函数 (通常就直接称为“函数”) 和聚合函数。这是完全不同的概念。常规函数的工作方式就像是对每一行单独应用 (对于每一行,函数的结果不依赖于其他行) 。聚合函数则会从多行中累积一组值 (即它们依赖于整组行) 。

本节讨论的是常规函数。关于聚合函数,请参阅“聚合函数”一节。

注意

还有第三类函数,其中 'arrayJoin' 函数就属于该类。另外,表函数也可以单独归为一类。

强类型

与标准 SQL 不同,ClickHouse 使用强类型。换句话说,它不会在类型之间进行隐式转换。每个函数仅适用于特定的一组类型。因此,有时需要使用类型转换函数。

公共子表达式消除

查询中所有具有相同 AST (相同的抽象语法树结构或相同的语法解析结果) 的表达式都被视为具有相同的值。此类表达式会被合并后只执行一次。相同的子查询也会以这种方式被消除。

结果类型

所有函数都返回单个值作为结果 (既不会返回多个值,也不会不返回值) 。结果类型通常仅由参数类型决定,而不是由参数值决定。例外是 tupleElement 函数 (a.N 运算符) 和 toFixedString 函数。

常量

为简化处理,某些函数在部分参数上只能使用常量。例如,LIKE 运算符的右侧参数必须是常量。 几乎所有函数在其参数是常量时都会返回常量。例外是生成随机数的函数。 now 函数对于在不同时间运行的查询会返回不同的值,但结果仍被视为常量,因为不变性只在单个查询内部才重要。 常量表达式也被视为常量 (例如,LIKE 运算符的右半部分可以由多个常量拼接而成) 。

对于常量参数和非常量参数,函数可以采用不同的实现方式 (执行不同的代码) 。但是,对于一个常量和一个仅包含相同值的实际列,它们的计算结果应当一致。

NULL 处理

函数具有以下行为:

  • 如果函数的至少一个参数为 NULL,则函数结果也为 NULL
  • 某些函数具有在各自描述中单独说明的特殊行为。在 ClickHouse 源代码中,这些函数将 UseDefaultImplementationForNulls 设为 false

不变性

函数不能更改其参数的值——任何修改都会通过返回结果体现出来。因此,单独计算各个函数的结果与这些函数在查询中的书写顺序无关。

高阶函数

-> 运算符和 lambda(params, expr) 函数

高阶函数只能接受 lambda 函数作为其函数型参数。要将 lambda 函数传递给高阶函数,请使用 -> 运算符。箭头左侧是形式参数,可以是任意 ID,或者多个形式参数——元组中的任意 ID。箭头右侧是一个表达式,该表达式可以使用这些形式参数以及任意表的列。

示例:

x -> 2 * x
str -> str != Referer

接收多个参数的 lambda 函数也可以作为参数传递给高阶函数。在这种情况下,会将若干长度相同的数组传递给高阶函数,这些参数分别对应这些数组中的元素。

对于某些函数,可以省略第一个参数 (lambda 函数) 。在这种情况下,默认认为执行的是恒等映射。

将函数名直接用作 lambda

无需编写完整的 lambda 表达式,可以直接将函数名传给高阶函数。函数名会自动转换为等价的 lambda 表达式。

例如,以下各组写法是等价的:

SELECT arrayMap(negate, [1, 2, 3]);            -- [-1, -2, -3]
SELECT arrayMap(x -> negate(x), [1, 2, 3]);    -- [-1, -2, -3]

SELECT arrayMap(plus, [1, 2, 3], [10, 20, 30]);            -- [11, 22, 33]
SELECT arrayMap((x, y) -> plus(x, y), [1, 2, 3], [10, 20, 30]); -- [11, 22, 33]

SELECT arrayFilter(isNotNull, [1, NULL, 3, NULL, 5]);            -- [1, 3, 5]
SELECT arrayFilter(x -> isNotNull(x), [1, NULL, 3, NULL, 5]);    -- [1, 3, 5]

SELECT arrayFold(plus, [1, 2, 3, 4, 5], toUInt64(0));                      -- 15
SELECT arrayFold((acc, x) -> plus(acc, x), [1, 2, 3, 4, 5], toUInt64(0));  -- 15

这适用于内置函数、SQL UDF、可执行 UDF 和 WebAssembly UDF。存在歧义时,列名和别名的优先级高于函数名。

lambda 的元数由内部函数决定。例如,arrayMap(plus, ...) 使用 2 元,因为 plus 接受两个参数,因此它也适用于元组输入,例如 arrayMap(plus, [(1, 10), (2, 20)]),其中元组元素会被解包并传入 lambda 参数。

对于可变参数的内部函数 (例如 concat,可接受任意数量的参数) ,lambda 的元数会退回到数组参数的数量。这对于 arrayMaparrayFilterarrayFold 这类高阶函数是正确的。对于除数组外还接受固定非数组参数的高阶函数——例如 arrayPartialSort(f, limit, arr)——直接使用可变参数函数名可能会得到错误的元数,此时需要显式 lambda。

可变参数的内部函数也不会自动解包元组输入。例如,arrayMap(concat, [('a', 'b'), ('c', 'd')]) 会被改写为一元 lambda,因此它并不等同于 arrayMap((x, y) -> concat(x, y), [('a', 'b'), ('c', 'd')])。如果要将元组元素解构后传给可变参数调用,请使用显式 lambda。

用户自定义函数 (UDF)

ClickHouse 支持用户自定义函数 (UDF) 。请参阅用户自定义函数 (UDF)