跳转到主要内容

描述

pg_clickhouse 是一个 PostgreSQL 扩展,可在 ClickHouse 数据库上远程执行查询, 其中包括一个[外部数据包装器]。它支持 PostgreSQL 13 及以上版本,以及 ClickHouse 23 及以上版本。

快速开始

试用 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() 函数的输出或 Postgres pg_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”。必填。
  • dbname:连接时使用的 ClickHouse 数据库。默认为 “default”。
  • fetch_size:HTTP streaming 的近似批次大小 (以字节为单位) 。批次 按行边界拆分。默认为 50000000 (50 MB) 。0 会禁用 streaming 并缓冲整个响应。foreign table 可以覆盖此 值。
  • 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

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 支持的选项。
导入标识符的大小写保留IMPORT FOREIGN SCHEMA 会对导入的表名和列名运行 quote_identifier(), 为包含大写字母或空格的标识符加上双引号。 因此,这类表名和列名在 PostgreSQL 查询中必须用双引号括起来。 如果名称全部为小写且不包含空格字符,则无需加引号。例如,给定以下 ClickHouse 表:
CREATE OR REPLACE TABLE test
(
    id UInt64,
    Name TEXT,
    updatedAt DateTime DEFAULT now()
)
ENGINE = MergeTree
ORDER BY id;
IMPORT FOREIGN SCHEMA 会创建这个外部表:
CREATE TABLE test
(
    id          BIGINT      NOT NULL,
    "Name"      TEXT        NOT NULL,
    "updatedAt" TIMESTAMPTZ NOT NULL
);
因此,查询中也必须正确加引号,例如:
SELECT id, "Name", "updatedAt" FROM test;
若要创建名称不同或全部为小写 (因此 不区分大小写) 的对象,请使用 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'
);
支持的表选项如下:
  • database:远程 database 的名称。默认使用为 外部服务器 定义的 database。
  • fetch_size:HTTP streaming 的近似批次大小 (以字节为单位) 。会覆盖 server 级别的 fetch_size。默认值为 50000000 (50 MB) 。0 会禁用 流式传输并缓冲完整响应。
  • table_name:远程表的名称。默认使用为 foreign table 指定的名称。
  • engine:ClickHouse 表使用的[表引擎]。对于 CollapsingMergeTree()AggregatingMergeTree(),pg_clickhouse 会自动将参数应用于在该表上执行的函数表达式。
对于每一列,请使用与远程 ClickHouse 数据 类型相对应的data type。支持的列选项如下:
  • 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 行。

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 行)
参数化执行会导致 http 驱动 在 25.8 之前的 ClickHouse 版本中 无法正确转换 DateTime 时区,因为该[底层缺陷]当时尚未[修复]。请注意,PostgreSQL 有时即使不使用 PREPARE,也会采用参数化查询计划。对于任何需要精确时区转换的查询, 如果无法升级到 25.8 或更高版本,请改用 binary driver
与往常一样,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;
DEALLOCATE

INSERT

使用 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 3

COPY

使用 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。将其设为空 字符串即可恢复为 ClickHouse server 的设置。
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 ROLESET 命令,可为特定角色预加载 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 为特定用户或角色设置。

shared_preload_libraries

在启动时将共享库加载到 PostgreSQL 主进程中:
shared_preload_libraries = pg_clickhouse
有助于减少每个会话的内存占用和加载开销,但在库更新时需要 重启集群。

数据类型

pg_clickhouse 将以下 ClickHouse 数据类型映射为 PostgreSQL 数据 类型。IMPORT FOREIGN SCHEMA 在导入列时会使用 PostgreSQL 列中的第一种类型;其他类型可在 CREATE FOREIGN TABLE 语句中使用:
ClickHousePostgreSQL说明
Boolboolean
Datedate
Date32date
DateTimetimestamptz
Decimalnumeric
Float32real
Float64double precision
IPv4inet
IPv6inet
Int16smallint
Int32integer
Int64bigint
Int8smallint
JSONjsonb, json
Stringtext, bytea
UInt16integer
UInt32bigint
UInt64bigint值大于 BIGINT 最大值时会报错
UInt8smallint
UUIDuuid
更多说明和细节如下。

BYTEA

ClickHouse 不提供与 PostgreSQL BYTEA 类型等效的类型,但允许将任意字节存储在 String 类型中。一般情况下,ClickHouse 字符串应映射到 PostgreSQL 的 TEXT 类型;若需使用二进制数据,则应映射到 BYTEA。示例:
-- 创建带有 String 列的 ClickHouse 表。
SELECT clickhouse_raw_query($$
    CREATE TABLE bytes (
        c1 Int8, c2 String, c3 String
    ) ENGINE = MergeTree ORDER BY (c1);
$$);

-- 创建带有 BYTEA 列的外部表。
CREATE FOREIGN TABLE bytes (
    c1 int,
    c2 BYTEA,
    c3 BYTEA
) SERVER ch_srv OPTIONS( table_name 'bytes' );

-- 向外部表中插入二进制数据。
INSERT INTO bytes
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- 查看结果。
SELECT * FROM bytes;
最终的 SELECT 查询将输出:
c1 |                             c2                             |                 c3
----+------------------------------------------------------------+------------------------------------
  1 | \x1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | \xae3b28cde02542f81acce8783245430d
  2 | \x5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | \x23e7c6cacb8383f878ad093b0027d72b
  3 | \x53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | \x7e969132fc656148b97b6a2ee8bc83c1
  4 | \x4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | \x8ef30f44c65480d12b650ab6b2b04245
(4 行)
请注意,如果 ClickHouse 列中存在空字节,使用 TEXT 列的外部表将无法输出正确的值:
-- 创建包含 TEXT 列的外部表。
CREATE FOREIGN TABLE texts (
    c1 int,
    c2 TEXT,
    c3 TEXT
) SERVER ch_srv OPTIONS( table_name 'bytes' );

-- 将二进制数据编码为十六进制。
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 列插入二进制值将会成功,并按预期运行:
-- 通过文本列插入:
TRUNCATE texts;
INSERT INTO texts
SELECT n, sha224(bytea('val'||n)), decode(md5('int'||n), 'hex')
  FROM generate_series(1, 4) n;

-- 查看数据。
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 行)
但如果按 BYTEA 读取,则不会:
# SELECT * FROM bytes;
 c1 |                                                           c2                                                           |                                   c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
  1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
  2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
  3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
  4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 行)
一般来说,TEXT 列只用于编码字符串,BYTEA 列只用于二进制数据, 并且绝不要在两者之间切换。

函数与 operator 参考

函数

这些函数提供了查询 ClickHouse 数据库的接口。

clickhouse_raw_query

SELECT clickhouse_raw_query(
    'CREATE TABLE t1 (x String) ENGINE = Memory',
    'host=localhost port=8123'
);
通过其 HTTP interface 连接到 ClickHouse 服务,执行单个 查询,然后断开连接。可选的第二个参数用于指定连接 字符串,默认为 host=localhost port=8123。支持的连接 参数如下:
  • host:要连接到的主机;必填。
  • port:要连接的 HTTP 端口;默认为 8123,除非 host 是 ClickHouse Cloud 主机,此时默认值为 8443
  • dbname:要连接到的数据库名称。
  • username:连接时使用的用户名;默认为 default
  • password:用于身份验证的密码;默认无密码
默认情况下,没有任何角色具有对此函数的 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)

下推函数

pg_clickhouse 会将条件子句 (HAVINGWHERE 子句) 中使用的部分 PostgreSQL 内置函数下推到 ClickHouse 中执行。该子集与 ClickHouse 中的对应函数如下:

下推操作符

自定义函数

这些由 pg_clickhouse 创建的自定义函数,可为部分在 PostgreSQL 中没有对应实现的 ClickHouse 函数提供外部查询下推。如果其中任何函数无法下推,则会引发异常。

扩展下推

pg_clickhouse 可识别部分核心扩展和第三方扩展中的函数,并将其下推为 ClickHouse 中对应的等效函数。

re2

所有 re2 extension 函数都可 1:1 下推到 ClickHouse:

intarray

有一个 intarray 函数可下推至 ClickHouse:

fuzzystrmatch

两个 fuzzystrmatch 函数可下推至 ClickHouse:

下推类型转换

对于兼容的 数据类型,pg_clickhouse 会下推 CAST(x AS bigint) 这类类型转换。 对于不兼容的类型,下推会失败;例如,如果此示例中的 x 是 ClickHouse 的 UInt64, ClickHouse 会拒绝执行该类型转换。 为了将类型转换下推到不兼容的 数据类型,pg_clickhouse 提供了 以下函数。如果这些函数没有被下推,就会在 PostgreSQL 中引发异常。

聚合函数下推

以下 PostgreSQL 聚合函数可下推到 ClickHouse。

自定义聚合函数

这些由 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 后缀 DESCNULLS FIRST, 使用它们会报错。

下推窗口函数

这些 PostgreSQL [窗口函数] 可以通过 OVER (PARTITION BY ... ORDER BY ...) 子句下推到 ClickHouse;在适用情况下,也包括窗口帧规范。 排名函数 (row_numberrankdense_rankntilecume_distpercent_rank) 在下推时会省略窗口帧子句,因为 ClickHouse 不接受这些函数的窗口帧规范。

兼容性说明

正则表达式

pg_clickhouse.pushdown_regex 为 true (默认值) 时, pg_clickhouse 会将正则表达式下推为 ClickHouse 中的等效形式, 并尽力保证基本兼容性,但你仍需了解二者之间的差异,以及 pg_clickhouse 如何处理这些差异。
  • PostgreSQL 支持 POSIX Regular Expressions,而 ClickHouse 支持 RE2 Regular Expressions。请注意二者在行为上的差异:当正则表达式由 ClickHouse 计算时 (例如在 WHERE 子句中) ,请按 RE2 编写;当它由 Postgres 计算时 (例如在 SELECT 子句中) ,请按 POSIX 编写。
  • pg_clickhouse 会将 Postgres 的 [Regex flags] 预置到 ClickHouse 正则表达式的 (?) 中,以实现下推。例如:
    regexp_like(val, '^VAL\d', 'i')
    
    会变成
    match(val, concat('(?i-s)', '^VAL\\d'))
    
    注意这里包含了 -s;这是为了禁用 ClickHouse 默认开启的 s, 以使其行为与 Postgres 正则表达式保持一致。 如果 Postgres 函数调用中的标志包含 s,pg_clickhouse 就不会添加 -s。 不幸的是,这种行为会破坏 Postgres 24 及更早版本中某些正则表达式的兼容性。
  • 两者共同支持、因此可在由 ClickHouse 计算时使用的标志只有:
    • i:不区分大小写
    • m:多行模式:
    • s:让 . 匹配 \n
    • p:部分换行敏感匹配 (按与 s 相同的方式处理)
    • t:严格语法 (默认值,会被 pg_clickhouse 移除)
    RE2 仅支持这些标志;不要使用任何其他 Postgres flags
  • 传递给正则表达式函数的任何其他标志都会导致该 函数无法下推。
  • 例外是 regexp_replace(),它还支持 g 标志。当 设置了 g 时,pg_clickhouse 会使用 replaceRegexpAll() 而不是 replaceRegexpOne(),并在预置其他标志前移除该标志。
  • Postgres 的 regexp_replace() 的 replacement 参数支持使用 \& 表示整个匹配结果,而 ClickHouse 则使用 \0 表示整个 匹配结果。函数下推到 ClickHouse 时,请务必使用 \0
为避免任何歧义,建议设置 pg_clickhouse.pushdown_regex 以阻止 Postgres 正则表达式下推到 ClickHouse,并使用 re2 extension;对于该扩展,pg_clickhouse 支持将 与 ClickHouse 兼容的 RE2 正则表达式进行直接下推

to_char()

PostgreSQL 中用于 timestamptimestamp with time zoneto_char() 仅在 format argument 为非 NULL 的 string constant,且其中每个 PostgreSQL 关键字 在 ClickHouse 中都有一个逐字节完全一致的对应项时,才会下推到 ClickHouse formatDateTime。 如果 format 是动态的 (不是 Const) ,或者包含任何不支持的关键字或 modifier, 则该调用会回退到 PostgreSQL 本地求值——绝不会在部分翻译的情况下尝试下推, 因此输出会保持与 PG 兼容。 用于 numericinterval 及其他非 timestamp 类型的双参数 to_char() 形式永远不会下推;ClickHouse formatDateTime 仅用于格式化日期时间值。

已转换的关键字

PostgreSQLClickHouse含义
YYYY, yyyy%Y4 位年份
YY, yy%y2 位年份
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 的格式说明符前缀。引号外的 \" 也会作为字面量 " 原样传递。在 "..." 内部,反斜杠 只会转义 ";其他反斜杠序列都会被视为字面文本。

作者

David E. Wheeler 版权所有 (c) 2025-2026,ClickHouse
最后修改于 2026年6月10日