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

pg_clickhouse 参考文档

描述

pg_clickhouse 是一个 PostgreSQL 扩展,用于在 ClickHouse 数据库上远程执行查询, 并提供一个 foreign data wrapper。它支持 PostgreSQL 13 及更高版本,以及 ClickHouse 23 及更高版本。

入门

尝试 pg_clickhouse 的最简单方式是使用 [Docker 镜像],该镜像基于标准 PostgreSQL Docker 镜像,并已预装 pg_clickhouse 扩展:

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 的正式发布遵循 [Semantic Versioning] 语义化版本规范。

  • 在 API 发生变更时递增主版本号
  • 在保持向后兼容的 SQL 变更时递增次版本号
  • 在仅有二进制级变更时递增补丁版本号

安装后,PostgreSQL 会跟踪两种版本号:

  • 库版本(在 PostgreSQL 18 及更高版本中由 PG_MODULE_MAGIC 定义)包含完整的语义化版本,可在 pg_get_loaded_modules() 函数的输出中看到。
  • 扩展版本(在控制文件中定义)只包含主版本号和次版本号,可在 pg_catalog.pg_extension 表、pg_available_extension_versions() 函数的输出以及 \dx pg_clickhouse 中看到。

在实践中,这意味着一个只提升补丁版本的发布,例如从 v0.1.0v0.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 的新版本后,使用 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"。
  • host: ClickHouse 服务器的主机名。默认为 "localhost"。
  • port: 要连接到 ClickHouse 服务器的端口。默认值如下:
    • 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 架构中:

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 uact (
    user_id    bigint NOT NULL,
    page_views int,
    duration   smallint,
    sign       smallint
) SERVER taxi_srv OPTIONS(
    table_name 'uact'
    engine 'CollapsingMergeTree'
);

支持的表选项包括:

  • database:远程数据库的名称。默认为外部服务器定义的数据库。
  • table_name:远程表的名称。默认为为外部表指定的名称。
  • engine:ClickHouse 表使用的[表引擎]。对于 CollapsingMergeTree()AggregatingMergeTree(),pg_clickhouse 会自动将参数应用到在该表上执行的函数表达式中。

为每一列使用与远程 ClickHouse 数据类型相匹配的数据类型。对于 AggregateFunction TypeSimpleAggregateFunction Type 列,将数据类型映射为传递给 函数的 ClickHouse 类型,并通过相应的列选项指定聚合函数的名称:

示例:

(aggregatefunction 'sum')

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;

对于使用 AggregateFunction 函数的列,pg_clickhouse 会自动在用于计算该列的聚合函数后追加 Merge

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 uact;

如果存在任何依赖该外部表的对象,此命令会失败。 使用 CASCADE 子句可以将它们一并删除:

DROP FOREIGN TABLE uact CASCADE;

DML SQL 参考

下面的 SQL DML 表达式可能会使用 pg_clickhouse。示例基于由 make-logs.sql 创建的这些 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”的计划节点将远程 SQL 下推到 ClickHouse。

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/totam
(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 下推到 ClickHouse 执行:

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)

如果不进行仔细调优,将本地表参与 JOIN 会导致查询效率较低。在本例中,我们创建一份 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,并在之后再与查找表进行关联:

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 行,每个节点一行。

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 notes] 中所述。在第六次执行时,它会向 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 批量插入 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.session_settings。 该参数用于配置后续查询中要应用的 ClickHouse settings。示例:

SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';

默认值为 join_use_nulls 1。将其设为空字符串以退回到 ClickHouse 服务器端的设置。

SET pg_clickhouse.session_settings = '';

语法为由逗号分隔的键值对列表,各键值对之间以一个或多个空格分隔。键必须对应 ClickHouse settings。值中的空格、逗号和反斜杠需要使用反斜杠进行转义:

SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';

或者使用用单引号包裹的值以避免对空格和逗号进行转义;也可以考虑使用 dollar quoting,从而无需使用双引号:

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 不会验证这些设置,而是在每次查询时将它们传递给 ClickHouse。因此,它支持该 ClickHouse 版本的所有设置。

请注意,必须先加载 pg_clickhouse,然后才能设置 pg_clickhouse.session_settings;可以使用 [shared library preloading],或者 直接使用扩展中的任一对象以确保其被加载。

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 ROLERESET 命令来重置 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 数据类型:

ClickHousePostgreSQL说明
Boolboolean
Datedate
Date32date
DateTimetimestamp
Decimalnumeric
Float32real
Float64double precision
IPv4inet
IPv6inet
Int16smallint
Int32integer
Int64bigint
Int8smallint
JSONjsonb仅适用于 HTTP engine
Stringtext
UInt16integer
UInt32bigint
UInt64bigint当值大于 BIGINT 最大值时会报错
UInt8smallint
UUIDuuid

函数

这些函数为对 ClickHouse 数据库进行查询提供接口。

clickhouse_raw_query

SELECT clickhouse_raw_query(
    'CREATE TABLE t1 (x String) ENGINE = Memory',
    'host=localhost port=8123'
);

通过 ClickHouse 服务的 HTTP 接口连接、执行单个 查询,然后断开连接。可选的第二个参数指定连接字符串, 默认值为 host=localhost port=8123。支持的连接参数包括:

  • host:要连接的主机;必填。
  • port:要连接的 HTTP 端口;默认是 8123,除非 host 是 ClickHouse Cloud 主机,在这种情况下默认是 8443
  • dbname:要连接的数据库名称。
  • username:用于连接的用户名;默认是 default
  • password:用于认证的密码;默认为无密码。

适用于不返回记录的查询;对于有返回值的查询, 结果将作为单个文本值返回:

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 外部表的条件(HAVINGWHERE 子句)中,所有 PostgreSQL 内置函数都会以相同的名称和签名自动下推到 ClickHouse。不过,其中有一些函数在 ClickHouse 中具有不同的名称或签名,需要映射到它们的等价函数。pg_clickhouse 会映射以下函数:

自定义函数

pg_clickhouse 创建的这些自定义函数,为部分在 PostgreSQL 中没有等价实现的 ClickHouse 函数提供外部查询下推功能。如果这些函数中的任意一个无法下推,则会抛出异常。

下推类型转换

pg_clickhouse 会对兼容的数据类型下推诸如 CAST(x AS bigint) 形式的类型转换。 对于不兼容的类型,下推会失败;如果此示例中的 x 是 ClickHouse 的 UInt64, ClickHouse 将拒绝执行该类型转换。

为了在不兼容的数据类型上也能进行下推类型转换,pg_clickhouse 提供了以下函数。 如果这些函数未被下推,就会在 PostgreSQL 中抛出异常。

可下推的聚合函数

这些 PostgreSQL 聚合函数可以下推到 ClickHouse 执行。

自定义聚合

这些由 pg_clickhouse 创建的自定义聚合函数,为部分在 PostgreSQL 中没有等价实现的 ClickHouse 聚合函数提供外部查询下推(foreign query pushdown)能力。如果其中任意函数无法下推,将抛出异常。

下推有序集合聚合函数

这些[有序集合聚合函数]会通过将其 直接参数 作为参数、将其 ORDER BY 表达式作为函数实参传入,从而映射到 ClickHouse 的参数化聚合函数。例如,下面这个 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 不被支持,并会导致报错。

作者

David E. Wheeler

Copyright (c) 2025-2026, ClickHouse

"PostgreSQL 文档:共享库预加载 [PREPARE notes]: https://www.postgresql.org/docs/current/sql-prepare.html#SQL-PREPARE-NOTES "PostgreSQL 文档:PREPARE 注意事项" [query parameters]: https://clickhouse.com/docs/guides/developer/stored-procedures-and-prepared-statements#alternatives-to-prepared-statements-in-clickhouse "ClickHouse 文档:ClickHouse 中预处理语句的替代方案"