描述
pg_clickhouse 是一个 PostgreSQL 扩展,可在 ClickHouse 数据库上远程执行查询, 其中包括一个[外部数据包装器]。它支持 PostgreSQL 13 及以上版本,以及 ClickHouse 23.3 及以上版本。
快速开始
试用 pg_clickhouse 最简单的方式是使用 [Docker 镜像],其中包含 标准的 PostgreSQL Docker 镜像,并集成了 pg_clickhouse 和 [re2][re2 扩展] 扩展:
docker run --name pg_clickhouse -e POSTGRES_PASSWORD=my_pass \
-d ghcr.io/clickhouse/pg_clickhouse:18
docker exec -it pg_clickhouse psql -U postgres请参阅教程,开始了解如何导入 ClickHouse 表并下推查询。
用法
CREATE EXTENSION pg_clickhouse;
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (user 'default');
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA taxi FROM SERVER taxi_srv INTO taxi;版本策略
pg_clickhouse 的公开发布版本遵循[语义化版本控制]。
- API 发生变更时,递增主版本号
- SQL 发生向后兼容的变更时,递增次版本号
- 仅有 binary 变更时,递增补丁版本号
安装后,PostgreSQL 会跟踪两种版本形式:
- 库版本 (在 PostgreSQL 18 及以上版本中由
PG_MODULE_MAGIC定义) 包含完整的语义化版本号,可在pgch_version()函数的输出或 Postgrespg_get_loaded_modules()函数中查看。 - 扩展版本 (在控制文件中定义) 仅包含主版本号和次版本号,可在
pg_catalog.pg_extension表、pg_available_extension_versions()函数的输出以及\dx pg_clickhouse中查看。
在实际使用中,这意味着补丁版本号递增的发布 (例如从 v0.1.0 升级到 v0.1.1) 会让所有已加载 v0.1 的数据库直接受益,无需运行 ALTER EXTENSION 即可获得升级内容。
相反,次版本号或主版本号递增的发布会附带 SQL 升级脚本,所有包含该扩展的现有数据库都必须运行 ALTER EXTENSION pg_clickhouse UPDATE 才能获得升级内容。
DDL SQL 参考
以下 SQL DDL 表达式使用 pg_clickhouse。
CREATE EXTENSION
使用 CREATE EXTENSION 将 pg_clickhouse 扩展添加到数据库中:
CREATE EXTENSION pg_clickhouse;使用 WITH SCHEMA 将其安装到指定的 schema 中 (推荐) :
CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;ALTER EXTENSION
使用 ALTER EXTENSION 修改 pg_clickhouse。示例:
-
安装 pg_clickhouse 的新 release 后,使用
UPDATE子句:ALTER EXTENSION pg_clickhouse UPDATE; -
使用
SET SCHEMA将该扩展移动到新的 schema:CREATE SCHEMA ch; ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
DROP EXTENSION
使用 DROP EXTENSION 从数据库中删除 pg_clickhouse:
DROP EXTENSION pg_clickhouse;如果存在任何依赖于 pg_clickhouse 的对象,此命令会失败。请使用
CASCADE 子句将它们一并删除:
DROP EXTENSION pg_clickhouse CASCADE;CREATE SERVER
使用 CREATE SERVER 创建连接到 ClickHouse 服务器的外部服务器。示例:
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');支持的选项有:
driver:要使用的 ClickHouse 连接驱动程序,可以是 "binary" 或 "http"。必填。compression:binary driver 的 native 协议压缩,可选值为 "none"、"lz4" 或 "zstd"。默认为 "lz4"。http 驱动会忽略此选项。dbname:连接时使用的 ClickHouse 数据库。默认为 "default"。host:ClickHouse server 的主机名。默认为 "localhost";port:连接到 ClickHouse server 时使用的端口。默认值 如下:- 如果
driver为 "binary" 且host是 ClickHouse Cloud 主机,则为 9440 - 如果
driver为 "binary" 且host不是 ClickHouse Cloud 主机,则为 9004 - 如果
driver为 "http" 且host是 ClickHouse Cloud 主机,则为 8443 - 如果
driver为 "http" 且host不是 ClickHouse Cloud 主机,则为 8123
- 如果
min_tls_version:对使用 TLS 的连接协商的最低 TLS 协议版本。 可选值为TLSv1、TLSv1.1、TLSv1.2或TLSv1.3。默认值 为 TLS 库自身的最低版本。适用于两种驱动程序。secure:控制连接是否使用 TLS。可选值为:auto(默认) :当host是 ClickHouse Cloud 主机或port是安全端口时使用 TLS;否则使用 plaintext。on(或true/yes/1) :始终使用 TLS。默认port为 8443 ("http") 或 9440 ("binary") 。off(或false/no/0) :从不使用 TLS。默认port为 8123 ("http") 或 9000 ("binary") 。
ALTER SERVER
使用 ALTER SERVER 修改 外部服务器。示例:
ALTER SERVER taxi_srv OPTIONS (SET driver 'http');这些选项与 CREATE SERVER 中的选项相同。
DROP SERVER
使用 DROP SERVER 删除 外部服务器:
DROP SERVER taxi_srv;如果有任何其他对象依赖于该服务器,此命令将失败。使用 CASCADE 还会
一并删除这些依赖对象:
DROP SERVER taxi_srv CASCADE;CREATE USER MAPPING
使用 CREATE USER MAPPING 将 PostgreSQL 用户映射为 ClickHouse 用户。例如,使用 taxi_srv 外部服务器 连接时,可将当前 PostgreSQL 用户映射为远程 ClickHouse 用户:
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (user 'demo');支持的选项如下:
user:ClickHouse 用户名。默认为 "default"。password:ClickHouse 用户的密码。
ALTER USER MAPPING
使用 ALTER USER MAPPING 修改用户映射的定义:
ALTER USER MAPPING FOR CURRENT_USER SERVER taxi_srv
OPTIONS (SET user 'default');这些选项与 CREATE USER MAPPING 中的选项相同。
DROP USER MAPPING
使用 DROP USER MAPPING 移除用户映射:
DROP USER MAPPING FOR CURRENT_USER SERVER taxi_srv;IMPORT FOREIGN SCHEMA
使用 IMPORT FOREIGN SCHEMA 可将 ClickHouse 数据库中定义的所有表作为外部表导入到 PostgreSQL schema 中:
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA demo FROM SERVER taxi_srv INTO taxi;使用 LIMIT TO 将导入限制到特定表:
IMPORT FOREIGN SCHEMA demo LIMIT TO (trips) FROM SERVER taxi_srv INTO taxi;使用 EXCEPT 排除这些表:
IMPORT FOREIGN SCHEMA demo EXCEPT (users) FROM SERVER taxi_srv INTO taxi;pg_clickhouse 将获取指定 ClickHouse 数据库 (上述示例中的 "demo") 中所有表的列表,获取每个表的列定义, 并执行 CREATE FOREIGN TABLE 命令来创建 外部表。列将使用支持的数据 类型进行定义,并在可检测的情况下使用 CREATE FOREIGN TABLE 支持的选项。
CREATE FOREIGN TABLE
使用 CREATE FOREIGN TABLE 创建可查询 ClickHouse 数据库中数据的外部表:
CREATE FOREIGN TABLE acts (
user_id bigint NOT NULL,
page_views int,
duration smallint,
sign smallint
) SERVER taxi_srv OPTIONS(
table_name 'acts'
engine 'CollapsingMergeTree(sign)'
);支持的表选项如下:
database:远程 数据库 的名称。默认使用为 外部服务器 定义的 数据库。table_name:远程表的名称。默认使用为 foreign table 指定的名称。engine:ClickHouse 表使用的[表引擎]。对于CollapsingMergeTree()和AggregatingMergeTree(),pg_clickhouse 会自动将参数应用于在该表上执行的函数表达式。
对于每一列,请使用与远程 ClickHouse 数据 类型相对应的数据类型。支持的列选项如下:
-
column_name:ClickHouse 端的列名,在反解析查询和 插入语句时,会优先使用该名称而不是 PostgreSQL attribute 名称。 这对于将未加引号的小写 PostgreSQL 列名映射到 区分大小写的 ClickHouse 列很有用,例如:CREATE FOREIGN TABLE hits ( watchid bigint OPTIONS(column_name 'WatchID'), javaenable smallint OPTIONS(column_name 'JavaEnable'), title text OPTIONS(column_name 'Title') ) SERVER taxi_srv OPTIONS(table_name 'hits'); -
AggregateFunction:应用于 AggregateFunction Type 列的聚合函数名称。将数据类型映射为传递给该函数的 ClickHouse 类型,并通过相应的列选项指定 聚合函数名称,pg_clickhouse 会自动在计算该列的聚合函数后追加Merge。CREATE FOREIGN TABLE test ( column1 bigint OPTIONS(AggregateFunction 'uniq'), column2 integer OPTIONS(AggregateFunction 'anyIf'), column3 bigint OPTIONS(AggregateFunction 'quantiles(0.5, 0.9)') ) SERVER clickhouse_srv; -
SimpleAggregateFunction:应用于 SimpleAggregateFunction Type 列的聚合函数名称。将数据类型映射为 传递给该函数的 ClickHouse 类型,并通过相应的 列选项指定聚合函数名称。
ALTER FOREIGN TABLE
使用 ALTER FOREIGN TABLE 修改外部表的定义:
ALTER TABLE table ALTER COLUMN b OPTIONS (SET AggregateFunction 'count');支持的表和列选项与 CREATE FOREIGN TABLE 中的相同。
DROP FOREIGN TABLE
使用 DROP FOREIGN TABLE 删除外部表:
DROP FOREIGN TABLE acts;如果有任何对象依赖于该外部表,此命令将失败。
使用 CASCADE 子句也会将它们一并删除:
DROP FOREIGN TABLE acts CASCADE;DML SQL 参考
下面的 SQL DML 表达式可能会用到 pg_clickhouse。示例基于 以下 ClickHouse 表:
CREATE TABLE logs (
req_id Int64 NOT NULL,
start_at DateTime64(6, 'UTC') NOT NULL,
duration Int32 NOT NULL,
resource Text NOT NULL,
method Enum8('GET' = 1, 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', 'QUERY') NOT NULL,
node_id Int64 NOT NULL,
response Int32 NOT NULL
) ENGINE = MergeTree
ORDER BY start_at;
CREATE TABLE nodes (
node_id Int64 NOT NULL,
name Text NOT NULL,
region Text NOT NULL,
arch Text NOT NULL,
os Text NOT NULL
) ENGINE = MergeTree
PRIMARY KEY node_id;EXPLAIN
EXPLAIN 命令可按预期运行,但 VERBOSE 选项会触发输出
ClickHouse 的 "Remote SQL" 查询:
try=# EXPLAIN (VERBOSE)
SELECT resource, avg(duration) AS average_duration
FROM logs
GROUP BY resource;
QUERY PLAN
------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=64)
Output: resource, (avg(duration))
Relations: Aggregate on (logs)
Remote SQL: SELECT resource, avg(duration) FROM "default".logs GROUP BY resource
(4 rows)此查询会通过 "Foreign Scan" 计划节点下推到 ClickHouse,作为远程 SQL 执行。
SELECT
使用 SELECT 语句在 pg_clickhouse 表上执行查询,与在任何其他表上执行查询无异:
try=# SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
start_at | duration | resource
----------------------------+----------+----------------
2025-12-05 15:07:32.944188 | 175 | /widgets/totem
(1 row)pg_clickhouse 会尽可能将查询执行下推到 ClickHouse,包括聚合函数。使用 EXPLAIN 可确定 下推的范围。例如,对于上述查询,所有执行都会下推到 ClickHouse
try=# EXPLAIN (VERBOSE, COSTS OFF)
SELECT start_at, duration, resource FROM logs WHERE req_id = 4117909262;
QUERY PLAN
-----------------------------------------------------------------------------------------------------
Foreign Scan on public.logs
Output: start_at, duration, resource
Remote SQL: SELECT start_at, duration, resource FROM "default".logs WHERE ((req_id = 4117909262))
(3 rows)pg_clickhouse 还会将同一远程服务器上的表之间的 JOIN 下推:
try=# EXPLAIN (ANALYZE, VERBOSE)
SELECT name, count(*), round(avg(duration))
FROM logs
LEFT JOIN nodes on logs.node_id = nodes.node_id
GROUP BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=72) (actual time=3.201..3.221 rows=8.00 loops=1)
Output: nodes.name, (count(*)), (round(avg(logs.duration), 0))
Relations: Aggregate on ((logs) LEFT JOIN (nodes))
Remote SQL: SELECT r2.name, count(*), round(avg(r1.duration), 0) FROM "default".logs r1 ALL LEFT JOIN "default".nodes r2 ON (((r1.node_id = r2.node_id))) GROUP BY r2.name
FDW Time: 0.086 ms
Planning Time: 0.335 ms
Execution Time: 3.261 ms
(7 rows)如果不经过仔细调优,与本地表连接会生成效率较低的查询。在这个示例中,我们创建了
nodes 表的一个本地副本,并与它连接,而不是连接远程表:
try=# CREATE TABLE local_nodes AS SELECT * FROM nodes;
SELECT 8
try=# EXPLAIN (ANALYZE, VERBOSE)
SELECT name, count(*), round(avg(duration))
FROM logs
LEFT JOIN local_nodes on logs.node_id = local_nodes.node_id
GROUP BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------
HashAggregate (cost=147.65..150.65 rows=200 width=72) (actual time=6.215..6.235 rows=8.00 loops=1)
Output: local_nodes.name, count(*), round(avg(logs.duration), 0)
Group Key: local_nodes.name
Batches: 1 Memory Usage: 32kB
Buffers: shared hit=1
-> Hash Left Join (cost=31.02..129.28 rows=2450 width=36) (actual time=2.202..5.125 rows=1000.00 loops=1)
Output: local_nodes.name, logs.duration
Hash Cond: (logs.node_id = local_nodes.node_id)
Buffers: shared hit=1
-> Foreign Scan on public.logs (cost=10.00..20.00 rows=1000 width=12) (actual time=2.089..3.779 rows=1000.00 loops=1)
Output: logs.req_id, logs.start_at, logs.duration, logs.resource, logs.method, logs.node_id, logs.response
Remote SQL: SELECT duration, node_id FROM "default".logs
FDW Time: 1.447 ms
-> Hash (cost=14.90..14.90 rows=490 width=40) (actual time=0.090..0.091 rows=8.00 loops=1)
Output: local_nodes.name, local_nodes.node_id
Buckets: 1024 Batches: 1 Memory Usage: 9kB
Buffers: shared hit=1
-> Seq Scan on public.local_nodes (cost=0.00..14.90 rows=490 width=40) (actual time=0.069..0.073 rows=8.00 loops=1)
Output: local_nodes.name, local_nodes.node_id
Buffers: shared hit=1
Planning:
Buffers: shared hit=14
Planning Time: 0.551 ms
Execution Time: 6.589 ms在这种情况下,我们可以通过
按 node_id 而不是本地列分组,将更多聚合下推到 ClickHouse,然后再
与查找表进行 join:
try=# EXPLAIN (ANALYZE, VERBOSE)
WITH remote AS (
SELECT node_id, count(*), round(avg(duration))
FROM logs
GROUP BY node_id
)
SELECT name, remote.count, remote.round
FROM remote
JOIN local_nodes
ON remote.node_id = local_nodes.node_id
ORDER BY name;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------
Sort (cost=65.68..66.91 rows=490 width=72) (actual time=4.480..4.484 rows=8.00 loops=1)
Output: local_nodes.name, remote.count, remote.round
Sort Key: local_nodes.name
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=4
-> Hash Join (cost=27.60..43.79 rows=490 width=72) (actual time=4.406..4.422 rows=8.00 loops=1)
Output: local_nodes.name, remote.count, remote.round
Inner Unique: true
Hash Cond: (local_nodes.node_id = remote.node_id)
Buffers: shared hit=1
-> Seq Scan on public.local_nodes (cost=0.00..14.90 rows=490 width=40) (actual time=0.010..0.016 rows=8.00 loops=1)
Output: local_nodes.node_id, local_nodes.name, local_nodes.region, local_nodes.arch, local_nodes.os
Buffers: shared hit=1
-> Hash (cost=15.10..15.10 rows=1000 width=48) (actual time=4.379..4.381 rows=8.00 loops=1)
Output: remote.count, remote.round, remote.node_id
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Subquery Scan on remote (cost=1.00..15.10 rows=1000 width=48) (actual time=4.337..4.360 rows=8.00 loops=1)
Output: remote.count, remote.round, remote.node_id
-> Foreign Scan (cost=1.00..5.10 rows=1000 width=48) (actual time=4.330..4.349 rows=8.00 loops=1)
Output: logs.node_id, (count(*)), (round(avg(logs.duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT node_id, count(*), round(avg(duration), 0) FROM "default".logs GROUP BY node_id
FDW Time: 0.055 ms
Planning:
Buffers: shared hit=5
Planning Time: 0.319 ms
Execution Time: 4.562 ms“Foreign Scan”节点现在会按 node_id 下推聚合,将需要从 Postgres 拉回的行数从 1000 行 (全部行) 减少到仅 8 行,即每个节点 1 行。
分区表
PostgreSQL [分区表]可以同时包含本地分区和由 ClickHouse 支持的外部分区。常见的布局是将较早的数据迁移到 ClickHouse,而较新的数据保留在 PostgreSQL 中:
CREATE TABLE events (id int, ts date, val int, amt float8)
PARTITION BY RANGE (ts);
-- 2023 data lives on ClickHouse
CREATE FOREIGN TABLE events_2023 PARTITION OF events
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')
SERVER ch_svr OPTIONS (table_name 'events');
-- 2024 data stays local
CREATE TABLE events_2024 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');有关如何将数据从本地分区迁移到外部分区的示例,请参阅 offload-partition.sql。
跨本地分区和外部分区的聚合需要启用 partitionwise aggregation,而 PostgreSQL 默认将其禁用:
SET enable_partitionwise_aggregate = on;启用 enable_partitionwise_aggregate 后,PostgreSQL 会在 Append 下方计算部分聚合,再由上方的最终聚合将这些部分结果合并为最终结果。pg_clickhouse 会将外部分区的部分聚合下推至 ClickHouse:
try=# EXPLAIN (VERBOSE, COSTS OFF)
SELECT count(*), sum(val), min(ts), max(ts) FROM events;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Finalize Aggregate
Output: count(*), sum(events.val), min(events.ts), max(events.ts)
-> Append
-> Foreign Scan
Output: (PARTIAL count(*)), (PARTIAL sum(events.val)), (PARTIAL min(events.ts)), (PARTIAL max(events.ts))
Relations: Aggregate on (events_2023 events)
Remote SQL: SELECT count(*), sum(val), min(ts), max(ts) FROM "default".events
-> Partial Aggregate
Output: PARTIAL count(*), PARTIAL sum(events_1.val), PARTIAL min(events_1.ts), PARTIAL max(events_1.ts)
-> Seq Scan on public.events_2024 events_1
Output: events_1.val, events_1.ts部分聚合何时可以下推
PostgreSQL 将部分聚合表示为转换状态,并由最终化步骤在各分区间合并。只有当 pg_clickhouse 能将分区的部分聚合表示为 ClickHouse 值时,才能将其下推:
- 可分解聚合函数的转换状态本身就是最终值,因此可以直接下推:
count、sum、min、max、bool_and/every、bool_or、bit_and、bit_or和bit_xor。 - 作用于整数的
avg会将其{count, sum}状态作为数组下推。 - 作用于浮点数的
avg、var_pop、var_samp、stddev_pop和stddev_samp会将其{N, sum, squared deviations 的总和}状态作为数组下推。
FILTER (WHERE …) 也可以与这些聚合函数一起下推。
何时回退
转换状态为 PostgreSQL 不透明 internal 类型的聚合没有可移植的表示形式,因此外部分区会转而拉取其行,并在本地进行聚合。这包括所有基于 numeric 的聚合,以及 avg(bigint) 和 avg(interval)。DISTINCT、有序集聚合和可变参数聚合也会回退。
PREPARE, EXECUTE, DEALLOCATE
自 v0.1.2 起,pg_clickhouse 支持参数化查询,主要通过 PREPARE 命令创建:
try=# PREPARE avg_durations_between_dates(date, date) AS
SELECT date(start_at), round(avg(duration)) AS average_duration
FROM logs
WHERE date(start_at) BETWEEN $1 AND $2
GROUP BY date(start_at)
ORDER BY date(start_at);
PREPARE像平常一样使用 EXECUTE 执行预处理语句:
try=# EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
date | average_duration
------------+------------------
2025-12-09 | 190
2025-12-10 | 194
2025-12-11 | 197
2025-12-12 | 190
2025-12-13 | 195
(5 rows)与往常一样,pg_clickhouse 会下推聚合操作,如 EXPLAIN 的详细输出所示:
try=# EXPLAIN (VERBOSE) EXECUTE avg_durations_between_dates('2025-12-09', '2025-12-13');
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=36)
Output: (date(start_at)), (round(avg(duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= '2025-12-09')) AND ((date(start_at) <= '2025-12-13')) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)请注意,它发送的是完整的日期值,而不是参数占位符。
前五次请求都是如此,如 PostgreSQL
[PREPARE 说明] 中所述。到第六次执行时,它会发送 ClickHouse
{param:type} 风格的[查询参数]:
参数:
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Foreign Scan (cost=1.00..5.10 rows=1000 width=36)
Output: (date(start_at)), (round(avg(duration), 0))
Relations: Aggregate on (logs)
Remote SQL: SELECT date(start_at), round(avg(duration), 0) FROM "default".logs WHERE ((date(start_at) >= {p1:Date})) AND ((date(start_at) <= {p2:Date})) GROUP BY (date(start_at)) ORDER BY date(start_at) ASC NULLS LAST
(4 rows)使用 DEALLOCATE 释放预处理语句:
try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATEINSERT
使用 INSERT 命令向远程 ClickHouse 表中插入值:
try=# INSERT INTO nodes(node_id, name, region, arch, os)
VALUES (9, 'Augustin Gamarra', 'us-west-2', 'amd64', 'Linux')
, (10, 'Cerisier', 'us-east-2', 'amd64', 'Linux')
, (11, 'Dewalt', 'use-central-1', 'arm64', 'macOS')
;
INSERT 0 3COPY
使用 COPY 命令将一个批次的数据行插入远程 ClickHouse 表:
try=# COPY logs FROM stdin CSV;
4285871863,2025-12-05 11:13:58.360760,206,/widgets,POST,8,401
4020882978,2025-12-05 11:33:48.248450,199,/users/1321945,HEAD,3,200
3231273177,2025-12-05 12:20:42.158575,220,/search,GET,2,201
\.
>> COPY 3⚠️ 批次 API 的限制
pg_clickhouse 尚未支持 PostgreSQL FDW 的批次 insert API。因此,COPY 目前通过 INSERT 语句 插入记录。后续版本将对此进行改进。
LOAD
使用 LOAD 加载 pg_clickhouse 共享库:
try=# LOAD 'pg_clickhouse';
LOAD通常不需要使用 LOAD,因为 Postgres 会在首次使用 pg_clickhouse 的任一功能 (函数、外部表等) 时自动加载它。
而 LOAD pg_clickhouse 可能有用的一种情况是:在执行依赖这些参数的查询之前,先通过 SET 设置 pg_clickhouse 参数。
SET
使用 SET 来设置 pg_clickhouse 的自定义配置参数。
pg_clickhouse.session_settings
pg_clickhouse.session_settings 参数用于配置在后续查询中要设置的 [ClickHouse
设置]。示例:
SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';默认值为
join_use_nulls 1, group_by_use_nulls 1, final 1, transform_null_in 0将其设为空字符串即可恢复为 ClickHouse server 的设置 —
但请注意,下推的正确性取决于其中某些默认值:
OUTER JOIN 的 join_use_nulls 以及 IN 家族的 transform_null_in
(请参阅 IN 和 NULL 语义) 。
SET pg_clickhouse.session_settings = '';其语法为由逗号分隔的键/值对列表,各项之间以一个或 多个空格分隔。键必须与 [ClickHouse 设置] 对应。在值中,空格、 逗号和反斜杠都要用反斜杠转义:
SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';或者使用单引号包裹值,以避免转义空格和逗号;也可以考虑 使用[美元引号表示法],这样就无需再使用双引号:
SET pg_clickhouse.session_settings = $$join_algorithm 'grace_hash,hash'$$;如果你更注重可读性,并且需要设置很多项,可以分成多 行,例如:
SET pg_clickhouse.session_settings TO $$
connect_timeout 2,
count_distinct_implementation uniq,
final 1,
group_by_use_nulls 1,
join_algorithm 'prefer_partial_merge',
join_use_nulls 1,
log_queries_min_type QUERY_FINISH,
max_block_size 32768,
max_execution_time 45,
max_result_rows 1024,
metrics_perf_events_list 'this,that',
network_compression_method ZSTD,
poll_interval 5,
totals_mode after_having_auto
$$;当这些设置会干扰 pg_clickhouse 自身运行时,其中一些设置将被忽略。包括:
date_time_output_format:http 驱动要求其为 "iso"format_tsv_null_representation:http 驱动要求使用默认值output_format_tsv_crlf_end_of_line:http 驱动要求使用默认值
除此之外,pg_clickhouse 不会校验这些设置,而是会在每次查询时将它们传递给 ClickHouse。因此,它支持各个 ClickHouse 版本中的所有设置。
请注意,必须先加载 pg_clickhouse,才能设置
pg_clickhouse.session_settings;可以使用共享库预加载,或者
直接使用该扩展中的某个对象以确保其被加载。
pg_clickhouse.pushdown_regex
pg_clickhouse.pushdown_regex 参数用于控制 pg_clickhouse
是否对正则表达式函数和操作符执行下推。默认情况下会执行下推;
将此参数设置为 false 可阻止下推:
SET pg_clickhouse.pushdown_regex = 'false';详见正则表达式。
ALTER ROLE
使用 ALTER ROLE 的 SET 命令,可为特定角色预加载 pg_clickhouse,
和/或 SET 其参数:
try=# ALTER ROLE CURRENT_USER SET session_preload_libraries = pg_clickhouse;
ALTER ROLE
try=# ALTER ROLE CURRENT_USER SET pg_clickhouse.session_settings = 'final 1';
ALTER ROLE使用 ALTER ROLE's RESET 命令重置 pg_clickhouse 的预加载
和/或相关参数:
try=# ALTER ROLE CURRENT_USER RESET session_preload_libraries;
ALTER ROLE
try=# ALTER ROLE CURRENT_USER RESET pg_clickhouse.session_settings;
ALTER ROLE预加载
如果每个或几乎每个 Postgres 连接都需要使用 pg_clickhouse, 请考虑使用共享库预加载来自动加载它:
session_preload_libraries
在每次建立与 PostgreSQL 的新连接时加载共享库:
session_preload_libraries = pg_clickhouse有助于在不重启服务器的情况下应用更新:只需 重新连接即可。也可以通过 ALTER ROLE 为特定用户或角色设置。
在启动时将共享库加载到 PostgreSQL 主进程中:
shared_preload_libraries = pg_clickhouse有助于减少每个会话的内存占用和加载开销,但在库更新时需要 重启集群。
数据类型
pg_clickhouse 将以下 ClickHouse 数据类型映射为 PostgreSQL 数据 类型。IMPORT FOREIGN SCHEMA 在导入列时会使用 PostgreSQL 列中的第一种类型;其他类型可在 CREATE FOREIGN TABLE 语句中使用:
| ClickHouse | PostgreSQL | 说明 |
|---|---|---|
| Bool | boolean | |
| Date | date | |
| Date32 | date | |
| DateTime | timestamptz | |
| Decimal | numeric | |
| Float32 | real | |
| Float64 | double precision | |
| IPv4 | inet | |
| IPv6 | inet | |
| Int16 | smallint | |
| Int32 | integer | |
| Int64 | bigint | |
| Int8 | smallint | |
| JSON | jsonb, json | |
| String | text, bytea | |
| UInt16 | integer | |
| UInt32 | bigint | |
| UInt64 | bigint | 值大于 BIGINT 最大值时会报错 |
| UInt8 | smallint | |
| UUID | uuid |
任何列也可读取为 text、varchar 或其他 String 类型。该值
会采用上述 PostgreSQL 类型,然后通过该类型的输出
函数进行呈现。超过 bigint 最大值的 UInt64 值仍会报错,因此请使用
ClickHouse toString() 函数呈现这些值。
更多说明和细节如下。
BYTEA
ClickHouse 不提供与 PostgreSQL BYTEA 类型等效的类型,但允许将任意字节存储在 String 类型中。一般情况下,ClickHouse 字符串应映射到 PostgreSQL 的 TEXT 类型;若需使用二进制数据,则应映射到 BYTEA。示例:
-- Create ClickHouse table with String columns.
CALL clickhouse_perform('ch_srv', $$
CREATE TABLE bytes (
c1 Int8, c2 String, c3 String
) ENGINE = MergeTree ORDER BY (c1);
$$);
-- Create foreign table with BYTEA columns.
CREATE FOREIGN TABLE bytes (
c1 int,
c2 BYTEA,
c3 BYTEA
) SERVER ch_srv OPTIONS( table_name 'bytes' );
-- Insert binary data into the foreign table.
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
FROM generate_series(1, 4) n;
-- View the results.
SELECT * FROM bytes;最终的 SELECT 查询将输出:
c1 | c2 | c3
----+------------------------------------------------------------+------------------------------------
1 | \x1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | \xae3b28cde02542f81acce8783245430d
2 | \x5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | \x23e7c6cacb8383f878ad093b0027d72b
3 | \x53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | \x7e969132fc656148b97b6a2ee8bc83c1
4 | \x4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | \x8ef30f44c65480d12b650ab6b2b04245
(4 rows)请注意,如果 ClickHouse 列中存在空字节,使用 TEXT 列的外部表将无法输出正确的值:
-- Create foreign table with TEXT columns.
CREATE FOREIGN TABLE texts (
c1 int,
c2 TEXT,
c3 TEXT
) SERVER ch_srv OPTIONS( table_name 'bytes' );
-- Encode binary data as hex.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;输出结果:
c1 | encode | encode
----+----------------------------------------------------------+----------------------------------
1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b
3 | 53ac2c1fa83c8f64603fe9568d883331 | 7e969132fc656148b97b6a2ee8bc83c1
4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)请注意,第二行和第三行包含被截断的值。这是因为 PostgreSQL 依赖以 nul 结尾的字符串,且不支持在字符串中 包含 nul 字符。
尝试向 TEXT 列插入二进制值将会成功,并按预期运行:
-- Insert via text columns:
TRUNCATE texts;
INSERT INTO texts
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
FROM generate_series(1, 4) n;
-- View the data.
SELECT c1, encode(c2::bytea, 'hex'), encode(c3::bytea, 'hex') FROM texts ORDER BY c1;文本列将是正确的:
c1 | encode | encode
----+----------------------------------------------------------+----------------------------------
1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b0027d72b
3 | 53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | 7e969132fc656148b97b6a2ee8bc83c1
4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)但如果按 BYTEA 读取,则不会:
# SELECT * FROM bytes;
c1 | c2 | c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 rows)函数与 operator 参考
函数
这些函数提供了查询 ClickHouse 数据库的接口。
clickhouse_raw_query
SELECT clickhouse_raw_query(
'CREATE TABLE t1 (x String) ENGINE = Memory',
'host=localhost port=8123'
);连接到 ClickHouse 服务,执行单个
查询,然后断开连接。可选的第二个参数用于指定连接
字符串,默认为 host=localhost port=8123。支持的连接
参数如下:
driver:要使用的连接驱动程序,可以是 "http" 或 "binary";默认为 "http"host:要连接到的主机;必填。port:要连接的端口。对于 "http" 驱动程序,默认为8123;对于 "binary" 驱动程序,默认为9000。当host是 ClickHouse Cloud 主机时, 则分别切换为8443或9440dbname:要连接到的数据库名称。username:连接时使用的用户名;默认为defaultpassword:用于身份验证的密码;默认无密码
两种驱动程序都返回制表符分隔的行 (NULL 值为 \N) ,但各值的
表示形式不同:"http" 驱动程序会原样返回 ClickHouse 自有的 TSV
格式,而 "binary" 驱动程序会将每个值传递给其
PostgreSQL 输出函数。
默认情况下,没有任何角色具有对此函数的 EXECUTE 权限;请考虑仅向
确实需要执行临时 ClickHouse 查询的角色 GRANT 此权限,
例如专用的 ClickHouse 管理员角色:
适用于不返回记录的查询,但如果查询确实返回值, 则会作为单个文本值返回:
SELECT clickhouse_raw_query(
'SELECT schema_name, schema_owner from information_schema.schemata',
'host=localhost port=8123'
); clickhouse_raw_query
---------------------------------
INFORMATION_SCHEMA default+
default default +
git default +
information_schema default+
system default +
(1 row)clickhouse_server_version
SELECT clickhouse_server_version('taxi_srv');报告指定 外部服务器 的 ClickHouse 服务器版本,格式为 major.minor.patch。如有必要,将使用该服务器的选项和当前用户的 user mapping 进行连接:
clickhouse_server_version
---------------------------
25.8.1
(1 row)从原生协议连接的握手过程中读取版本信息,或通过 HTTP 执行单个 SELECT version() 查询获取版本信息,并在连接的整个生命周期内缓存该信息。
clickhouse_query
SELECT * FROM clickhouse_query(
'server',
'SELECT id, name, salary FROM remote_table WHERE salary > 50000'
) AS ch(id int, name text, salary numeric);针对已配置的外部服务器执行查询,并将返回的
行作为关系返回,同时将每个 ClickHouse 结果列映射为列定义列表中
指定的 PostgreSQL 类型。它会复用该服务器的 driver、
凭据、数据库和连接缓存。
第一个参数是通过 CREATE SERVER 创建的服务器名称。必须提供
列定义列表 (AS name(col type, ...)) :PostgreSQL 在拉取行之前需要知道
结果形态,且该列表必须与查询返回的列相匹配。值会以与外部表列相同的方式
从 ClickHouse 转换为声明的类型。不返回结果的语句 (如 DDL)
无需声明任何内容;请改用
clickhouse_perform 执行它们。
默认情况下,任何角色都没有 EXECUTE 权限;请向角色 GRANT 权限,以允许其使用
该函数。
GRANT EXECUTE ON FUNCTION clickhouse_query(text, text) TO ch_admin;clickhouse_perform
CALL clickhouse_perform(
'server',
'CREATE TABLE remote_table (id Int32) ENGINE = MergeTree ORDER BY id'
);对已配置的 外部服务器 执行一条语句,并丢弃
所有结果。适用于不返回行的语句,例如 DDL;此时
clickhouse_query 没有可声明的结果形态。它以与 clickhouse_query 相同的方式
解析 server,并复用其 driver、credentials、database 和 连接缓存。
作为存储过程,必须通过 CALL 而非 SELECT 调用,并且不返回
行。默认情况下,没有任何角色拥有 EXECUTE 权限;向角色授予 GRANT 权限即可允许其
使用该存储过程。
GRANT EXECUTE ON PROCEDURE clickhouse_perform(text, text) TO ch_admin;下推函数
pg_clickhouse 会将条件子句 (HAVING 和 WHERE 子句) 中使用的部分 PostgreSQL 内置函数下推到 ClickHouse 中执行。该子集与 ClickHouse 中的对应函数如下:
abs: absfactorial: factorialmod(int2/int4/int8/numeric): 取模pow&power(float8/numeric): powround: roundsin,cos,tan,atan,atan2,sinh,cosh,tanh,asinh,degrees,radians,pi:ClickHouse 数学函数 中的同名函数。asin、acos、atanh、acosh不会下推:对于超出范围的输入,PG 会报错,而 CH 会返回NaN。date_part:date_part('day'): toDayOfMonthdate_part('doy'): toDayOfYeardate_part('dow'): toDayOfWeekdate_part('year'): toYeardate_part('month'): toMonthdate_part('hour'): toHourdate_part('minute'): toMinutedate_part('second'): toSeconddate_part('quarter'): toQuarterdate_part('isoyear'): toISOYeardate_part('week'): toISOYeardate_part('epoch'): toISOYear
date_trunc:date_trunc('week'): toMondaydate_trunc('second'): toStartOfSeconddate_trunc('minute'): toStartOfMinutedate_trunc('hour'): toStartOfHourdate_trunc('day'): toStartOfDaydate_trunc('month'): toStartOfMonthdate_trunc('quarter'): toStartOfQuarterdate_trunc('year'): toStartOfYear
extract(field FROM source):映射与date_part相同date(timestamp)&date(timestamptz): toDate (反解析后为 CH 别名date)array_position:indexOf,配合 nullIf 将0转换为NULL;如果存在用于指定搜索起始索引的第三个参数,则使用 arraySlice。 请注意,nan当前不会匹配array_cat: arrayConcatarray_append: arrayPushBackarray_prepend: arrayPushFrontarray_remove: arrayRemovecardinality:lengtharray_length(array, 1):nullIf(length(array), 0)array_length&cardinality:lengtharray_to_string: arrayStringConcatstring_to_array: splitByStringsplit_part: splitByString + 数组下标trim_array: arrayResizearray_fill: arrayWithConstantarray_reverse: arrayReversearray_shuffle:arrayShufflearray_sample: arrayRandomSamplearray_sort: arraySort / arrayReverseSortbtrim: trimBothltrim: trimLeftrtrim: trimRightconcat_ws: concatWithSeparatorlower(text): lowerUTF8upper(text): upperUTF8substring(text, ...)&substr(text, ...): substringUTF8substring(bytea, ...)&substr(bytea, ...): 子串length(text): lengthUTF8length(bytea)&octet_length:lengthreverse(text): reverseUTF8reverse(bytea): reversestrpos:positionUTF8regexp_like: matchregexp_match: extractGroups 如果正则表达式包含带括号的子表达式;否则为 extractAll 再使用 arraySlice 切片。regexp_replace:replaceRegexpOne 或 replaceRegexpOne,存在g标志时regexp_split_to_array: splitByRegexpmd5: MD5- 当
fmt为字符串常量 (不区分大小写) 时的encode(bytea, fmt):encode(bytea, 'hex'):hex 再套用 lower, 因为 PostgreSQL 会输出小写 hex。encode(bytea, 'base64'):base64Encode 再套用 replaceRegexpAll, 以复现 PostgreSQL's MIME (RFC 2045) 每 76 个字符换行的规则。encode(bytea, 'base64url')(PostgreSQL 19+):base64URLEncode, 与 PostgreSQL's 不带 padding 的 RFC 4648 URL 字母表一致。
json_extract_path_text:子列语法json_extract_path: toJSONString + 子列语法jsonb_extract_path_text: 子列语法jsonb_extract_path:toJSONString + 子列语法bit_count(bytea): bitCountto_timestamp(float8): toDateTime64to_char(timestamp[tz], fmt): formatDateTime 当fmt为字符串常量,且其中每个关键字在 ClickHouse 中都有完全对应的等价写法时。支持的关键字请参见兼容性说明中的 to_char()。 否则,该函数会在 PostgreSQL 中本地执行。statement_timestamp,transaction_timestamp, &clock_timestamp: nowInBlock64 (nowInBlock64(9, $session_timezone))CURRENT_DATE: now 以及 toDate (toDate(now($session_timezone)))now、CURRENT_TIMESTAMP和LOCALTIMESTAMP: now64 (now64(9, $session_timezone))CURRENT_TIMESTAMP(n)&LOCALTIMESTAMP(n): now64 (now64(n, $session_timezone))CURRENT_DATABASE:作为值由 PostgreSQL 函数传入。CURRENT_SCHEMA:以 PostgreSQL 函数返回值的形式传递。CURRENT_CATALOG:作为值由 PostgreSQL 函数传入。CURRENT_USER:作为值从 PostgreSQL 函数传入。USER:由 PostgreSQL 函数作为值传入。CURRENT_ROLE:作为值从 PostgreSQL 函数中传入。SESSION_USER:作为值从 PostgreSQL 函数传入。
下推操作符
- 数组切片 (
arr[L:U]): arraySlice @>(数组包含) : hasAll<@(数组被包含) : hasAll&&(数组重叠) : hasAny~(正则表达式匹配) : match!~(正则表达式不匹配) : match~*(不区分大小写的正则表达式不匹配) : match!~*(不区分大小写的正则表达式不匹配) : match->>(将 JSON/JSONB 元素提取为文本) : sub-column syntax->(提取 JSON/JSONB) : toJSONString + sub-column syntax
IN 和 NULL 语义
ClickHouse 对 IN 采用二值逻辑:当探测值未找到
匹配项时,即使涉及 NULL,也会返回 0,而 PostgreSQL 则会计算为
NULL。为保留 PostgreSQL 语义,pg_clickhouse 会无条件下推针对常量列表或数组的 IN
系列操作 (IN、NOT IN、= ANY、= ALL、
<> ANY、<> ALL) :若能够证明探测值和数组元素均不可能为 NULL,则使用原生或开销较低的形式;
否则使用带保护条件的 CASE 形式,在运行时检查 NULL 值,
从而在所有上下文中准确计算 PostgreSQL 的三值结果 (TRUE、FALSE、NULL) ,
包括 SELECT 列表或 GROUP BY 等值位置。
针对可空列的 NOT IN (SELECT ...) 过滤器也会下推,
并在反解析时加入补偿性保护条件,以保持 PostgreSQL 的行为:包含 NULL 的集合会排除
所有行,而 NULL 探测值仅会在集合为空时通过。当 NOT NULL 声明
证明某个保护条件并非必需时,该条件会被省略。与上述数组形式不同,此保护条件仅
适用于普通过滤条件 (或位于 NOT 之下) ;我们仍不会下推
值位置中的 IN (SELECT ...),也不会下推已分组或聚合的子查询
主体。将列声明为 NOT NULL 可使系统采用开销更低的无保护形式,从而最大化
下推;对于非 Nullable 的 ClickHouse 列,IMPORT FOREIGN SCHEMA 会自动执行此操作。
该推断基于非 NULL 常量、NOT NULL 列,以及对它们执行的基本算术运算 (+、-、*、一元 -) 。
这些规则假定 ClickHouse 的默认设置为 transform_null_in = 0。pg_clickhouse 会通过
pg_clickhouse.session_settings 参数的默认值,
在每个查询中设置该值,
以防止 ClickHouse server profile 对其进行静默更改。设置
transform_null_in = 1 会破坏所有已下推 IN 的语义。
自定义函数
这些由 pg_clickhouse 创建的自定义函数,可为部分在 PostgreSQL 中没有对应实现的 ClickHouse 函数提供外部查询下推。如果其中任何函数无法下推,则会引发异常。
扩展下推
pg_clickhouse 可识别部分核心扩展和第三方扩展中的函数,并将其下推为 ClickHouse 中对应的等效函数。
re2
所有 re2 extension 运算符和函数都可 1:1 下推到 ClickHouse:
@~→ matchre2match→ matchre2extract→ extractre2extractall→ extractAllre2regexpextract→ regexpExtractre2extractgroups→ extractGroupsre2replaceregexpone→ replaceRegexpOnere2replaceregexpall→ replaceRegexpAllre2countmatches→ countMatchesre2countmatchescaseinsensitive→ countMatchesCaseInsensitivere2multimatchany→ multiMatchAnyre2multimatchanyindex→ multiMatchAnyIndexre2multimatchallindices→ multiMatchAllIndices
intarray
有一个 intarray 函数可下推至 ClickHouse:
idx→ indexOf
fuzzystrmatch
两个 fuzzystrmatch 函数可下推至 ClickHouse:
soundex:soundexlevenshtein(2 参数) :editDistanceUTF8
下推类型转换
对于兼容的 数据类型,pg_clickhouse 会下推 CAST(x AS bigint) 这类类型转换。
对于不兼容的类型,下推会失败;例如,如果此示例中的 x 是 ClickHouse 的 UInt64,
ClickHouse 会拒绝执行该类型转换。
为了将类型转换下推到不兼容的 数据类型,pg_clickhouse 提供了 以下函数。如果这些函数没有被下推,就会在 PostgreSQL 中引发异常。
聚合函数下推
以下 PostgreSQL 聚合函数可下推到 ClickHouse。
- any_value
- array_agg
- avg
- bit_and
- bit_or
- bit_xor
- bool_and / every
- bool_or
- count
- corr
- covarpop
- covarsamp
- min
- max
- stddev_pop
- stddev_samp / stddev
- string_agg
- sum
- var_op
- var_samp /variance
自定义聚合函数
这些由 pg_clickhouse 创建的自定义聚合函数,可为部分在 PostgreSQL 中没有对应函数的 ClickHouse 聚合函数提供外部查询下推。如果这些函数中的任何一个无法下推, 就会引发异常。
下推有序集合聚合函数
这些有序集合聚合函数会映射为 ClickHouse 的[参数化
聚合函数]:将其直接参数作为 parameter 传递,并将其 ORDER BY expression 作为 argument。例如,以下 PostgreSQL 查询:
SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY a) FROM t1;对应的 ClickHouse 查询如下:
SELECT quantile(0.25)(a) FROM t1;请注意,不支持非默认的 ORDER BY 后缀 DESC 和 NULLS FIRST,
使用它们会报错。
percentile_cont(double): quantilepercentile_cont(double[]): quantilespercentile_disc(double): quantileExactLowpercentile_disc(double[]): quantilesExactLow
自定义有序集合聚合函数
这些由 pg_clickhouse 创建的自定义有序集合聚合函数为部分 ClickHouse [参数化聚合 函数]提供外部查询下推。若其中任何函数无法下推,将引发异常。
quantile(double): quantilequantileExact(double): quantileExact
自定义有序集合聚合函数
这些由 pg_clickhouse 创建的自定义有序集合聚合函数可为特定的 ClickHouse 参数化聚合函数提供外部查询下推。如果其中任何函数无法下推,则会引发异常。
下推窗口函数
以下 PostgreSQL [窗口函数]可通过包含 OVER (PARTITION BY ... ORDER BY ...) 子句的查询下推到 ClickHouse;适用时还包括窗口帧规范。
- row_number
- rank
- dense_rank
- ntile
- cume_dist
- percent_rank
- lead
- lag
- first_value
- last_value
- nth_value
min/max(带OVER子句)
排名函数 (row_number、rank、dense_rank、ntile、cume_dist、
percent_rank) 在下推时会省略窗口帧子句,因为 ClickHouse 不支持为这些函数指定窗口帧。
兼容性说明
正则表达式
当 pg_clickhouse.pushdown_regex 为 true (默认值) 时, pg_clickhouse 会将正则表达式下推为 ClickHouse 中的等效形式, 并尽力保证基本兼容性,但你仍需了解二者之间的差异,以及 pg_clickhouse 如何处理这些差异。
-
PostgreSQL 支持 [POSIX 正则表达式],而 ClickHouse 支持 RE2 正则表达式。请注意两者的行为差异:当正则表达式由 ClickHouse 求值时 (例如,在
WHERE子句中) ,请使用 RE2;当它由 Postgres 求值时 (例如,在SELECT子句中) ,请使用 POSIX。 -
pg_clickhouse 通过将 [Postgres 标志] 置于 ClickHouse 正则表达式前面并放入
(?)中,来实现下推。例如:regexp_like(val, '^VAL\d', 'i')变成
match(val, concat('(?i)', '^VAL\\d')) -
两者唯一同时支持、因此可在由 ClickHouse 求值时使用的标志有:
标志 对应 说明 ii不区分大小写匹配 mm-s^和$除了匹配文本开头/结尾外,也匹配行首/行尾nm-sm的 Postgres 别名p-s不允许 .和[^x]匹配\nss允许 .和[^x]匹配\nt紧凑语法,会被忽略 wm部分换行敏感匹配的反向形式 RE2 仅支持这些标志;请勿使用任何其他 [Postgres 标志]。
-
下表概述了各种标志 (以及不加标志的情况,此时 与
s相同) 在匹配换行符和行尾时的影响。请注意,在 Postgres 中,m和p会阻止否定字符类 ([^xyz]) 匹配 换行符,而 ClickHouse 中的对应标志则不会。除此之外, ClickHouse 的行为与 Postgres 相同:应用于 a\nb的模式Postgres ClickHouse 一致? a.btrue true ✔︎ a[^x]btrue true ✔︎ a$false false ✔︎ s标志(?s)a.btrue true ✔︎ (?s)a[^x]btrue true ✔︎ (?s)a$false false ✔︎ m标志(?m)a.bfalse false ✔︎ (?m)a[^x]btrue false ✘ (?m)a$true true ✔︎ p标志(?p)a.bfalse false ✔︎ (?p)a[^x]btrue false ✘ (?p)a$false false ✔︎ w标志(?w)a.btrue true ✔ (?w)a[^x]btrue true ✔ (?w)a$true true ✔ -
传递给正则表达式函数的任何其他标志都会阻止 该函数被下推。
-
例外是
regexp_replace(),它还支持g标志。当 设置g时,pg_clickhouse 会使用replaceRegexpAll()而不是replaceRegexpOne(),并会先移除该标志,再添加其他标志。 -
Postgres 的
regexp_replace()的 replacement 参数支持使用\&来 引用整个匹配;而在 ClickHouse 中,则使用\0表示整个 匹配。该函数下推到 ClickHouse 时,务必使用\0。 -
当没有匹配项时,Postgres
regexp_match会返回NULL,而 它下推执行的表达式则会返回空数组。请使用COALESCE()返回空数组而不是NULL,以便以兼容的方式比较返回值。 例如:SELECT * FROM events WHERE COALESCE(regexp_match(msg, '^ERR'), '{}');
为避免任何歧义,建议设置 pg_clickhouse.pushdown_regex 以阻止 Postgres 正则表达式下推到 ClickHouse,并使用 re2 extension;对于该扩展,pg_clickhouse 支持将 与 ClickHouse 兼容的 RE2 正则表达式进行直接下推。
to_char()
PostgreSQL 中用于 timestamp 和 timestamp with time zone 的 to_char()
仅在 format argument 为非 NULL 的 string constant,且其中每个 PostgreSQL 关键字
在 ClickHouse 中都有一个逐字节完全一致的对应项时,才会下推到 ClickHouse formatDateTime。
如果 format 是动态的 (不是 Const) ,或者包含任何不支持的关键字或 modifier,
则该调用会回退到 PostgreSQL 本地求值——绝不会在部分翻译的情况下尝试下推,
因此输出会保持与 PG 兼容。
用于 numeric、interval 及其他非 timestamp 类型的双参数 to_char()
形式永远不会下推;ClickHouse formatDateTime 仅用于格式化日期时间值。
已转换的关键字
| PostgreSQL | ClickHouse | 含义 |
|---|---|---|
YYYY, yyyy |
%Y |
4 位年份 |
YY, yy |
%y |
2 位年份 |
MM, mm |
%m |
补零的月份 (01–12) |
DD, dd |
%d |
补零的日期 (01–31) |
DDD, ddd |
%j |
补零的一年中的第几天 (001–366) |
HH24, hh24 |
%H |
补零的 24 小时制小时 (00–23) |
HH, hh, HH12, hh12 |
%I |
补零的 12 小时制小时 (01–12) |
MI, mi |
%i |
补零的分钟 (00–59) |
SS, ss |
%S |
补零的秒 (00–59) |
Q, q |
%Q |
季度 (1–4) |
Mon |
%b |
月份简称,例如 Oct |
Dy |
%a |
星期简称,例如 Mon |
AM, PM |
%p |
上/下午标记,始终为大写 |
带引号的文本和字面量
用 "..." 包裹的文本会原样传递,其中任何字面的 %
都会写成 %%,以转义 ClickHouse 的格式说明符前缀。引号外的 \"
也会作为字面量 " 原样传递。在 "..." 内部,反斜杠
只会转义 ";其他反斜杠序列都会被视为字面文本。
版权
版权所有 (c) 2025-2026,ClickHouse