메인 콘텐츠로 건너뛰기

설명

pg_clickhouse는 foreign data wrapper를 포함해 ClickHouse 데이터베이스에서 원격 쿼리를 실행할 수 있게 해주는 PostgreSQL 확장 기능입니다. PostgreSQL 13 이상과 ClickHouse 23 이상을 지원합니다.

시작하기

pg_clickhouse를 가장 간단하게 사용해 보는 방법은 Docker image를 사용하는 것이며, 여기에는 pg_clickhouse와 [re2][re2 extension] 확장 기능이 포함된 표준 PostgreSQL Docker image가 들어 있습니다:
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으로 정의됨)에는 전체 시맨틱 버전이 포함되며, pgch_version() 함수의 출력 또는 Postgres pg_get_loaded_modules() 함수에서 확인할 수 있습니다.
  • 확장 기능 버전(control file에 정의됨)에는 메이저 버전과 마이너 버전만 포함되며, 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

데이터베이스(database)에 pg_clickhouse를 추가하려면 CREATE EXTENSION을 사용하십시오:
CREATE EXTENSION pg_clickhouse;
특정 스키마에 설치하려면 WITH SCHEMA를 사용하십시오(권장):
CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;

ALTER EXTENSION

pg_clickhouse를 변경하려면 ALTER EXTENSION을 사용하십시오. 예시:
  • 새 pg_clickhouse release를 설치한 후 UPDATE 절을 사용하십시오:
    ALTER EXTENSION pg_clickhouse UPDATE;
    
  • 확장 기능을 새 스키마로 이동하려면 SET SCHEMA를 사용하십시오:
    CREATE SCHEMA ch;
    ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
    

DROP EXTENSION

데이터베이스에서 pg_clickhouse 확장 기능을 제거하려면 DROP EXTENSION을 사용합니다:
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 스트리밍에 사용할 대략적인 바이트 단위 배치 크기입니다. 배치는 행 경계에서 분할됩니다. 기본값은 50000000(50 MB)입니다. 0으로 설정하면 스트리밍이 비활성화되고 전체 응답을 버퍼링합니다. 외부 테이블은 이 값을 재정의할 수 있습니다.
  • 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;
다른 객체가 server에 종속되어 있으면 이 명령은 실패합니다. 해당 종속 객체도 함께 삭제하려면 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 acts (
    user_id    bigint NOT NULL,
    page_views int,
    duration   smallint,
    sign       smallint
) SERVER taxi_srv OPTIONS(
    table_name 'acts'
    engine 'CollapsingMergeTree'
);
지원되는 테이블 옵션은 다음과 같습니다:
  • database: 원격 데이터베이스의 이름입니다. 기본값은 외부 서버에 정의된 데이터베이스입니다.
  • fetch_size: HTTP streaming의 대략적인 배치 크기(바이트 단위)입니다. 서버 수준의 fetch_size를 재정의합니다. 기본값은 50000000(50 MB)입니다. 0은 streaming을 비활성화하고 전체 응답을 버퍼링합니다.
  • 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)
이 쿼리의 원격 SQL은 “Foreign Scan” 계획 노드를 통해 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/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는 같은 원격 서버에 있는 테이블 간 조인도 푸시다운합니다:
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
이 경우 로컬 컬럼 대신 node_id를 기준으로 그룹화한 다음, 나중에 lookup 테이블을 조인하여 더 많은 집계 처리를 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개(전체 행)에서 각 노드당 1개씩, 총 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
prepared statement를 실행할 때는 평소처럼 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)
매개변수화된 실행을 사용하면 25.8 이전 버전의 ClickHouse에서 http 드라이버가 DateTime의 시간대를 올바르게 변환하지 못합니다. 이 문제는 [근본 원인 버그]가 [수정]되면서 해결되었습니다. 경우에 따라 PostgreSQL은 PREPARE를 사용하지 않아도 매개변수화된 쿼리 계획을 사용할 수 있습니다. 정확한 시간대 변환이 필요한 쿼리에서 25.8 이상으로 업그레이드할 수 없다면, 대신 binary 드라이버를 사용하십시오.
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 참고 사항]에 설명된 대로 처음 5번의 요청에 해당합니다. 여섯 번째 실행에서는 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)
prepared statement을 해제하려면 DEALLOCATE를 사용합니다:
try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATE

INSERT

원격 ClickHouse 테이블에 값을 삽입할 때는 INSERT 명령을 사용합니다:
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

원격 ClickHouse 테이블에 여러 행을 한 번에 삽입하려면 COPY 명령을 사용합니다:
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는 아직 PostgreSQL FDW의 Batch 삽입 API를 지원하지 않습니다. 따라서 현재 COPY는 레코드 삽입에 INSERT SQL 문을 사용합니다. 이 부분은 향후 릴리스에서 개선될 예정입니다.

LOAD

pg_clickhouse shared library를 로드하려면 LOAD를 사용합니다:
try=# LOAD 'pg_clickhouse';
LOAD
일반적으로 LOAD를 사용할 필요는 없습니다. Postgres는 pg_clickhouse의 기능(함수, 외부 테이블 등) 중 하나를 처음 사용할 때 자동으로 로드하기 때문입니다. LOAD pg_clickhouse가 유용할 수 있는 경우는 한 가지뿐입니다. 즉, 해당 매개변수에 의존하는 쿼리를 실행하기 전에 pg_clickhouse 매개변수를 SET해야 할 때입니다.

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 서버 설정을 사용하려면 빈 문자열로 설정하십시오.
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.session_settings를 설정하기 전에 pg_clickhouse를 먼저 로드해야 합니다. [공유 라이브러리 사전 로드]를 사용하거나, 확장 기능의 객체 중 하나를 사용해 로드되도록 하면 됩니다.

pg_clickhouse.pushdown_regex

pg_clickhouse.pushdown_regex 매개변수는 pg_clickhouse가 정규식 함수와 연산자를 pushdown할지 여부를 제어합니다. 기본적으로 활성화되어 있으며, 이들이 pushdown되지 않도록 하려면 이 매개변수를 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 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
server를 다시 시작하지 않고도 업데이트를 적용할 수 있어 유용합니다. 다시 연결하기만 하면 됩니다. ALTER ROLE을 통해 특정 사용자 또는 역할별로 설정할 수도 있습니다.

shared_preload_libraries

시작 시 PostgreSQL 부모 프로세스에 공유 라이브러리를 로드합니다:
shared_preload_libraries = pg_clickhouse
각 세션의 메모리 사용량과 로드 오버헤드를 줄이는 데 유용하지만, 라이브러리가 업데이트되면 클러스터를 재시작해야 합니다.

데이터 타입

pg_clickhouse는 다음 ClickHouse 데이터 타입을 PostgreSQL 데이터 타입으로 매핑합니다. IMPORT FOREIGN SCHEMA는 컬럼을 가져올 때 PostgreSQL 컬럼의 첫 번째 타입을 사용하고, 추가 타입은 CREATE FOREIGN TABLE SQL 문에서 사용할 수 있습니다:
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 컬럼에 nul 바이트가 포함된 경우, TEXT 컬럼을 사용하는 외부 테이블은 올바른 값을 출력하지 않습니다:
-- TEXT 컬럼으로 외부 테이블을 생성합니다.
CREATE FOREIGN TABLE texts (
    c1 int,
    c2 TEXT,
    c3 TEXT
) SERVER ch_srv OPTIONS( table_name 'bytes' );

-- 바이너리 데이터를 16진수로 인코딩합니다.
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)
2번째와 3번째 행에는 잘린 값이 포함되어 있습니다. PostgreSQL이 nul 종료 문자열(nul-terminated string)을 사용하며 문자열 내에 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 rows)
하지만 BYTEA로 읽을 때는 그렇지 않습니다:
# SELECT * FROM bytes;
 c1 |                                                           c2                                                           |                                   c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
  1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
  2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
  3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
  4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 행)
일반적으로 인코딩된 문자열에는 TEXT 컬럼만 사용하고, BYTEA 컬럼은 바이너리 데이터에만 사용하며, 두 타입을 서로 바꿔 사용하지 마십시오.

함수 및 연산자 참고

함수

이 함수들은 ClickHouse 데이터베이스에 쿼리를 실행할 수 있는 인터페이스를 제공합니다.

clickhouse_raw_query

SELECT clickhouse_raw_query(
    'CREATE TABLE t1 (x String) ENGINE = Memory',
    'host=localhost port=8123'
);
HTTP 인터페이스를 통해 ClickHouse 서비스에 연결하여 단일 쿼리를 실행한 후 연결을 종료합니다. 선택적 두 번째 인수는 연결 문자열을 지정하며, 기본값은 host=localhost port=8123입니다. 지원되는 연결 매개변수는 다음과 같습니다:
  • host: 연결할 호스트입니다. 필수입니다.
  • port: 연결할 HTTP 포트입니다. host가 ClickHouse Cloud 호스트가 아닌 경우 기본값은 8123이고, 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 함수에 대해 원격 쿼리 푸시다운을 지원합니다. 이들 함수 중 하나라도 pushdown할 수 없으면 예외를 발생시킵니다.

확장 기능 푸시다운

pg_clickhouse는 일부 핵심 및 서드파티 확장 기능의 함수를 인식하고, 이를 ClickHouse의 해당 함수로 푸시다운합니다.

re2

모든 re2 확장 기능 함수는 ClickHouse에 1:1로 푸시다운됩니다:

intarray

ClickHouse로 푸시다운되는 intarray 함수는 다음 1개입니다:

fuzzystrmatch

ClickHouse로 푸시다운되는 fuzzystrmatch 함수는 2개입니다:

푸시다운 캐스트

pg_clickhouse는 호환되는 데이터 타입(data type)에 대해 CAST(x AS bigint)와 같은 캐스트 연산을 푸시다운합니다. 호환되지 않는 데이터 타입에서는 푸시다운이 실패합니다. 이 예시에서 x가 ClickHouse UInt64이면, ClickHouse는 해당 값의 형 변환을 거부합니다. 호환되지 않는 데이터 타입으로의 캐스트도 푸시다운할 수 있도록 pg_clickhouse는 다음 함수를 제공합니다. 이 함수가 푸시다운되지 않으면 PostgreSQL에서 예외를 발생시킵니다.

푸시다운 집계 함수

다음 PostgreSQL 집계 함수는 ClickHouse로 푸시다운됩니다.

사용자 정의 집계 함수

pg_clickhouse에서 생성한 이러한 사용자 정의 집계 함수는 PostgreSQL에 해당 기능이 없는 일부 ClickHouse 집계 함수에 대해 원격 쿼리 푸시다운 기능을 제공합니다. 이 함수들 중 하나라도 푸시다운되지 않으면 예외가 발생합니다.

푸시다운 정렬된 집합 집계 함수

정렬된 집합 집계 함수직접 인수를 매개변수로 전달하고 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는 지원되지 않으며 오류가 발생합니다.

푸시다운 윈도우 함수

다음 PostgreSQL [윈도우 함수]는 OVER (PARTITION BY ... ORDER BY ...) 절과 함께 ClickHouse로 푸시다운되며, 해당하는 경우 프레임 사양도 포함됩니다. 순위 함수(row_number, rank, dense_rank, ntile, cume_dist, percent_rank)는 푸시다운 시 프레임 절을 생략하는데, ClickHouse가 이러한 함수에 대한 프레임 사양을 허용하지 않기 때문입니다.

호환성 참고사항

정규식

pg_clickhouse는 pg_clickhouse.pushdown_regex가 true일 때(기본값) 정규식을 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는 replaceRegexpOne() 대신 replaceRegexpAll()을 사용하고, 다른 플래그를 앞에 붙이기 전에 해당 플래그를 제거합니다.
  • Postgres regexp_replace()의 replacement 인수는 전체 일치를 가리키는 데 \&를 지원하지만, ClickHouse에서는 전체 일치에 \0를 사용합니다. 함수가 ClickHouse로 푸시다운될 때는 반드시 \0를 사용하십시오.
모호함을 완전히 피하려면 pg_clickhouse.pushdown_regex를 설정하여 Postgres 정규식이 ClickHouse로 푸시다운되지 않도록 하고, re2 확장 기능을 사용하는 방안을 고려하십시오. pg_clickhouse는 이에 대해 ClickHouse와 호환되는 RE2 정규식의 direct pushdown을 지원합니다.

to_char()

timestamptimestamp with time zone에 대한 PostgreSQL to_char()는 포맷 인수가 NULL이 아닌 문자열 상수이고, 여기에 포함된 모든 PostgreSQL 키워드에 바이트 단위로 완전히 동일한 ClickHouse 대응 키워드가 있을 때에만 ClickHouse formatDateTime으로 푸시다운됩니다. 포맷이 동적이거나 (Const가 아니거나), 지원되지 않는 키워드 또는 수정자가 하나라도 포함되어 있으면 해당 호출은 PostgreSQL에서 로컬로 평가됩니다 — 부분 번역으로는 푸시다운을 전혀 시도하지 않으므로 출력은 PG 호환성을 유지합니다. numeric, interval 및 기타 타임스탬프가 아닌 타입에 대한 2개 인수 to_char() 형식은 푸시다운되지 않습니다. ClickHouse formatDateTime은 날짜/시간 값만 포맷합니다.

변환된 키워드

PostgreSQLClickHouse의미
YYYY, yyyy%Y4자리 연도
YY, yy%y2자리 연도
MM, mm%m0으로 채운 월 (01–12)
DD, dd%d0으로 채운 일 (01–31)
DDD, ddd%j0으로 채운 연중 날짜 (001–366)
HH24, hh24%H0으로 채운 24시간제 시 (00–23)
HH, hh, HH12, hh12%I0으로 채운 12시간제 시 (01–12)
MI, mi%i0으로 채운 분 (00–59)
SS, ss%S0으로 채운 초 (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일