用户自定义函数 (UDF) 允许用户将 ClickHouse 的功能扩展到其内置的一千多种函数之外。
在 ClickHouse Cloud 中,有多种方式可以创建和管理用户自定义函数:
SQL 用户自定义函数
可以使用 CREATE FUNCTION 语句,基于 lambda 表达式创建 SQL UDF。
在此示例中,我们将创建一个简单的可执行用户自定义函数 isBusinessHours。
该函数会检查某个时间戳是否处于正常营业时间内;如果是则返回 true,否则返回 false。
- 登录 Cloud Console 并打开 SQL 控制台
- 编写以下 SQL 查询以创建
isBusinessHours函数:
CREATE FUNCTION isBusinessHours AS (ts) ->
toDayOfWeek(ts) BETWEEN 1 AND 5
AND toHour(ts) BETWEEN 9 AND 17;- 运行以下内容,测试你刚创建的 UDF:
SELECT isBusinessHours('2026-03-20 10:00:00'::DateTime), isBusinessHours('2026-03-20 23:00:00'::DateTime);你应该会看到如下结果:
1 0- 您可以使用
DROP FUNCTION命令删除刚刚创建的 UDF:
DROP FUNCTION isBusinessHours这意味着:
- 会话级设置 (通过
SET语句设置) 不会传递到 UDF 的执行上下文中 - 用户 profile 中的设置不会被 UDFs 继承
- 查询级设置在 UDF 执行期间不生效
通过 UI 创建的用户自定义函数
ClickHouse Cloud 提供了可通过 UI 创建用户自定义函数的配置界面。
在本示例中,我们将创建与前文相同的简单可执行用户自定义函数 isBusinessHours,用于检查某个时间戳是否落在正常工作时间内。
此前我们是通过 SQL 创建它的,这次则改用 Python 创建,并通过 UI 进行配置。
创建 Python 文件
在本地新建一个 main.py 文件:
cat > main.py << 'EOF'
import sys
from datetime import datetime
for line in sys.stdin:
ts = datetime.fromisoformat(line.strip())
result = 1 if (0 <= ts.weekday() <= 4 and 9 <= ts.hour <= 17) else 0
print(result)
sys.stdout.flush()
EOF如果你的 Python 脚本导入了第三方包,请在 requirements.txt 文件中列出这些依赖项,ClickHouse Cloud 会为你安装。你也可以选择直接将依赖项打包到 ZIP 中,但这样就必须同时包含适用于两种 CPU 架构的缓存包,因此使用 requirements.txt 更简单。例如:
requests>=2.28.0
numpy>=1.23.0打包依赖项和本地文件
要包含依赖包以及其他本地文件 (例如 wheel 文件、配置文件或数据文件) ,请将它们放在与您的 main.py 和 requirements.txt 相同的目录中。创建 ZIP 归档时,请包含所有文件:
zip is_business_hours.zip main.py requirements.txt你可以在 Python 代码中使用 os.path.dirname(os.path.abspath(__file__)) 引用本地打包路径的基目录。它会返回 ZIP 归档中 main.py 所在目录的绝对路径,以便你访问其他一并打包的文件:
import os
# Get the base directory of the bundled files
base_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(base_dir, 'config.json')在你需要执行以下操作时,这会很有用:
- 访问随 UDF 一起打包的配置文件
- 为自定义依赖加载 wheel 包
- 引用其他脚本或数据文件
现在将该文件压缩为 ZIP 压缩包:
zip is_business_hours.zip main.py通过 UI 创建 UDF
- 在 Cloud 控制台主页上,点击左下角菜单中的组织名称。
- 从菜单中选择用户自定义函数。
- 在用户自定义函数页面中,点击设置 UDF。屏幕右侧会打开一个配置面板。
- 输入函数名称。本示例使用
isBusinessHours。 - 选择函数类型,可选 Executable pool 或 Executable:
- Executable pool:系统会维护一个持久进程池,并从池中取出进程来处理读取请求。
- Executable:脚本会在每次查询时运行。
- 本示例使用默认设置。有关完整的配置参数列表,请参见可执行用户自定义函数。
- 点击浏览文件,上传在本教程开头创建的
.zip文件。 - 添加一个新参数。本示例中,添加一个类型为
DateTime的参数timestamp。 - 选择返回类型。本示例中,选择
Bool。 - 点击创建 UDF。此时会显示一个对话框,展示当前构建状态。
- 如果出现任何问题,状态将变为错误。
- 否则,状态会从构建中变为预配中。你的服务必须处于唤醒状态才能完成预配。如果服务处于空闲状态,请在服务名称旁的 UDF 详细信息 面板中点击唤醒服务。
- 完成后,状态将变为已部署。
测试你的 UDF
- 点击页面左上角的 Settings - 返回到服务视图,返回 SQL 控制台主页
- 点击左侧菜单中的 SQL 控制台
- 输入以下查询:
SELECT isBusinessHours('2026-03-20 10:00:00'::DateTime), isBusinessHours('2026-03-20 23:00:00'::DateTime);你应该会看到如下结果:
true false创建新版本
如需更改 UDF 的代码,请创建一个新版本。Edit 面板仅用于管理 UDF 分配给哪些服务;在此处上传文件不会替换已部署的代码。
- 在 Cloud Console 首页,点击左下角菜单中的组织名称。
- 在菜单中选择 用户自定义函数。
- 在
isBusinessHoursUDF 的 操作 一栏中点击三点图标,然后点击 创建新版本 - 上传包含修改后代码的 ZIP 压缩包,或修改设置,然后点击 创建新版本
你已成功通过 UI 添加了第一个用户自定义函数,确认其可正常运行,并了解了在需要时如何创建新版本。
使用 Cloud API 管理 UDF
UI 中提供的所有功能也可通过 ClickHouse Cloud API 以编程方式使用。 UDF 端点可让您通过脚本管理 UDF 的完整生命周期:上传源代码归档文件、创建函数和版本、将其附加到服务,以及进行清理。
通过 API 创建和部署 UDF 的典型工作流如下:
- 创建上传 URL 以获取预签名的
application/zip上传 URL,然后将 ZIP 归档文件上传至该 URL。每个上传 ID 只能用于一次创建 UDF 或创建版本的尝试;重试时请申请新的上传 URL。 - 从上传的归档文件创建 UDF,并指定函数名称、运行时、参数和返回类型。
- 将 UDF 附加到服务。如果省略版本,则会附加最新的就绪版本。服务必须处于运行状态;可先唤醒空闲服务。
完整端点列表如下:
| 端点 | 描述 |
|---|---|
| 创建 UDF 上传 URL | 创建组织范围的预签名 application/zip 上传 URL |
| 创建 UDF | 从已上传的归档文件创建新的 UDF |
| 列出 UDF | 返回组织中每个 UDF 的最新版本 |
| 获取 UDF | 返回某个 UDF 的最新版本 |
| 删除 UDF | 删除 UDF 的所有版本,并将其从所有服务中分离 |
| 创建 UDF 版本 | 使用源代码归档文件创建版本,并启动 UDF 构建 |
| 列出 UDF 版本 | 返回 UDF 的所有版本 |
| 删除 UDF 版本 | 删除未附加到任何服务的 UDF 版本 |
| 将 UDF 附加到服务 | 将一个 UDF 版本附加到服务,必要时替换当前版本 |
| 列出 UDF 附加关系 | 返回 UDF 当前附加到各服务的情况 |
| 获取 UDF 附加关系 | 返回 UDF 当前附加到某个服务的情况 |
| 从服务中分离 UDF | 将 UDF 从服务中分离 |
有关请求和响应 schema,请参阅 UDF API 参考文档。
使用 Terraform 管理 UDF
官方 ClickHouse Terraform provider 提供了两个资源,可用于以基础设施即代码的方式管理 UDF:
clickhouse_udf用于管理函数本身。它接收包含函数源代码的 ZIP 归档文件,并在归档文件的哈希发生变化时发布新版本,同时等待构建完成。clickhouse_udf_attachment用于将 UDF 版本附加到服务。一个服务在同一时间最多只能使用一个函数版本。您可以固定指定版本号,也可以引用clickhouse_udf.<name>.version,以自动将服务升级到最新版本。
例如,要使用 Terraform 部署前文示例中的 isBusinessHours UDF:
resource "clickhouse_udf" "is_business_hours" {
function_name = "isBusinessHours"
runtime = "python3.11"
type = "executable_pool"
return_type = "Bool"
arguments = [
{ name = "timestamp", type = "DateTime" },
]
source_archive_path = "${path.module}/is_business_hours.zip"
source_archive_hash = filebase64sha256("${path.module}/is_business_hours.zip")
}
resource "clickhouse_udf_attachment" "production" {
function_name = clickhouse_udf.is_business_hours.function_name
service_id = var.service_id
version = clickhouse_udf.is_business_hours.version
}仅当版本处于就绪状态时,附加操作才能成功,且可能需要数分钟;空闲服务会自动唤醒。删除 clickhouse_udf 资源会移除该函数的所有版本,并将其从所有服务中分离。