别只盯着性能优化了:2025 年 ClickHouse 被低估的新函数盘点
ClickHouse 2025版本新增了119个函数,涵盖数据处理、空间计算、加密认证等多个领域。

本文字数:10706;估计阅读时间:27 分钟
作者:Mark Needham
本文在公众号【ClickHouseInc】首发

原标题:2025 年你可能忽略的新函数
我和同事 Tom Schreiber 会在每次 ClickHouse 版本发布之后撰写一篇博客文章,重点介绍该版本中的重要变化,例如新增的数据湖目录或 join 性能方面的改进。
不过,在每个版本中,往往还会包含一些我们没有机会详细介绍的新函数。在这篇博客文章中,我将挑选一些 2025 年引入、但你可能没有注意到的新函数进行介绍。
2025 年一共引入了多少个函数?
首先,你是否知道,可以通过运行下面的查询来统计 2025 年一共新增了多少个函数:
select count()
FROM system.functions
WHERE introduced_in LIKE '25%';
┌─count()─┐
│ 119 │
└─────────┘
我们也可以进一步统计每个版本分别引入了多少个函数。为此,我们需要借用一个用于对语义版本号进行排序的用户自定义函数:
CREATE FUNCTION sortableSemVer AS version ->
arrayMap(
x -> toUInt32OrZero(x),
splitByChar('.', extract(version, '(d+(.d+)+)'))
);
接下来,我们就可以编写如下查询:
SELECT introduced_in, count()
FROM system.functions
WHERE introduced_in LIKE '25%'
GROUP BY ALL
ORDER BY sortableSemVer(introduced_in);
┌─introduced_in─┬─count()─┐
│ 25.1 │ 2 │
│ 25.2 │ 2 │
│ 25.3 │ 3 │
│ 25.4 │ 7 │
│ 25.5 │ 18 │
│ 25.6 │ 17 │
│ 25.7 │ 25 │
│ 25.8 │ 18 │
│ 25.9 │ 8 │
│ 25.10 │ 10 │
│ 25.11 │ 7 │
│ 25.12 │ 2 │
└───────────────┴─────────┘
如果想要获取函数的完整列表,可以运行下面的查询:
SELECT
name,
introduced_in AS version,
if(length(description) > 80,
substring(description, 1, 80) || '...',
description) AS description
FROM system.functions
WHERE introduced_in LIKE '25%'
ORDER BY sortableSemVer(introduced_in);
为了便于展示,这里的结果仅保留了前十行:
Row 1:
──────
name: variantElement
version: 25.2
description:
Extracts a column with specified type from a `Variant` column.
Row 2:
──────
name: numericIndexedVectorPointwiseMultiply
version: 25.7
description:
Performs pointwise multiplication between a numericIndexedVector and either ano...
Row 3:
──────
name: __patchPartitionID
version: 25.5
description:
Internal function. Receives the name of a part and a hash of patch part's colum...
Row 4:
──────
name: readWKBPolygon
version: 25.5
description:
Parses a Well-Known Binary (WKB) representation of a Polygon ge...
Row 5:
──────
name: initialQueryStartTime
version: 25.4
description:
Returns the start time of the initial current query.
`initialQueryStartTime` re...
下面让我们来看看其中一些新函数!
mapContainsValueLike
mapContainsValueLike 是在 ClickHouse 25.5 中引入的,用于检查某个 map 中是否存在一个值,该值可以通过 LIKE 运算符匹配指定的模式。
例如,假设我们有下面这样一个查询,它会返回公司名称以及对应的用例详情:
SELECT
'Netflix' AS company,
map('use_case', 'streaming analytics', 'scale', '5 petabytes daily') AS details
UNION ALL
SELECT
'Tesla',
map('use_case', 'observability platform', 'scale', 'quadrillion rows', 'feature', 'vector search')
UNION ALL
SELECT
'Anthropic',
map('use_case', 'AI observability', 'scale', 'billions of events')
UNION ALL
SELECT
'Uber',
map('use_case', 'ride analytics', 'scale', 'petabyte scale')
FORMAT Vertical;
Row 1:
──────
company: Netflix
details: {'use_case':'streaming analytics','scale':'5 petabytes daily'}
Row 2:
──────
company: Tesla
details: {'use_case':'observability platform','scale':'quadrillion rows','feature':'vector search'}
Row 3:
──────
company: Anthropic
details: {'use_case':'AI observability','scale':'billions of events'}
Row 4:
──────
company: Uber
details: {'use_case':'ride analytics','scale':'petabyte scale'}
接着,我们可以编写如下查询,用来检查 map 中是否有任意值包含 obser、petabyte 或 vector 这些关键词:
WITH useCases AS (
SELECT
'Netflix' AS company,
map('use_case', 'streaming analytics', 'scale', '5 petabytes daily') AS details
UNION ALL
SELECT
'Tesla',
map('use_case', 'observability platform', 'scale', 'quadrillion rows', 'feature', 'vector search')
UNION ALL
SELECT
'Anthropic',
map('use_case', 'AI observability', 'scale', 'billions of events')
UNION ALL
SELECT
'Uber',
map('use_case', 'ride analytics', 'scale', 'petabyte scale')
)
SELECT
company,
details,
mapContainsValueLike(details, '%observ%') AS is_observability,
mapContainsValueLike(details, '%petabyte%') AS petabyte_scale,
mapContainsValueLike(details, '%vector%') AS has_vector_search
FROM useCases
FORMAT Vertical;
执行后,我们会得到如下结果:
Row 1:
──────
company: Netflix
details: {'use_case':'streaming analytics','scale':'5 petabytes daily'}
is_observability: 0
petabyte_scale: 1
has_vector_search: 0
Row 2:
──────
company: Tesla
details: {'use_case':'observability platform','scale':'quadrillion rows','feature':'vector search'}
is_observability: 1
petabyte_scale: 0
has_vector_search: 1
Row 3:
──────
company: Anthropic
details: {'use_case':'AI observability','scale':'billions of events'}
is_observability: 1
petabyte_scale: 0
has_vector_search: 0
Row 4:
──────
company: Uber
details: {'use_case':'ride analytics','scale':'petabyte scale'}
is_observability: 0
petabyte_scale: 1
has_vector_search: 0
perimeterCartesian
perimeterCartesian 是在 ClickHouse 25.10 中新增的函数,用于在笛卡尔 (平面) 坐标系中计算指定 Geometry 对象的周长。
下面我们来看一个示例,演示它在计算正方形周长时的效果:
SELECT perimeterCartesian(readWKT('POLYGON((0 0,1 0,1 1,0 1,0 0))'));
┌─perimeterCar⋯ 1,0 0))'))─┐
│ 4 │
└──────────────────────────┘
此外,还有 perimeterSpherical,它用于计算位于球面上的 Geometry 对象的周长。因此,如果我们想要计算环绕伦敦的 M25 高速公路的周长,就可以使用 perimeterSpherical:
WITH
readWKT('POLYGON((0.13870239257812503 51.2968127854147, 0.16342163085937503 51.37403072457134, 0.212860107421875 51.41516045575089, 0.27053833007812506 51.483627853536014, 0.27328491210937506 51.54686881000932, 0.25405883789062506 51.633894901713354, 0.13870239257812503 51.67308742846449, 0.08102416992187501 51.695224736990404, -0.023345947265625003 51.68500886266592, -0.12222290039062501 51.69352225137908, -0.29525756835937506 51.71224607096211, -0.37490844726562506 51.71905281158759, -0.44631958007812506 51.68330599278565, -0.49850463867187506 51.64412230646439, -0.5259704589843751 51.55028473901506, -0.5039978027343751 51.51440469156115, -0.5369567871093751 51.44255973575031, -0.5177307128906251 51.37403072457134, -0.41061401367187506 51.30883300776494, -0.29525756835937506 51.30539897974217, -0.15243530273437503 51.272762896039936, 0.04531860351562501 51.272762896039936, 0.13870239257812503 51.2968127854147))') AS m25,
perimeterSpherical(m25) AS per_rad
SELECT
per_rad,
per_rad * 6371000 AS per_meters,
per_rad * 6371 AS per_km;
该函数返回的是单位球面上的弧度长度,因此需要再乘以地球半径,才能得到以米或千米为单位的实际长度:
┌──────────────per_rad─┬─────────per_meters─┬─────────────per_km─┐
│ 0.027954722202348813 │ 178099.53515116428 │ 178.09953515116428 │
└──────────────────────┴────────────────────┴────────────────────┘
事实上,这条高速公路的直径约为 188 km,因此计算结果已经相当接近了 —— 那点误差就归咎于我画的多边形不够精确吧。
HMAC
HMAC (Hash-based Message Authentication Code) 是一种加密构造,用于同时验证消息的完整性与真实性。ClickHouse 25.12 新增了这一函数。
下面我们通过为单词 ‘ClickHouse’ 生成签名,来看一下它的使用方式。返回结果采用十六进制格式,因此我们会使用 hex 函数将其转换为字符串形式:
SELECT hex(HMAC('sha256', 'ClickHouse', 'mySecretKey'))
┌─hex(HMAC('sha256', 'ClickHouse', 'mySecretKey'))─────────────────┐
│ 5A79F3AA2874164CFD9811F9D1DBCEBE428C9BC52A7F57303EC6BAFCD6C9377B │
└──────────────────────────────────────────────────────────────────┘
消息 ‘ClickHouse’ 及其对应的签名随后可以发送给另一方。接收方可以使用自己持有的密钥副本计算 HMAC,并将计算结果与收到的签名进行比对,从而验证消息的真实性。
argAndMin and argAndMax
ClickHouse 25.11 引入了 argAndMax 和 argAndMin 函数。下面我们通过英国房产价格数据集来了解这两个函数的用法。
假设我们想要找出 2025 年成交价格最高的一套房产,可以编写如下查询:
SELECT max(price)
FROM uk_price_paid
WHERE toYear(date) = 2025;
┌─max(price)─┐
│ 127700000 │ -- 127.70 million
└────────────┘
如果我们还想知道这个最高价格对应的是哪个城镇,该怎么做呢?这时可以使用 argMax 函数:
SELECT argMax(town, price)
FROM uk_price_paid
WHERE toYear(date) = 2025;
┌─argMax(town, price)─┐
│ PURFLEET-ON-THAMES │
└─────────────────────┘
argAndMax 函数则可以一次性返回城镇名称以及对应的最高成交价格:
SELECT argAndMax(town, price)
FROM uk_price_paid
WHERE toYear(date) = 2025;
┌─argAndMax(town, price)───────────┐
│ ('PURFLEET-ON-THAMES',127700000) │
└──────────────────────────────────┘
同样地,我们也可以使用 argAndMin 来获取城镇以及对应的最低成交价格:
SELECT argAndMin(town, price)
FROM uk_price_paid
WHERE toYear(date) = 2025;
┌─argAndMin(town, price)─┐
│ ('CAMBRIDGE',100) │
└────────────────────────┘
这个结果看起来并不合理,因为在 2025 年以 100 英镑成交一套房产的可能性非常低。
sparseGrams
sparseGrams 是在 ClickHouse 25.5 中引入的函数,用于查找给定字符串中所有长度至少为 n 的子字符串。这些子字符串需要满足一个条件:其边界处的 (n-1)-gram 的哈希值必须严格大于该子字符串内部任意 (n-1)-gram 的哈希值。该函数使用 CRC32 作为哈希函数。
下面来看一个示例,看看它是如何工作的:
SELECT sparseGrams('ClickHouse') FORMAT Vertical;
Row 1:
──────
sparseGrams('ClickHouse'): ['Cli','lic','ick','lick','ckH','kHo','ckHo','lickHo','Hou','ous','Hous','use']
该函数由 GitHub 团队提出,在构建搜索索引时,可以作为传统 n-grams 的一种合适替代方案。
stringBytesUniq
stringBytesUniq 在 ClickHouse 25.6 中引入,用于统计一个字符串中不同字节的数量。下面来看几个示例:
SELECT
stringBytesUniq('ClickHouse') AS ch,
stringBytesUniq('Alexey Milovidov') AS alexey,
stringBytesUniq('AAAAA') AS a;
┌─ch─┬─alexey─┬─a─┐
│ 10 │ 11 │ 1 │
└────┴────────┴───┘
financialInternalRateOfReturn
financialInternalRateOfReturn 是在 ClickHouse 25.7 中新增的函数,用于计算按年度折算后的回报率 (例如某项投资的收益率)。
例如,假设我们在 2020 年以 113 美元买入 Apple 股票,在 2021、2022、2023 和 2024 年期间没有任何操作,并在 2025 年以 231 美元卖出。可以通过运行下面的查询来计算内部收益率:
SELECT financialInternalRateOfReturn([-113, 0, 0, 0, 0, 231])
┌─financialInt⋯0, 0, 231])─┐
│ 0.15373669910090634 │
└──────────────────────────┘
这相当于在 2020 年到 2025 年期间,每一年都获得了约 15% 的回报。我们可以通过运行下面的查询来验证这一点:
SELECT 113 * power(1.15373669910090634, 5);
┌─multiply(113⋯009064, 5))─┐
│ 230.99999999999997 │
└──────────────────────────┘
此外,还有一个相关函数 financialInternalRateOfReturnExtended,用于在现金流发生在不规则时间点 (即具体日期) 的情况下计算回报率。
因此,我们仍然使用 Apple 股票的示例,不过这一次改为使用买入和卖出时的具体日期:
SELECT financialInternalRateOfReturnExtended(
[-113, 231],
[toDate('2020-09-11'), toDate('2025-03-05')]
);
┌─financialInt⋯5-03-05')])─┐
│ 0.17295574412431242 │
└──────────────────────────┘
这一次计算得到的回报率略高于 17%。
toInterval
toInterval 是在 ClickHouse 25.4 中引入的函数,用于根据一个数值以及对应的单位字符串生成一个 interval 值。
此前已经存在用于完成该任务的多个独立函数 (例如 toIntervalSecond、toIntervalMinute、toIntervalDay 等),而 toInterval 将这些功能统一到了一个函数之中。
下面来看一下它的用法:
SELECT
toInterval(5, 'second') AS seconds,
toTypeName(seconds) AS secType,
toInterval(3, 'day') AS days,
toTypeName(days) AS daysType,
toInterval(2, 'month') AS months,
toTypeName(months) AS monthsType;
┌─seconds─┬─secType────────┬─days─┬─daysType────┬─months─┬─monthsType────┐
│ 5 │ IntervalSecond │ 3 │ IntervalDay │ 2 │ IntervalMonth │
└─────────┴────────────────┴──────┴─────────────┴────────┴───────────────┘
我们也可以使用该函数在现有的 DateTime 值基础上增加一段时间,如下所示:
WITH toDateTime('2025-12-17 12:32:12') AS currentTime
SELECT
currentTime,
currentTime + toInterval(7, 'day') + toInterval(23, 'hour') AS nextWeek;
┌─────────currentTime─┬────────────nextWeek─┐
│ 2025-12-17 12:32:12 │ 2025-12-25 11:32:12 │
└─────────────────────┴─────────────────────
timeSeriesRange
timeSeriesRange 是在 25.8 版本中引入的函数,用于生成一段时间戳范围。它的作用类似于 range 函数,但针对的是 DateTime 类型。
该函数会返回一个数组结果,不过我们可以借助 arrayJoin 函数将数组展开为多行记录:
SELECT arrayJoin(
timeSeriesRange(
'2025-06-01 00:00:00'::DateTime,
'2025-06-01 00:01:00'::DateTime,
10
)) AS ts;
┌──────────────────ts─┐
│ 2025-06-01 00:00:00 │
│ 2025-06-01 00:00:10 │
│ 2025-06-01 00:00:20 │
│ 2025-06-01 00:00:30 │
│ 2025-06-01 00:00:40 │
│ 2025-06-01 00:00:50 │
│ 2025-06-01 00:01:00 │
└─────────────────────┘
接下来,就可以计算距离这些时间点分别已经过去了多长时间
WITH toDateTime('2025-12-17 12:32:12') AS currentTime
SELECT arrayJoin(
timeSeriesRange(
'2025-06-01 00:00:00'::DateTime,
'2025-06-01 00:01:00'::DateTime,
10
)) AS ts,
formatReadableTimeDelta(now() - ts) AS timeAgo;
┌──────────────────ts─┬─timeAgo────────────────────────────────────────────────┐
│ 2025-06-01 00:00:00 │ 6 months, 16 days, 14 hours, 10 minutes and 41 seconds │
│ 2025-06-01 00:00:10 │ 6 months, 16 days, 14 hours, 10 minutes and 31 seconds │
│ 2025-06-01 00:00:20 │ 6 months, 16 days, 14 hours, 10 minutes and 21 seconds │
│ 2025-06-01 00:00:30 │ 6 months, 16 days, 14 hours, 10 minutes and 11 seconds │
│ 2025-06-01 00:00:40 │ 6 months, 16 days, 14 hours, 10 minutes and 1 second │
│ 2025-06-01 00:00:50 │ 6 months, 16 days, 14 hours, 9 minutes and 51 seconds │
│ 2025-06-01 00:01:00 │ 6 months, 16 days, 14 hours, 9 minutes and 41 seconds │
└─────────────────────┴────────────────────────────────────────────────────────┘
征稿启示
面向社区长期正文,文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出&图文并茂。质量合格的文章将会发布在本公众号,优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至:Tracy.Wang@clickhouse.com

更多推荐
所有评论(0)