Описание
pg_clickhouse — это расширение PostgreSQL, которое позволяет выполнять удалённые запросы к базам данных ClickHouse, в том числе через [обёртка сторонних данных]. Оно поддерживает PostgreSQL 13 и выше, а также ClickHouse 23.3 и выше.
Начало работы
Проще всего попробовать pg_clickhouse с помощью [Docker-образа], который представляет собой стандартный Docker-образ PostgreSQL с расширениями pg_clickhouse и 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 придерживается Semantic Versioning для своих публичных релизов.
- Основная версия увеличивается при изменениях API
- Дополнительная версия увеличивается при обратно совместимых изменениях SQL
- Номер патча увеличивается при изменениях только в бинарном файле
После установки PostgreSQL отслеживает два варианта версии:
- Версия библиотеки (определяемая
PG_MODULE_MAGICв PostgreSQL 18 и выше) включает полную семантическую версию, которая видна в выводе функцииpgch_version()или функции Postgrespg_get_loaded_modules(). - Версия расширения (определяемая в control-файле) включает только основную
и дополнительную версии, которые видны в таблице
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, чтобы воспользоваться
обновлением.
Справочник по SQL DDL
В следующих выражениях SQL DDL используется pg_clickhouse.
CREATE EXTENSION
Используйте CREATE EXTENSION, чтобы добавить pg_clickhouse в базу данных:
CREATE EXTENSION pg_clickhouse;Используйте WITH 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, чтобы переместить расширение в новую схему: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: сжатие native-протокола для драйвера "binary": одно из значений "none", "lz4" или "zstd". По умолчанию — "lz4". Для драйвера "http" не используется.dbname: база данных ClickHouse, используемая при подключении. По умолчанию — "default".host: имя хоста сервера ClickHouse. По умолчанию — "localhost";port: порт сервера ClickHouse, к которому нужно подключаться. Значения по умолчанию следующие:- 9440, если
driver— "binary" иhost— хост ClickHouse Cloud - 9004, если
driver— "binary" иhostне является хостом ClickHouse Cloud - 8443, если
driver— "http" иhost— хост ClickHouse Cloud - 8123, если
driver— "http" иhostне является хостом ClickHouse Cloud
- 9440, если
min_tls_version: минимальная версия протокола TLS, согласуемая при подключениях, использующих TLS. Одно из значений:TLSv1,TLSv1.1,TLSv1.2илиTLSv1.3. По умолчанию — минимальная версия, заданная в самой библиотеке TLS. Применяется к обоим драйверам.secure: управляет использованием TLS для подключения. Возможные значения:auto(по умолчанию): использовать TLS, еслиhost— хост ClickHouse Cloud илиport— защищенный порт; в противном случае — 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. Например,
чтобы сопоставить текущего пользователя PostgreSQL с удалённым пользователем ClickHouse при
подключении через внешний сервер taxi_srv:
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 получит список всех table в указанной базе данных ("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: Имя удалённой таблицы. По умолчанию используется имя, указанное для внешней таблицы.engine: [движок таблицы], используемый таблицей ClickHouse. ДляCollapsingMergeTree()иAggregatingMergeTree()pg_clickhouse автоматически применяет параметры к функциональным выражениям, выполняемым для таблицы.
Используйте тип данных, соответствующий удалённому типу данных ClickHouse для каждого столбца. Поддерживаются следующие опции столбцов:
-
column_name: Имя столбца на стороне ClickHouse, которое используется вместо имени атрибута PostgreSQL при генерации запросов и операций вставки. Это полезно для сопоставления имён столбцов 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)Этот запрос передаётся в ClickHouse через узел плана "Foreign Scan" как удалённый 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, чтобы определить степень использования pushdown. Например, для приведённого выше запроса всё выполнение переносится в 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 также выполняет pushdown 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)JOIN с локальной таблицей без тщательной настройки приведёт к менее эффективным
запросам. В этом примере мы создаём локальную копию
таблицы nodes и выполняем JOIN с ней вместо удалённой таблицы:
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В этом случае мы можем перенести большую часть агрегации в ClickHouse,
выполнив группировку по node_id вместо локального столбца, а затем выполнить JOIN
с таблицей lookup:
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 — по одной на каждый узел.
Партиционированные таблицы
[Партиционированная таблица] 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.
Для агрегирования данных по локальным и внешним партициям требуется [пораздельная агрегация партиций], которая в 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, sum of 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)Обратите внимание, что были отправлены полные значения дат, а не плейсхолдеры параметров.
Так происходит для первых пяти запросов, как описано в
[заметках о 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⚠️ Ограничения Batch API
В pg_clickhouse пока не реализована поддержка Batch API вставки PostgreSQL FDW. Поэтому 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 —
однако учтите, что корректность pushdown зависит от некоторых значений по умолчанию:
join_use_nulls для внешних JOIN и transform_null_in для семейства IN
(см. IN и семантика NULL).
SET pg_clickhouse.session_settings = '';Синтаксис представляет собой список пар ключ/значение, разделённых запятыми и отделённых друг от друга одним или несколькими пробелами. Ключи должны соответствовать [настройкам ClickHouse]. Экранируйте пробелы, запятые и символы обратной косой черты в значениях с помощью обратной косой черты:
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. К ним относятся:
date_time_output_format: HTTP-драйвер требует, чтобы значение было "iso"format_tsv_null_representation: HTTP-драйвер требует значение по умолчаниюoutput_format_tsv_crlf_end_of_lineHTTP-драйвер требует значение по умолчанию
В остальных случаях pg_clickhouse не проверяет настройки, а передаёт их в ClickHouse для каждого запроса. Таким образом, он поддерживает все настройки каждой версии ClickHouse.
Обратите внимание: pg_clickhouse должен быть загружен до установки
pg_clickhouse.session_settings; для этого либо используйте [предварительную загрузку разделяемой библиотеки], либо
просто воспользуйтесь одним из объектов в расширении, чтобы гарантировать его загрузку.
pg_clickhouse.pushdown_regex
Параметр pg_clickhouse.pushdown_regex определяет, будет ли pg_clickhouse
выполнять pushdown функций и операторов регулярных выражений. По умолчанию это включено;
установите для этого параметра значение false, чтобы отключить pushdown для них:
SET pg_clickhouse.pushdown_regex = 'false';См. Регулярные выражения для получения подробной информации.
ALTER ROLE
Используйте команду SET оператора ALTER ROLE для предварительной загрузки pg_clickhouse
и/или для установки его параметров для определённых ролей:
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Используйте команду RESET оператора ALTER ROLE, чтобы сбросить предварительную загрузку 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Предварительная загрузка
Если pg_clickhouse нужен для всех или почти всех подключений Postgres, рассмотрите [предварительную загрузку разделяемой библиотеки], чтобы она загружалась автоматически:
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 max |
| UInt8 | smallint | |
| UUID | uuid |
Любой столбец также можно прочитать как text, varchar или другой строковый тип. Значение
сначала преобразуется в указанный выше тип PostgreSQL, а затем выводится с помощью функции
вывода этого типа. Для значений UInt64, превышающих максимум bigint, по-прежнему возникает ошибка, поэтому выводите их
с помощью функции 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 использует строки, завершающиеся нулевым байтом, и не поддерживает нулевые байты внутри строк.
Попытка вставить бинарные значения в столбцы 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)Справочник по Function и операторам
Функции
Эти функции служат интерфейсом для выполнения запросов к базе данных 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: Порт, к которому нужно подключиться. По умолчанию8123для драйвера "http" или9000для драйвера "binary"; при подключении к хосту ClickHouse Cloud используются соответственно8443или9440dbname: Имя базы данных, к которой нужно подключиться.username: Имя пользователя, от которого выполняется подключение; по умолчаниюdefaultpassword: Пароль, используемый для аутентификации; по умолчанию пароль отсутствует
Оба драйвера возвращают строки, разделенные табуляцией (значения null как \N), но представление
отдельных значений различается: драйвер "http" возвращает собственное TSV-форматирование ClickHouse
без изменений, тогда как драйвер "binary" преобразует каждое значение с помощью
функции вывода PostgreSQL.
По умолчанию ни одна роль не имеет доступа EXECUTE к этой функции; рассмотрите возможность
предоставления доступа GRANT только тем ролям, которым действительно требуется выполнять
произвольные запросы ClickHouse, например выделенной роли администратора 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 для указанного
стороннего сервера, при необходимости подключаясь с использованием параметров сервера и
пользовательского сопоставления текущего пользователя:
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 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, повторно используя его driver,
учётные данные, базу данных и кэш подключений.
Как процедуру его следует вызывать с помощью CALL, а не SELECT; он не возвращает
строк. По умолчанию ни у одной роли нет права EXECUTE; предоставьте его роли с помощью GRANT, чтобы она могла
использовать процедуру.
GRANT EXECUTE ON PROCEDURE clickhouse_perform(text, text) TO ch_admin;Функции с pushdown
pg_clickhouse выполняет pushdown для части встроенных функций PostgreSQL, используемых
в условных выражениях (в секциях HAVING и WHERE). Для них используются следующие
эквиваленты в 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,acoshpushdown не применяется: 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_partdate(timestamp)&date(timestamptz): toDate (при обратном преобразовании как алиас CHdate)array_position: indexOf с nullIf для преобразования0вNULLи arraySlice при наличии третьего аргумента, задающего начальный индекс поиска; обратите внимание, чтоnanпока не сопоставляетсяarray_cat: arrayConcatarray_append: arrayPushBackarray_prepend: arrayPushFrontarray_remove: arrayRemovecardinality: длинаarray_length(array, 1):nullIf(length(array), 0)array_length&cardinality: длинаarray_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, ...): substringlength(text): lengthUTF8length(bytea)&octet_length: lengthreverse(text): reverseUTF8reverse(bytea): reversestrpos: positionUTF8regexp_like: matchregexp_match: extractGroups если регулярное выражение содержит подвыражения в скобках; в противном случае extractAll со срезом через arraySlice.regexp_replace: replaceRegexpOne или replaceRegexpOne при наличии флагаgregexp_split_to_array: splitByRegexpmd5: MD5encode(bytea, fmt), еслиfmt— строковая константа (регистронезависимая):encode(bytea, 'hex'): hex обёрнутая в lower, поскольку PostgreSQL выводит hex в нижнем регистре.encode(bytea, 'base64'): base64Encode обёрнутая в replaceRegexpAll для воспроизведения разрыва строки MIME (RFC 2045), вставляемого PostgreSQL через каждые 76 символов.encode(bytea, 'base64url')(PostgreSQL 19+): base64URLEncode, которая соответствует URL-алфавиту RFC 4648 PostgreSQL без дополнения.
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.
Операторы pushdown
- Срез массива (
arr[L:U]): arraySlice @>(массив содержит): hasAll<@(массив содержится в): hasAll&&(массивы пересекаются): hasAny~(совпадение с регулярным выражением): match!~(нет совпадения с регулярным выражением): match~*(регистронезависимое отсутствие совпадения с регулярным выражением): match!~*(регистронезависимое отсутствие совпадения с регулярным выражением): match->>(извлечение элемента JSON/JSONB как текста): синтаксис подстолбцов->(извлечение JSON/JSONB): toJSONString + синтаксис подстолбцов
Семантика IN и NULL
ClickHouse вычисляет IN в рамках двузначной логики: если проверяемое значение не находит
совпадений, возвращается 0, даже если участвует NULL, тогда как PostgreSQL возвращает
NULL. Чтобы сохранить семантику PostgreSQL, pg_clickhouse безусловно выполняет pushdown семейства
IN для списка констант или массива (IN, NOT IN, = ANY, = ALL,
<> ANY, <> ALL): нативной или недорогой формы, когда можно
доказать, что ни проверяемое значение, ни элемент массива не могут быть NULL, либо защищённой
формы CASE в остальных случаях, которая вместо этого проверяет NULL во время выполнения,
вычисляя точный трёхзначный результат PostgreSQL (TRUE, FALSE, NULL) в
любом контексте, включая позиции значений, например в списке SELECT или
GROUP BY.
Фильтр NOT IN (SELECT ...) по столбцам с типом Nullable также поддерживает pushdown;
при обратном преобразовании добавляются компенсирующие проверки, сохраняющие поведение PostgreSQL:
множество, содержащее NULL, исключает каждую строку, а NULL в проверяемом значении проходит только
при сравнении с пустым множеством. Каждая проверка опускается, если объявление NOT NULL
доказывает её ненужность. В отличие от форм с массивами выше, эта проверка применяется только
в простом условии фильтрации (или под NOT); IN (SELECT ...) (в позиции значения) и тела
сгруппированных или агрегированных подзапросов по-прежнему не поддерживают pushdown.
Объявление столбцов как NOT NULL максимально расширяет возможности pushdown, позволяя вместо этого
отправлять более дешёвую форму без проверок; IMPORT FOREIGN SCHEMA делает это автоматически для
столбцов ClickHouse без типа Nullable. При доказательстве учитываются константы, отличные от NULL,
столбцы NOT NULL и базовая арифметика (+, -, *, унарный -) над ними.
Эти правила предполагают, что в ClickHouse используется значение по умолчанию transform_null_in = 0, которое
pg_clickhouse задаёт для каждого запроса через значение по умолчанию
параметра pg_clickhouse.session_settings,
чтобы профиль сервера ClickHouse не мог незаметно его изменить. Установка
transform_null_in = 1 нарушает семантику каждого IN, для которого выполняется pushdown.
Пользовательские функции
Эти пользовательские функции, созданные pg_clickhouse, обеспечивают pushdown внешних запросов для некоторых функций ClickHouse, у которых нет аналогов в PostgreSQL. Если какую-либо из этих функций не удастся выполнить через pushdown, будет вызвано исключение.
Pushdown для расширений
pg_clickhouse распознает функции некоторых основных и сторонних расширений и передает их на pushdown к их эквивалентам в ClickHouse.
re2
Все операторы и функции [расширения re2] проталкиваются в ClickHouse в соотношении 1:1:
@~→ matchre2match→ matchre2extract→ extractre2extractall→ extractAllre2regexpextract→ regexpExtractre2extractgroups→ extractGroupsre2replaceregexpone→ replaceRegexpOnere2replaceregexpall→ replaceRegexpAllre2countmatches→ countMatchesre2countmatchescaseinsensitive→ countMatchesCaseInsensitivere2multimatchany→ multiMatchAnyre2multimatchanyindex→ multiMatchAnyIndexre2multimatchallindices→ multiMatchAllIndices
intarray
Одна функция intarray выполняется в ClickHouse:
idx→ indexOf
fuzzystrmatch
В ClickHouse проталкиваются две функции fuzzystrmatch:
soundex: soundexlevenshtein(с двумя аргументами): editDistanceUTF8
Приведения типов с pushdown
pg_clickhouse выполняет pushdown для приведений типов, таких как CAST(x AS bigint), если
типы данных совместимы. Для несовместимых типов pushdown завершится ошибкой; если x в этом
примере имеет тип ClickHouse UInt64, ClickHouse откажется приводить это значение.
Чтобы выполнять pushdown приведений к несовместимым типам данных, pg_clickhouse предоставляет следующие функции. Они вызывают исключение в PostgreSQL, если pushdown не выполняется.
Агрегатные функции с pushdown
Для этих агрегатных функций PostgreSQL поддерживается pushdown в 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, обеспечивают pushdown внешних запросов для некоторых агрегатных функций ClickHouse, не имеющих эквивалентов в PostgreSQL. Если какую-либо из этих функций невозможно передать через pushdown, будет вызвано исключение.
Pushdown для агрегатных функций ordered set
Эти [агрегатные функции ordered set] сопоставляются с [параметрическими
агрегатными функциями] ClickHouse путём передачи их непосредственного аргумента в качестве параметра, а
выражений ORDER BY — в качестве аргументов. Например, следующий запрос 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
Пользовательская агрегатная функция ordered set
Эти пользовательские [агрегатные функции ordered set], созданные pg_clickhouse, обеспечивают pushdown внешних запросов для некоторых [параметрических агрегатных функций] ClickHouse. Если какую-либо из этих функций не удаётся выполнить с pushdown, будет вызвано исключение.
quantile(double): quantilequantileExact(double): quantileExact
Пользовательские агрегатные функции ordered set
Эти пользовательские [агрегатные функции ordered set], созданные pg_clickhouse, поддерживают pushdown внешних запросов для отдельных параметрических [агрегатных функций] ClickHouse. Если любую из этих функций нельзя выполнить с pushdown, будет вызвано исключение.
Оконные функции с pushdown
Эти [оконные функции] PostgreSQL проталкиваются в ClickHouse с секциями OVER (PARTITION BY ... ORDER BY ...), включая спецификации рамки окна, где это
применимо.
- row_number
- rank
- dense_rank
- ntile
- cume_dist
- percent_rank
- lead
- lag
- first_value
- last_value
- nth_value
min/max(с секциейOVER)
При pushdown функции ранжирования (row_number, rank, dense_rank, ntile, cume_dist,
percent_rank) не включают секцию рамки окна, поскольку ClickHouse
не поддерживает спецификации рамки окна для этих функций.
Примечания по совместимости
Регулярные выражения
Хотя pg_clickhouse выполняет pushdown регулярных выражений в эквиваленты ClickHouse, когда pg_clickhouse.pushdown_regex имеет значение true (по умолчанию), и старается обеспечить базовый уровень совместимости, важно учитывать различия между ними и то, как pg_clickhouse их обрабатывает.
-
PostgreSQL поддерживает [регулярные выражения POSIX], а ClickHouse — регулярные выражения RE2. Учитывайте различия в их поведении: используйте RE2, если регулярное выражение будет обрабатываться в ClickHouse (например, в предложении
WHERE), и POSIX, если оно будет обрабатываться в Postgres (например, в предложенииSELECT). -
pg_clickhouse проталкивает [флаги Postgres], добавляя их в начало регулярного выражения ClickHouse внутри
(?). Например:regexp_like(val, '^VAL\d', 'i')Преобразуется в
match(val, concat('(?i)', '^VAL\\d')) -
Единственные флаги, которые поддерживаются в обоих вариантах и потому могут использоваться при обработке в ClickHouse:
Флаг Как Примечания iiрегистронезависимое сопоставление mm-s^и$соответствуют началу/концу строки, а также началу/концу текстаnm-sпсевдоним Postgres для mp-sне позволяет .и[^x]соответствовать\nssпозволяет .и[^x]соответствовать\ntстрогий синтаксис, игнорируется wmобратное частичное сопоставление с учётом переводов строки RE2 поддерживает только эти флаги; не используйте другие [флаги Postgres].
-
В этой таблице приведено краткое описание влияния различных флагов (а также отсутствия флага, что эквивалентно
s) на сопоставление символов новой строки и концов строк. Обратите внимание, что в Postgres флагиmиpне позволяют отрицательным символьным классам ([^xyz]) сопоставляться с символом новой строки, тогда как их аналоги в ClickHouse это ограничение не вводят. В остальном поведение ClickHouse такое же, как в Postgres:Шаблон регулярного выражения для a\nbPostgres 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 ✔ -
Любые другие флаги, передаваемые функциям регулярных выражений, будут препятствовать pushdown функции.
-
Исключение —
regexp_replace(), которая также поддерживает флагg. Когда заданg, pg_clickhouse используетreplaceRegexpAll()вместоreplaceRegexpOne()и удаляет этот флаг перед добавлением остальных флагов. -
Аргумент replacement в Postgres
regexp_replace()поддерживает\&для ссылки на всё совпадение, тогда как в ClickHouse для ссылки на всё совпадение используется\0. Обязательно используйте\0, когда функция проталкивается в ClickHouse. -
Postgres
regexp_matchвозвращаетNULL, если совпадений нет, тогда как проталкиваемые выражения возвращают пустой массив. ИспользуйтеCOALESCE(), чтобы вместоNULLвозвращать пустой массив и тем самым сравнивать возвращаемые значения единообразно. Например:SELECT * FROM events WHERE COALESCE(regexp_match(msg, '^ERR'), '{}');
Чтобы полностью избежать неоднозначности, рассмотрите возможность установки pg_clickhouse.pushdown_regex, чтобы предотвратить передачу регулярных выражений Postgres через pushdown в ClickHouse, и используйте re2 extension, для которого pg_clickhouse поддерживает прямой pushdown совместимых с ClickHouse регулярных выражений RE2.
to_char()
PostgreSQL to_char() для timestamp и timestamp with time zone
проталкивается в ClickHouse formatDateTime только в том случае, если аргумент format
— это строковая константа, не равная NULL, и каждому ключевому слову PostgreSQL в ней
соответствует побайтно идентичный эквивалент в ClickHouse. Если формат задаётся динамически
(не Const) или содержит неподдерживаемое ключевое слово либо модификатор,
вызов переключается на локальное вычисление в PostgreSQL — pushdown никогда
не применяется при частичном переводе, поэтому вывод остаётся совместимым с PG.
Формы to_char() с двумя аргументами для numeric, interval и других
нетемпоральных типов никогда не проталкиваются; 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 |
индикатор AM/PM, всегда в верхнем регистре |
Текст в кавычках и литералы
Текст, заключённый в "...", передаётся как есть; при этом любой символ %
удваивается до %%, чтобы экранировать префикс спецификатора ClickHouse. Последовательность \" вне
кавычек также передаётся как литеральный ". Внутри "..." обратная косая черта
экранирует только "; другие последовательности с обратной косой чертой трактуются как литеральный текст.
Авторские права
Авторские права (c) 2025-2026, ClickHouse