> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-8a08bda2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> pg_clickhouse 전체 참고 문서

# pg_clickhouse 참고 문서

<div id="description">
  ## 설명
</div>

pg\_clickhouse는 \[외부 데이터 래퍼]를 포함해 ClickHouse 데이터베이스에서 원격 쿼리 실행을 지원하는 PostgreSQL 확장 기능입니다. PostgreSQL 13 이상과
ClickHouse 23 이상을 지원합니다.

<div id="getting-started">
  ## 시작하기
</div>

pg\_clickhouse를 가장 간단하게 사용해 보려면 [Docker image]를 이용하는 방법이 있습니다. 이 이미지에는
pg\_clickhouse와 \[re2]\[re2
확장 기능]이 포함된 표준 PostgreSQL Docker image가 들어 있습니다:

```sh theme={null}
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 테이블 가져오기와
쿼리 푸시다운을 시작하려면 [튜토리얼](/ko/products/managed-postgres/extensions/pg_clickhouse/tutorial)을 참조하십시오.

<div id="usage">
  ## 사용
</div>

```sql theme={null}
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;
```

<div id="versioning-policy">
  ## 버전 정책
</div>

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`를 실행해야 합니다.

<div id="ddl-sql-reference">
  ## DDL SQL 참고
</div>

다음 SQL [DDL] 표현식에서는 pg\_clickhouse를 사용합니다.

<div id="create-extension">
  ### CREATE EXTENSION
</div>

데이터베이스에 pg\_clickhouse 확장 기능을 추가하려면 [CREATE EXTENSION]을 사용합니다:

```sql theme={null}
CREATE EXTENSION pg_clickhouse;
```

특정 스키마에 설치하려면 `WITH SCHEMA`를 사용하세요(권장됨):

```sql theme={null}
CREATE SCHEMA ch;
CREATE EXTENSION pg_clickhouse WITH SCHEMA ch;
```

<div id="alter-extension">
  ### ALTER EXTENSION
</div>

pg\_clickhouse를 변경하려면 [ALTER EXTENSION]을 사용합니다. 예시:

* 새 릴리스의 pg\_clickhouse를 설치한 후에는 `UPDATE` 절을 사용합니다:

  ```sql theme={null}
  ALTER EXTENSION pg_clickhouse UPDATE;
  ```

* 확장 기능을 새 스키마로 이동하려면 `SET SCHEMA`를 사용합니다:

  ```sql theme={null}
  CREATE SCHEMA ch;
  ALTER EXTENSION pg_clickhouse SET SCHEMA ch;
  ```

<div id="drop-extension">
  ### DROP EXTENSION
</div>

데이터베이스에서 pg\_clickhouse 확장 기능을 제거하려면 [DROP EXTENSION]을 사용합니다:

```sql theme={null}
DROP EXTENSION pg_clickhouse;
```

pg\_clickhouse에 의존하는 객체가 있으면 이 명령은 실패합니다. 해당 객체도 함께 삭제하려면
`CASCADE` 절을 사용하십시오:

```sql theme={null}
DROP EXTENSION pg_clickhouse CASCADE;
```

<div id="create-server">
  ### CREATE SERVER
</div>

ClickHouse 서버에 연결할 외부 서버를 생성하려면 [CREATE SERVER]를 사용합니다.
예시:

```sql theme={null}
CREATE SERVER taxi_srv FOREIGN DATA WRAPPER clickhouse_fdw
       OPTIONS(driver 'binary', host 'localhost', dbname 'taxi');
```

지원되는 옵션은 다음과 같습니다:

* `driver`: 사용할 ClickHouse 연결 드라이버입니다. `binary` 또는
  `http`여야 합니다. **필수입니다.**
* `dbname`: 연결 시 사용할 ClickHouse 데이터베이스입니다. 기본값은
  `default`입니다.
* `fetch_size`: HTTP streaming에 사용할 대략적인 바이트 단위 배치 크기입니다. 배치는
  행 경계에서 분할됩니다. 기본값은 `50000000`(50 MB)입니다. `0`으로 설정하면
  streaming이 비활성화되고 전체 응답이 버퍼에 저장됩니다. 외부 테이블은 이
  값을 재정의할 수 있습니다.
* `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

<div id="alter-server">
  ### ALTER SERVER
</div>

[ALTER SERVER]를 사용해 외부 서버를 수정합니다. 예시:

```sql theme={null}
ALTER SERVER taxi_srv OPTIONS (SET driver 'http');
```

[CREATE SERVER](#create-server)의 옵션과 동일합니다.

<div id="drop-server">
  ### DROP SERVER
</div>

외부 서버를 삭제하려면 [DROP SERVER]를 사용합니다:

```sql theme={null}
DROP SERVER taxi_srv;
```

다른 객체가 서버에 종속되어 있으면 이 명령은 실패합니다. 해당 종속 항목도
함께 삭제하려면 `CASCADE`를 사용하십시오:

```sql theme={null}
DROP SERVER taxi_srv CASCADE;
```

<div id="create-user-mapping">
  ### CREATE USER MAPPING
</div>

[CREATE USER MAPPING]을 사용하여 PostgreSQL 사용자를 ClickHouse 사용자에 매핑할 수 있습니다. 예를
들어, `taxi_srv` 외부 서버를 통해 연결할 때 현재 PostgreSQL 사용자를 원격 ClickHouse 사용자에 매핑하려면
다음과 같이 하십시오:

```sql theme={null}
CREATE USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (user 'demo');
```

지원되는 옵션은 다음과 같습니다.

* `user`: ClickHouse 사용자의 이름입니다. 기본값은 "default"입니다.
* `password`: ClickHouse 사용자의 비밀번호입니다.

<div id="alter-user-mapping">
  ### ALTER USER MAPPING
</div>

사용자 매핑 정의를 변경하려면 [ALTER USER MAPPING]을 사용하십시오:

```sql theme={null}
ALTER USER MAPPING FOR CURRENT_USER SERVER taxi_srv
       OPTIONS (SET user 'default');
```

옵션은 [CREATE USER MAPPING](#create-user-mapping)과 동일합니다.

<div id="drop-user-mapping">
  ### DROP USER MAPPING
</div>

사용자 매핑을 삭제하려면 [DROP USER MAPPING]을 사용합니다:

```sql theme={null}
DROP USER MAPPING FOR CURRENT_USER SERVER taxi_srv;
```

<div id="import-foreign-schema">
  ### IMPORT FOREIGN SCHEMA
</div>

[IMPORT FOREIGN SCHEMA]를 사용하면 ClickHouse 데이터베이스에 정의된 모든 테이블을 PostgreSQL 스키마로 외부 테이블로 가져올 수 있습니다:

```sql theme={null}
CREATE SCHEMA taxi;
IMPORT FOREIGN SCHEMA demo FROM SERVER taxi_srv INTO taxi;
```

가져오기를 특정 테이블로 제한하려면 `LIMIT TO`를 사용하십시오:

```sql theme={null}
IMPORT FOREIGN SCHEMA demo LIMIT TO (trips) FROM SERVER taxi_srv INTO taxi;
```

테이블을 제외하려면 `EXCEPT`를 사용합니다:

```sql theme={null}
IMPORT FOREIGN SCHEMA demo EXCEPT (users) FROM SERVER taxi_srv INTO taxi;
```

pg\_clickhouse는 지정된 ClickHouse 데이터베이스(위 예시의 "demo")에 있는 모든 테이블 목록을 가져오고, 각 테이블의 컬럼 정의를 가져온 뒤 [CREATE FOREIGN TABLE](#create-foreign-table) 명령을 실행해 외부 테이블을 생성합니다. 컬럼은 [지원되는 데이터 타입](#data-types)과, 감지 가능한 경우 [CREATE
FOREIGN TABLE](#create-foreign-table)에서 지원하는 옵션을 사용해 정의됩니다.

<Tip>
  **가져온 식별자의 대소문자 보존**

  `IMPORT FOREIGN SCHEMA`는 가져오는 테이블 및 컬럼 이름에 `quote_identifier()`를 적용하며, 이때 대문자나 공백이 포함된 식별자는 큰따옴표로 묶습니다. 따라서 이러한 테이블 및 컬럼 이름은 PostgreSQL 쿼리에서 반드시 큰따옴표로 묶어야 합니다. 모두 소문자이고 공백 문자가 없는 이름은 따옴표로 묶지 않아도 됩니다.

  예를 들어, 다음과 같은 ClickHouse 테이블이 있다고 가정해 보겠습니다:

  ```sql theme={null}
  CREATE OR REPLACE TABLE test
  (
      id UInt64,
      Name TEXT,
      updatedAt DateTime DEFAULT now()
  )
  ENGINE = MergeTree
  ORDER BY id;
  ```

  `IMPORT FOREIGN SCHEMA`는 다음과 같은 외부 테이블을 생성합니다:

  ```sql theme={null}
  CREATE TABLE test
  (
      id          BIGINT      NOT NULL,
      "Name"      TEXT        NOT NULL,
      "updatedAt" TIMESTAMPTZ NOT NULL
  );
  ```

  따라서 쿼리에서도 적절히 따옴표를 사용해야 합니다. 예를 들면 다음과 같습니다.

  ```sql theme={null}
  SELECT id, "Name", "updatedAt" FROM test;
  ```

  다른 이름이나 모두 소문자인 이름(즉, 대소문자를 구분하지 않는 이름)으로 객체를 생성하려면 [CREATE FOREIGN TABLE](#create-foreign-table)을 사용하십시오.
</Tip>

<div id="create-foreign-table">
  ### CREATE FOREIGN TABLE
</div>

ClickHouse 데이터베이스의 데이터를 쿼리할 수 있는 외부 테이블을 생성하려면 [CREATE FOREIGN TABLE]을 사용합니다:

```sql theme={null}
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 데이터 타입에 맞는 [데이터 타입](#data-types)을
사용하십시오. 지원되는 컬럼 옵션은 다음과 같습니다.

* `column_name`: ClickHouse 쪽 컬럼의 이름입니다. 쿼리와 삽입을 deparse할 때
  PostgreSQL 속성 이름보다 우선적으로 사용됩니다. 따옴표 없는 소문자 PostgreSQL 컬럼 이름을
  대소문자를 구분하는 ClickHouse 컬럼에 매핑할 때 유용합니다. 예를 들면 다음과 같습니다.

  ```sql theme={null}
  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`를 자동으로 추가합니다.

  ```sql theme={null}
  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 타입에 매핑하고,
  적절한 컬럼 옵션으로 집계 함수 이름을 지정하십시오.

<div id="alter-foreign-table">
  ### ALTER FOREIGN TABLE
</div>

외부 테이블의 정의를 변경할 때는 [ALTER FOREIGN TABLE]을 사용합니다:

```sql theme={null}
ALTER TABLE table ALTER COLUMN b OPTIONS (SET AggregateFunction 'count');
```

지원되는 테이블 및 컬럼 옵션은 [CREATE FOREIGN
TABLE]과 동일합니다.

<div id="drop-foreign-table">
  ### DROP FOREIGN TABLE
</div>

외부 테이블을 삭제할 때는 [DROP FOREIGN TABLE]을 사용합니다:

```sql theme={null}
DROP FOREIGN TABLE acts;
```

외부 테이블에 종속된 객체가 하나라도 있으면 이 명령은 실패합니다.
해당 객체도 함께 삭제하려면 `CASCADE` 절을 사용하십시오:

```sql theme={null}
DROP FOREIGN TABLE acts CASCADE;
```

<div id="dml-sql-reference">
  ## DML SQL 참고
</div>

아래의 SQL [DML] 표현식에서는 pg\_clickhouse를 사용할 수 있습니다. 예시는
다음 ClickHouse 테이블(table)을 기준으로 합니다:

```sql theme={null}
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;
```

<div id="explain">
  ### EXPLAIN
</div>

[EXPLAIN] 명령은 예상대로 작동하지만, `VERBOSE` 옵션을 사용하면
ClickHouse "Remote SQL" 쿼리가 출력됩니다:

```pgsql theme={null}
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로 푸시다운합니다.

<div id="select">
  ### SELECT
</div>

다른 테이블과 마찬가지로 pg\_clickhouse 테이블에서도 [SELECT] 문을 사용해 쿼리를 실행할 수 있습니다:

```pgsql theme={null}
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](#explain)을 사용하십시오.
예를 들어 위 쿼리의 경우 모든 실행이 ClickHouse로 푸시다운됩니다

```pgsql theme={null}
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는 동일한 원격 서버에 있는 테이블 간 조인도 푸시다운합니다:

```pgsql theme={null}
try=# EXPLAIN (ANALYZE, VERBOSE)
       SELECT name, count(*), round(avg(duration))
         FROM logs
         LEFT JOIN nodes on logs.node_id = nodes.node_id
        GROUP BY name;
                                                                                  QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Foreign Scan  (cost=1.00..5.10 rows=1000 width=72) (actual time=3.201..3.221 rows=8.00 loops=1)
   Output: nodes.name, (count(*)), (round(avg(logs.duration), 0))
   Relations: Aggregate on ((logs) LEFT JOIN (nodes))
   Remote SQL: SELECT r2.name, count(*), round(avg(r1.duration), 0) FROM  "default".logs r1 ALL LEFT JOIN "default".nodes r2 ON (((r1.node_id = r2.node_id))) GROUP BY r2.name
   FDW Time: 0.086 ms
 Planning Time: 0.335 ms
 Execution Time: 3.261 ms
(7 rows)
```

로컬 테이블과 조인하면
세심하게 튜닝하지 않는 한 비효율적인 쿼리가 생성될 수 있습니다. 이 예시에서는 원격 테이블 대신
`nodes` 테이블의 로컬 복사본을 만들고 이를 조인 대상으로 사용합니다:

```pgsql theme={null}
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에서 처리하도록 할 수 있으며, 이후 lookup 테이블과 조인할 수 있습니다:

```sql theme={null}
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개로 줄입니다.

<div id="prepare-execute-deallocate">
  ### PREPARE, EXECUTE, DEALLOCATE
</div>

v0.1.2부터 pg\_clickhouse는 주로 [PREPARE] 명령으로 생성한
매개변수화된 쿼리를 지원합니다:

```pgsql theme={null}
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]를 사용해 prepared statement를 실행하십시오:

```pgsql theme={null}
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)
```

<Warning>
  매개변수화된 실행을 사용하면 25.8 이전 버전의 ClickHouse에서는 [http driver](#create-server)가
  DateTime 시간대를 올바르게 변환하지 못합니다. 이는 \[해당 버그]가 \[수정]되기 전까지의
  문제입니다. PostgreSQL은 `PREPARE`를 사용하지 않더라도 경우에 따라 매개변수화된 쿼리 계획을
  사용할 수 있다는 점에 유의하십시오. 정확한 시간대 변환이 필요한 쿼리에서 25.8 이상으로
  업그레이드할 수 없다면, 대신 [binary driver](#create-server)를 사용하십시오.
</Warning>

pg\_clickhouse는 평소와 같이 집계를 푸시다운하며, 이는
[EXPLAIN](#explain) 상세 출력에서 확인할 수 있습니다:

```pgsql theme={null}
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}` 형식 \[쿼리 매개변수]를 전송합니다:
매개변수:

```pgsql theme={null}
                                                                                                         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]를 사용합니다:

```pgsql theme={null}
try=# DEALLOCATE avg_durations_between_dates;
DEALLOCATE
```

<div id="insert">
  ### INSERT
</div>

원격 ClickHouse 테이블에 값을 삽입하려면 [INSERT] 명령을 사용하십시오:

```pgsql theme={null}
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
```

<div id="copy">
  ### COPY
</div>

원격 ClickHouse 테이블(table)에 여러 행(row)을 한 번에 삽입하려면 [COPY] 명령을 사용합니다:

```pgsql theme={null}
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 배치
> 삽입 API를 지원하지 않습니다. 따라서 현재 [COPY]는 레코드를
> 삽입할 때 [INSERT](#insert) SQL 문을 사용합니다. 이 부분은 향후 릴리스에서 개선될 예정입니다.

<div id="load">
  ### LOAD
</div>

[LOAD]를 사용해 pg\_clickhouse 공유 라이브러리를 로드합니다:

```pgsql theme={null}
try=# LOAD 'pg_clickhouse';
LOAD
```

일반적으로 [LOAD]를 사용할 필요는 없습니다. Postgres는 해당 기능(함수, foreign
table 등) 중 하나를 처음 사용할 때 pg\_clickhouse를 자동으로 로드하기 때문입니다.

[LOAD] pg\_clickhouse가 유용할 수 있는 경우는, 이에 의존하는 쿼리를 실행하기 전에 [SET](#set)으로
pg\_clickhouse 매개변수를 설정해야 할 때입니다.

<div id="set">
  ### SET
</div>

[SET]를 사용해 pg\_clickhouse의 사용자 지정 구성 매개변수를 설정합니다.

<div id="pg_clickhousesession_settings">
  #### `pg_clickhouse.session_settings`
</div>

`pg_clickhouse.session_settings` 매개변수는 이후 쿼리에 설정할 \[ClickHouse
설정]을 지정합니다. 예시:

```sql theme={null}
SET pg_clickhouse.session_settings = 'join_use_nulls 1, final 1';
```

기본값은 `join_use_nulls 1, group_by_use_nulls 1, final 1`입니다. ClickHouse 서버의 설정을
사용하려면 빈 문자열로 설정하십시오.

```sql theme={null}
SET pg_clickhouse.session_settings = '';
```

구문은 하나 이상의 공백으로 구분된 쉼표 구분 키/값 쌍 목록입니다. 키는 \[ClickHouse 설정]에 해당해야 합니다. 값에 포함된 공백,
쉼표, 백슬래시는 백슬래시로 이스케이프하세요:

```sql theme={null}
SET pg_clickhouse.session_settings = 'join_algorithm grace_hash\,hash';
```

또는 공백과 쉼표를 이스케이프하지 않으려면 작은따옴표로 묶인 값을 사용할 수 있습니다. 이중따옴표를 두 번 사용할 필요를 피하려면 \[달러 인용] 사용을 고려하십시오:

```sql theme={null}
SET pg_clickhouse.session_settings = $$join_algorithm 'grace_hash,hash'$$;
```

가독성이 중요하고 많은 설정을 지정해야 한다면 여러
줄로 작성하십시오. 예시는 다음과 같습니다:

```sql theme={null}
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 driver는 이 값을 "iso"로 요구합니다
* `format_tsv_null_representation`: http driver는 기본값을 요구합니다
* `output_format_tsv_crlf_end_of_line`: http driver는 기본값을 요구합니다

그 외에는 pg\_clickhouse가 설정을 검증하지 않고, 모든 쿼리마다 이를 ClickHouse에 전달합니다. 따라서 각 ClickHouse 버전에서 제공하는 모든 설정을 지원합니다.

`pg_clickhouse.session_settings`를 설정하기 전에 pg\_clickhouse가 먼저 로드되어야 합니다. 이를 위해 \[공유 라이브러리 사전 로드]를 사용하거나, 확장 기능에 포함된 객체 중 하나를 사용해 로드되도록 하면 됩니다.

<div id="pg_clickhousepushdown_regex">
  #### `pg_clickhouse.pushdown_regex`
</div>

`pg_clickhouse.pushdown_regex` 매개변수는 pg\_clickhouse가
정규식 함수와 연산자를 푸시다운할지 여부를 제어합니다. 기본값은 푸시다운을 수행하는 것이며,
푸시다운하지 않도록 하려면 이 매개변수를 false로 설정하십시오:

```sql theme={null}
SET pg_clickhouse.pushdown_regex = 'false';
```

자세한 내용은 [정규식](#regular-expressions)을 확인하십시오.

<div id="alter-role">
  ### ALTER ROLE
</div>

[ALTER ROLE]의 `SET` 명령을 사용하면 pg\_clickhouse를 [사전 로드](#preloading)하거나
특정 역할별로 매개변수를 [SET](#set)할 수 있습니다:

```pgsql theme={null}
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
```

pg\_clickhouse의 사전 로드
및/또는 매개변수를 재설정하려면 [ALTER ROLE]의 `RESET` 명령을 사용하십시오:

```pgsql theme={null}
try=# ALTER ROLE CURRENT_USER RESET session_preload_libraries;
ALTER ROLE

try=# ALTER ROLE CURRENT_USER RESET pg_clickhouse.session_settings;
ALTER ROLE
```

<div id="preloading">
  ## 사전 로드
</div>

모든 또는 거의 모든 Postgres 연결에서 pg\_clickhouse를 사용해야 한다면,
이를 자동으로 로드하도록 \[공유 라이브러리 사전 로드] 사용을 고려하십시오:

<div id="session_preload_libraries">
  ### `session_preload_libraries`
</div>

PostgreSQL에 새 연결이 생성될 때마다 공유 라이브러리를 로드합니다:

```ini theme={null}
session_preload_libraries = pg_clickhouse
```

서버를 다시 시작하지 않고도 업데이트 내용을 적용하는 데 유용합니다. 다시
연결만 하면 됩니다. [ALTER ROLE](#alter-role)을 통해 특정 사용자나 역할에 대해서도 설정할 수 있습니다.

<div id="shared_preload_libraries">
  ### `shared_preload_libraries`
</div>

시작 시 PostgreSQL 부모 프로세스에 공유 라이브러리를 로드합니다:

```ini theme={null}
shared_preload_libraries = pg_clickhouse
```

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

<div id="data-types">
  ## 데이터 타입
</div>

pg\_clickhouse는 다음 ClickHouse 데이터 타입을 PostgreSQL 데이터
타입에 매핑합니다. [IMPORT FOREIGN SCHEMA](#import-foreign-schema)는 컬럼을 가져올 때
PostgreSQL 컬럼 타입으로 첫 번째 타입을 사용하고, 추가 타입은
[CREATE FOREIGN TABLE](#create-foreign-table) SQL 문에서 사용할 수 있습니다:

| ClickHouse | PostgreSQL       | 비고                        |
| ---------- | ---------------- | ------------------------- |
| Bool       | boolean          |                           |
| Date       | date             |                           |
| Date32     | date             |                           |
| DateTime   | timestamptz      |                           |
| Decimal    | numeric          |                           |
| Float32    | real             |                           |
| Float64    | double precision |                           |
| IPv4       | inet             |                           |
| IPv6       | inet             |                           |
| Int16      | smallint         |                           |
| Int32      | integer          |                           |
| Int64      | bigint           |                           |
| Int8       | smallint         |                           |
| JSON       | jsonb, json      |                           |
| String     | text, bytea      |                           |
| UInt16     | integer          |                           |
| UInt32     | bigint           |                           |
| UInt64     | bigint           | 값이 BIGINT 최댓값을 초과하면 오류 발생 |
| UInt8      | smallint         |                           |
| UUID       | uuid             |                           |

추가 참고 사항과 세부 내용은 아래에서 설명합니다.

<div id="bytea">
  ### BYTEA
</div>

ClickHouse는 PostgreSQL의 [BYTEA] 유형에 해당하는 타입을 제공하지 않지만,
[String] 유형에 임의의 바이트를 저장할 수 있습니다. 일반적으로 ClickHouse 문자열은
PostgreSQL의 [TEXT]에 매핑하고, 바이너리 데이터를 사용하는 경우에는
[BYTEA]에 매핑하십시오. 예시:

```sql theme={null}
-- 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` 쿼리의 출력 결과는 다음과 같습니다:

```pgsql theme={null}
c1 |                             c2                             |                 c3
----+------------------------------------------------------------+------------------------------------
  1 | \x1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | \xae3b28cde02542f81acce8783245430d
  2 | \x5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | \x23e7c6cacb8383f878ad093b0027d72b
  3 | \x53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | \x7e969132fc656148b97b6a2ee8bc83c1
  4 | \x4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | \x8ef30f44c65480d12b650ab6b2b04245
(4 행)
```

ClickHouse 컬럼에 nul 바이트가 포함된 경우, [TEXT] 컬럼을 사용하는 외부 테이블은 올바른 값을 출력하지 않습니다:

```sql theme={null}
-- 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;
```

출력 결과:

```pgsql theme={null}
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] 컬럼에 이진 값을 삽입하면 예상대로 성공적으로 작동합니다:

```sql theme={null}
-- 텍스트 컬럼을 통해 삽입:
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;
```

텍스트 컬럼은 올바르게 표시됩니다:

```pgsql theme={null}

 c1 |                          encode                          |              encode
----+----------------------------------------------------------+----------------------------------
  1 | 1bf7f0cc821d31178616a55a8e0c52677735397cdde6f4153a9fd3d7 | ae3b28cde02542f81acce8783245430d
  2 | 5f6e9e12cd8592712e638016f4b1a2e73230ee40db498c0f0b1dc841 | 23e7c6cacb8383f878ad093b0027d72b
  3 | 53ac2c1fa83c8f64603fe9568d883331007d6281de330a4b5e728f9e | 7e969132fc656148b97b6a2ee8bc83c1
  4 | 4e3c2e4cb7542a45173a8dac939ddc4bc75202e342ebc769b0f5da2f | 8ef30f44c65480d12b650ab6b2b04245
(4 rows)
```

하지만 [BYTEA]로 읽으면 그렇지 않습니다:

```pgsql theme={null}
# SELECT * FROM bytes;
 c1 |                                                           c2                                                           |                                   c3
----+------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------
  1 | \x5c783162663766306363383231643331313738363136613535613865306335323637373733353339376364646536663431353361396664336437 | \x5c786165336232386364653032353432663831616363653837383332343534333064
  2 | \x5c783566366539653132636438353932373132653633383031366634623161326537333233306565343064623439386330663062316463383431 | \x5c783233653763366361636238333833663837386164303933623030323764373262
  3 | \x5c783533616332633166613833633866363436303366653935363864383833333331303037643632383164653333306134623565373238663965 | \x5c783765393639313332666336353631343862393762366132656538626338336331
  4 | \x5c783465336332653463623735343261343531373361386461633933396464633462633735323032653334326562633736396230663564613266 | \x5c783865663330663434633635343830643132623635306162366232623034323435
(4 행)
```

<Tip>
  일반적으로 인코딩된 문자열에는 [TEXT] 컬럼만 사용하고, [BYTEA] 컬럼은
  바이너리 데이터에만 사용하며, 두 타입을 서로 바꿔 사용해서는 안 됩니다.
</Tip>

<div id="function-and-operator-reference">
  ## Function 및 연산자 참고
</div>

<div id="functions">
  ### 함수
</div>

이 함수들은 ClickHouse 데이터베이스를 쿼리하기 위한 인터페이스를 제공합니다.

<div id="clickhouse_raw_query">
  #### `clickhouse_raw_query`
</div>

```sql theme={null}
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` 권한을 가진 역할은 없습니다. 따라서 정당한 이유로 ad-hoc ClickHouse
쿼리를 실행해야 하는 역할(예: 전용 ClickHouse 관리자 역할)에만 [GRANT]로
권한을 부여하는 것이 좋습니다.

레코드를 반환하지 않는 쿼리에 유용하지만, 값을 반환하는 쿼리의 결과는
단일 텍스트 값으로 반환됩니다.

```sql theme={null}
SELECT clickhouse_raw_query(
    'SELECT schema_name, schema_owner from information_schema.schemata',
    'host=localhost port=8123'
);
```

```sql theme={null}
      clickhouse_raw_query
---------------------------------
 INFORMATION_SCHEMA      default+
 default default                +
 git     default                +
 information_schema      default+
 system  default                +

(1 row)
```

<div id="pushdown-functions">
  ### 푸시다운 함수
</div>

`pg_clickhouse`는 조건식(`HAVING` 및 `WHERE` 절)에 사용되는 PostgreSQL 내장 함수 일부를 ClickHouse로 푸시다운합니다. 이 함수 집합은 다음과 같이 ClickHouse의 대응 함수에 매핑됩니다:

* `abs`: [abs](/ko/reference/functions/regular-functions/arithmetic-functions#abs)
* `factorial`: [factorial](/ko/reference/functions/regular-functions/math-functions#factorial)
* `mod` (int2/int4/int8/numeric): [나머지](/ko/reference/functions/regular-functions/arithmetic-functions#modulo)
* `pow` & `power` (float8/numeric): [pow](/ko/reference/functions/regular-functions/math-functions#pow)
* `round`: [round](/ko/reference/functions/regular-functions/rounding-functions#round)
* `sin`, `cos`, `tan`, `atan`, `atan2`, `sinh`, `cosh`, `tanh`, `asinh`, `degrees`, `radians`, `pi`는 이름이 같은
  [ClickHouse 수학 함수](/ko/reference/functions/regular-functions/math-functions)입니다. `asin`, `acos`, `atanh`, `acosh`는 푸시다운되지 않습니다: PG는
  범위를 벗어난 입력에서 오류를 발생시키지만 CH는 `NaN`을 반환합니다.
* `date_part`:
  * `date_part('day')`: [toDayOfMonth](/ko/reference/functions/regular-functions/date-time-functions#toDayOfMonth)
  * `date_part('doy')`: [toDayOfYear](/ko/reference/functions/regular-functions/date-time-functions#toDayOfYear)
  * `date_part('dow')`: [toDayOfWeek](/ko/reference/functions/regular-functions/date-time-functions#toDayOfWeek)
  * `date_part('year')`: [toYear](/ko/reference/functions/regular-functions/date-time-functions#toYear)
  * `date_part('month')`: [toMonth](/ko/reference/functions/regular-functions/date-time-functions#toMonth)
  * `date_part('hour')`: [toHour](/ko/reference/functions/regular-functions/date-time-functions#toHour)
  * `date_part('minute')`: [toMinute](/ko/reference/functions/regular-functions/date-time-functions#toMinute)
  * `date_part('second')`: [toSecond](/ko/reference/functions/regular-functions/date-time-functions#toSecond)
  * `date_part('quarter')`: [toQuarter](/ko/reference/functions/regular-functions/date-time-functions#toQuarter)
  * `date_part('isoyear')`: [toISOYear](/ko/reference/functions/regular-functions/date-time-functions#toISOYear)
  * `date_part('week')`: [toISOYear](/ko/reference/functions/regular-functions/date-time-functions#toISOWeek)
  * `date_part('epoch')`: [toISOYear](/ko/reference/functions/regular-functions/date-time-functions#toUnixTimestamp)
* `date_trunc`:
  * `date_trunc('week')`: [toMonday](/ko/reference/functions/regular-functions/date-time-functions#toMonday)
  * `date_trunc('second')`: [toStartOfSecond](/ko/reference/functions/regular-functions/date-time-functions#toStartOfSecond)
  * `date_trunc('minute')`: [toStartOfMinute](/ko/reference/functions/regular-functions/date-time-functions#toStartOfMinute)
  * `date_trunc('hour')`: [toStartOfHour](/ko/reference/functions/regular-functions/date-time-functions#toStartOfHour)
  * `date_trunc('day')`: [toStartOfDay](/ko/reference/functions/regular-functions/date-time-functions#toStartOfDay)
  * `date_trunc('month')`: [toStartOfMonth](/ko/reference/functions/regular-functions/date-time-functions#toStartOfMonth)
  * `date_trunc('quarter')`: [toStartOfQuarter](/ko/reference/functions/regular-functions/date-time-functions#toStartOfQuarter)
  * `date_trunc('year')`: [toStartOfYear](/ko/reference/functions/regular-functions/date-time-functions#toStartOfYear)
* `extract(field FROM source)`: 매핑은 `date_part`와 동일합니다
* `date(timestamp)` & `date(timestamptz)`: [toDate](/ko/reference/functions/regular-functions/type-conversion-functions#toDate)
  (CH 별칭 `date`로 다시 표현됨)
* `array_position`: [indexOf](/ko/reference/functions/regular-functions/array-functions#indexOf)
* `array_cat`: [arrayConcat](/ko/reference/functions/regular-functions/array-functions#arrayConcat)
* `array_append`: [arrayPushBack](/ko/reference/functions/regular-functions/array-functions#arrayPushBack)
* `array_prepend`: [arrayPushFront](/ko/reference/functions/regular-functions/array-functions#arrayPushFront)
* `array_remove`: [arrayRemove](/ko/reference/functions/regular-functions/array-functions#arrayRemove)
* `array_length` & `cardinality`: [length](/ko/reference/functions/regular-functions/array-functions#length)
* `array_to_string`: [arrayStringConcat](/ko/reference/functions/regular-functions/array-functions#arrayStringConcat)
* `string_to_array`: [splitByString](/ko/reference/functions/regular-functions/splitting-merging-functions#splitByString)
* `split_part`: [splitByString](/ko/reference/functions/regular-functions/splitting-merging-functions#splitByString) + 배열 인덱스
* `trim_array`: [arrayResize](/ko/reference/functions/regular-functions/array-functions#arrayResize)
* `array_fill`: [arrayWithConstant](/ko/reference/functions/regular-functions/array-functions#arrayWithConstant)
* `array_reverse`: [arrayReverse](/ko/reference/functions/regular-functions/array-functions#arrayReverse)
* `array_shuffle`: [arrayShuffle](/ko/reference/functions/regular-functions/array-functions#arrayShuffle)
* `array_sample`: [arrayRandomSample](/ko/reference/functions/regular-functions/array-functions#arrayRandomSample)
* `array_sort`: [arraySort](/ko/reference/functions/regular-functions/array-functions#arraySort) / [arrayReverseSort](/ko/reference/functions/regular-functions/array-functions#arrayReverseSort)
* `btrim`: [trimBoth](/ko/reference/functions/regular-functions/string-functions#trimboth)
* `ltrim`: [ltrim](/ko/reference/functions/regular-functions/string-functions#ltrim)
* `rtrim`: [rtrim](/ko/reference/functions/regular-functions/string-functions#rtrim)
* `concat_ws`: [concatWithSeparator](/ko/reference/functions/regular-functions/string-functions#concatwithseparator)
* `lower(text)`: [lowerUTF8](/ko/reference/functions/regular-functions/string-functions#lowerutf8)
* `upper(text)`: [upperUTF8](/ko/reference/functions/regular-functions/string-functions#upperutf8)
* `substring(text, ...)` & `substr(text, ...)`: [substringUTF8](/ko/reference/functions/regular-functions/string-functions#substringutf8)
* `substring(bytea, ...)` & `substr(bytea, ...)`: [substring](/ko/reference/functions/regular-functions/string-functions#substring)
* `length(text)`: [lengthUTF8](/ko/reference/functions/regular-functions/string-functions#lengthutf8)
* `length(bytea)` & `octet_length`: [length](/ko/reference/functions/regular-functions/array-functions#length)
* `reverse(text)`: [reverseUTF8](/ko/reference/functions/regular-functions/string-functions#reverseutf8)
* `reverse(bytea)`: [reverse](/ko/reference/functions/regular-functions/string-functions#reverse)
* `strpos`: [positionUTF8](/ko/reference/functions/regular-functions/string-search-functions#positionutf8)
* `regexp_like`: [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `regexp_replace`: [replaceRegexpOne](/ko/reference/functions/regular-functions/string-replace-functions#replaceRegexpOne) 또는 `g` 플래그가 있으면 [replaceRegexpOne](/ko/reference/functions/regular-functions/string-replace-functions#replaceRegexpAll)
* `regexp_split_to_array`: [splitByRegexp](/ko/reference/functions/regular-functions/splitting-merging-functions#splitByRegexp)
* `md5`: [MD5](/ko/reference/functions/regular-functions/hash-functions#MD5)
* `json_extract_path_text`: [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)
* `json_extract_path`: [toJSONString](/ko/reference/functions/regular-functions/json-functions#toJSONString) + [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)
* `jsonb_extract_path_text`: [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)
* `jsonb_extract_path`: [toJSONString](/ko/reference/functions/regular-functions/json-functions#toJSONString) + [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)
* `bit_count(bytea)`: [bitCount](/ko/reference/functions/regular-functions/bit-functions#bitcount)
* `to_timestamp(float8)`: [fromUnixTimestamp](/ko/reference/functions/regular-functions/date-time-functions#fromUnixTimestamp)
* `to_char(timestamp[tz], fmt)`: [formatDateTime](/ko/reference/functions/regular-functions/date-time-functions#formatDateTime)
  `fmt`가 모든 키워드에 대해
  ClickHouse에 정확히 대응하는 값이 있는 string constant인 경우입니다. 지원되는 키워드는 호환성
  참고 사항의 [to\_char()](#to_char)를 참조하십시오. 그렇지 않으면 이 함수는
  PostgreSQL에서 로컬로 실행됩니다.
* `statement_timestamp`, `transaction_timestamp`, & `clock_timestamp`:
  [nowInBlock64](/ko/reference/functions/regular-functions/date-time-functions#nowInBlock64)
  (`nowInBlock64(9, $session_timezone)`)
* `CURRENT_DATE`:
  [now](/ko/reference/functions/regular-functions/date-time-functions#now)와
  [toDate](/ko/reference/functions/regular-functions/type-conversion-functions#toDate)
  (`toDate(now($session_timezone))`)
* `now`, `CURRENT_TIMESTAMP`, & `LOCALTIMESTAMP`:
  [now64](/ko/reference/functions/regular-functions/date-time-functions#now64)
  (`now64(9, $session_timezone)`)
* `CURRENT_TIMESTAMP(n)` & `LOCALTIMESTAMP(n)`:
  [now64](/ko/reference/functions/regular-functions/date-time-functions#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 함수의 값으로 전달됩니다.

<div id="pushdown-operators">
  ### 푸시다운 연산자
</div>

* 배열 슬라이스 (`arr[L:U]`): [arraySlice](/ko/reference/functions/regular-functions/array-functions#arraySlice)
* `@>` (배열 포함): [hasAll](/ko/reference/functions/regular-functions/array-functions#hasAll)
* `<@` (배열에 포함됨): [hasAll](/ko/reference/functions/regular-functions/array-functions#hasAll)
* `&&` (배열 겹침): [hasAny](/ko/reference/functions/regular-functions/array-functions#hasAny)
* `~` (정규식 일치): [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `!~` (정규식 불일치): [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `~*` (대소문자를 구분하지 않는 정규식 불일치): [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `!~*` (대소문자를 구분하지 않는 정규식 불일치): [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `->>` (JSON/JSONB 요소를 텍스트로 추출): [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)
* `->` (JSON/JSONB 요소 추출): [toJSONString](/ko/reference/functions/regular-functions/json-functions#toJSONString) + [서브컬럼 구문](/ko/reference/data-types/newjson#reading-json-paths-as-sub-columns)

<div id="custom-functions">
  ### 사용자 지정 함수
</div>

`pg_clickhouse`로 생성된 이러한 사용자 지정 함수는 PostgreSQL에 대응하는 기능이 없는 일부 ClickHouse 함수에 대해 원격 쿼리 푸시다운을 제공합니다. 이 함수들 중 하나라도 pushdown되지 않으면 예외를 발생시킵니다.

* [dictGet](/ko/reference/functions/regular-functions/ext-dict-functions#dictget-dictgetordefault-dictgetornull)

<div id="extension-pushdown">
  ### 확장 기능 푸시다운
</div>

pg\_clickhouse는 일부 핵심 확장 기능과 서드파티 확장 기능의 함수를 인식하여,
해당 ClickHouse 함수로 푸시다운합니다.

<div id="re2">
  #### re2
</div>

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

* `re2match` → [match](/ko/reference/functions/regular-functions/string-search-functions#match)
* `re2extract` → [extract](/ko/reference/functions/regular-functions/string-search-functions#extract)
* `re2extractall` → [extractAll](/ko/reference/functions/regular-functions/string-search-functions#extractAll)
* `re2regexpextract` → [regexpExtract](/ko/reference/functions/regular-functions/string-search-functions#regexpExtract)
* `re2extractgroups` → [extractGroups](/ko/reference/functions/regular-functions/string-search-functions#extractGroups)
* `re2replaceregexpone` → [replaceRegexpOne](/ko/reference/functions/regular-functions/string-replace-functions#replaceRegexpOne)
* `re2replaceregexpall` → [replaceRegexpAll](/ko/reference/functions/regular-functions/string-replace-functions#replaceRegexpAll)
* `re2countmatches` → [countMatches](/ko/reference/functions/regular-functions/string-search-functions#countMatches)
* `re2countmatchescaseinsensitive` → [countMatchesCaseInsensitive](/ko/reference/functions/regular-functions/string-search-functions#countMatchesCaseInsensitive)
* `re2multimatchany` → [multiMatchAny](/ko/reference/functions/regular-functions/string-search-functions#multiMatchAny)
* `re2multimatchanyindex` → [multiMatchAnyIndex](/ko/reference/functions/regular-functions/string-search-functions#multiMatchAnyIndex)
* `re2multimatchallindices` → [multiMatchAllIndices](/ko/reference/functions/regular-functions/string-search-functions#multiMatchAllIndices)

<div id="intarray">
  #### intarray
</div>

다음 [intarray] 함수 1개가 ClickHouse로 푸시다운됩니다:

* `idx` → [indexOf](/ko/reference/functions/regular-functions/array-functions#indexOf)

<div id="fuzzystrmatch">
  #### fuzzystrmatch
</div>

다음 2개의 [fuzzystrmatch] 함수는 ClickHouse로 푸시다운됩니다:

* `soundex`: [soundex](/ko/reference/functions/regular-functions/string-functions#soundex)
* `levenshtein` (2-arg): [editDistanceUTF8](/ko/reference/functions/regular-functions/string-functions#editDistanceUTF8)

<div id="pushdown-casts">
  ### 푸시다운 캐스트
</div>

pg\_clickhouse는 호환되는 데이터 타입에 대해서는 `CAST(x AS bigint)`와 같은 캐스트를 푸시다운합니다. 호환되지 않는 타입에서는 푸시다운이 실패합니다. 이 예시에서 `x`가 ClickHouse `UInt64`라면 ClickHouse는 해당 값의 캐스트를 거부합니다.

호환되지 않는 데이터 타입으로의 캐스트도 푸시다운할 수 있도록 pg\_clickhouse는 다음 함수를 제공합니다. 이 함수들이 푸시다운되지 않으면 PostgreSQL에서 예외를 발생시킵니다.

* [toUInt8](/ko/reference/functions/regular-functions/type-conversion-functions#touint8)
* [toUInt16](/ko/reference/functions/regular-functions/type-conversion-functions#touint16)
* [toUInt32](/ko/reference/functions/regular-functions/type-conversion-functions#touint32)
* [toUInt64](/ko/reference/functions/regular-functions/type-conversion-functions#touint64)
* [toUInt128](/ko/reference/functions/regular-functions/type-conversion-functions#touint128)

<div id="pushdown-aggregates">
  ### 푸시다운 집계 함수
</div>

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

* [array\_agg](/ko/reference/functions/aggregate-functions/groupArray)
* [avg](/ko/reference/functions/aggregate-functions/avg)
* [bit\_and](/ko/reference/functions/aggregate-functions/groupBitAnd)
* [bit\_or](/ko/reference/functions/aggregate-functions/groupBitOr)
* [bit\_xor](/ko/reference/functions/aggregate-functions/groupBitXor)
* [bool\_and / every](/ko/reference/functions/aggregate-functions/groupBitAnd)
* [bool\_or](/ko/reference/functions/aggregate-functions/groupBitOr)
* [count](/ko/reference/functions/aggregate-functions/count)
* [min](/ko/reference/functions/aggregate-functions/min)
* [max](/ko/reference/functions/aggregate-functions/max)
* [string\_agg](/ko/reference/functions/aggregate-functions/groupConcat)
* [sum](/ko/reference/functions/aggregate-functions/sum)

<div id="custom-aggregates">
  ### 사용자 지정 집계 함수
</div>

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

* [argMax](/ko/reference/functions/aggregate-functions/argMax)
* [argMin](/ko/reference/functions/aggregate-functions/argMin)
* [uniq](/ko/reference/functions/aggregate-functions/uniq)
* [uniqCombined](/ko/reference/functions/aggregate-functions/uniqCombined)
* [uniqCombined64](/ko/reference/functions/aggregate-functions/uniqCombined64)
* [uniqExact](/ko/reference/functions/aggregate-functions/uniqExact)
* [uniqHLL12](/ko/reference/functions/aggregate-functions/uniqHLL12)
* [uniqTheta](/ko/reference/functions/aggregate-functions/uniqthetasketch)
* [quantile](/ko/reference/functions/aggregate-functions/quantile)
* [quantileExact](/ko/reference/functions/aggregate-functions/quantileExact)

<div id="pushdown-ordered-set-aggregates">
  ### 푸시다운 정렬된 집합 집계 함수
</div>

이 \[정렬된 집합 집계 함수]는 *직접 인수*를 매개변수로, `ORDER BY` 표현식을 인수로 전달하는 방식으로 ClickHouse \[매개변수화된
집계 함수]에 대응됩니다. 예를 들어, 다음 PostgreSQL 쿼리는:

```sql theme={null}
SELECT percentile_cont(0.25) WITHIN GROUP (ORDER BY a) FROM t1;
```

다음 ClickHouse 쿼리로 변환됩니다:

```sql theme={null}
SELECT quantile(0.25)(a) FROM t1;
```

기본값이 아닌 `ORDER BY` 접미사 `DESC` 및 `NULLS FIRST`는 지원되지 않으며, 사용하면 오류가 발생합니다.

* `percentile_cont(double)`: [quantile](/ko/reference/functions/aggregate-functions/quantile)
* `quantile(double)`: [quantile](/ko/reference/functions/aggregate-functions/quantile)
* `quantileExact(double)`: [quantileExact](/ko/reference/functions/aggregate-functions/quantileExact)

<div id="pushdown-window-functions">
  ### 푸시다운 윈도우 함수
</div>

다음 PostgreSQL \[윈도우 함수]는 `OVER
(PARTITION BY ... ORDER BY ...)` 절과 함께 ClickHouse로 푸시다운되며, 해당하는 경우 프레임 사양도 함께 푸시다운됩니다.

* [row\_number](/ko/reference/functions/window-functions#row_number)
* [rank](/ko/reference/functions/window-functions#rank)
* [dense\_rank](/ko/reference/functions/window-functions#dense_rank)
* [ntile](/ko/reference/functions/window-functions#ntile)
* [cume\_dist](/ko/reference/functions/window-functions#cume_dist)
* [percent\_rank](/ko/reference/functions/window-functions#percent_rank)
* [lead](/ko/reference/functions/window-functions#lead)
* [lag](/ko/reference/functions/window-functions#lag)
* [first\_value](/ko/reference/functions/window-functions#first_value)
* [last\_value](/ko/reference/functions/window-functions#last_value)
* [nth\_value](/ko/reference/functions/window-functions#nth_value)
* `min` / `max` (`OVER` 절 사용)

순위 함수(`row_number`, `rank`, `dense_rank`, `ntile`, `cume_dist`,
`percent_rank`)는 ClickHouse가 이러한 함수에서 프레임 사양을 허용하지 않으므로,
푸시다운할 때 프레임 절을 생략합니다.

<div id="compatibility-notes">
  ## 호환성 관련 참고 사항
</div>

<div id="regular-expressions">
  ### 정규식
</div>

[pg\_clickhouse.pushdown\_regex](#pg_clickhousepushdown_regex)가 true일 때(기본값) pg\_clickhouse는 정규식을 ClickHouse의 대응 표현식으로 푸시다운하며, 기본적인 수준의 호환성을 보장하기 위해 노력합니다. 다만 두 시스템 간 차이점과 pg\_clickhouse가 이를 어떻게 처리하는지는 숙지해야 합니다.

* PostgreSQL은 \[POSIX 정규식]을 지원하고 ClickHouse는
  [RE2 정규식][RE2]을 지원합니다. 동작 차이에 유의하십시오. 정규식이
  ClickHouse에서 평가되는 경우(예: `WHERE` 절)에는 RE2로 작성하고, Postgres에서 평가되는 경우(예: `SELECT` 절)에는 POSIX로 작성하십시오.

* pg\_clickhouse는 Postgres의 \[정규식 플래그]를 ClickHouse 정규식 앞에 `(?)` 형식으로 붙여 푸시다운합니다. 예를 들어 다음과 같습니다.

  ```sql theme={null}
  regexp_like(val, '^VAL\d', 'i')
  ```

  다음과 같이 변환됩니다.

  ```sql theme={null}
  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 플래그]는 사용하지 마십시오.

* 정규식 함수에 이외의 플래그가 전달되면 해당 함수는 푸시다운되지 않습니다.

* 예외는 `regexp_replace()`이며, 이 함수는 `g` 플래그도 지원합니다. `g`가 설정되면 pg\_clickhouse는
  `replaceRegexpOne()` 대신 `replaceRegexpAll()`을 사용하고, 다른 플래그를 앞에 붙이기 전에 `g` 플래그를 제거합니다.

* Postgres `regexp_replace()`의 replacement 인수는 전체 일치를 가리키는 데 `\&`를 지원하지만, ClickHouse에서는 전체 일치에 `\0`를 사용합니다. 함수가 ClickHouse로 푸시다운될 때는 반드시 `\0`를 사용하십시오.

모호함을 완전히 피하려면
[pg\_clickhouse.pushdown\_regex](#pg_clickhousepushdown_regex)를 설정하여
Postgres 정규식이 ClickHouse로 푸시다운되지 않도록 하고,
pg\_clickhouse가 ClickHouse 호환 [RE2] 정규식의 [직접 푸시다운](#re2)을 지원하는
\[re2 확장 기능]을 사용하는 것을 고려하십시오.

<div id="to_char">
  ### `to_char()`
</div>

`timestamp` 및 `timestamp with time zone`에 대한 PostgreSQL [`to_char()`]는 포맷 인수가
비-`NULL` 문자열 상수이고, 그 안의 모든 PostgreSQL 키워드에 바이트 단위까지 동일한 ClickHouse 대응 항목이 있는 경우에만 ClickHouse [formatDateTime]으로
푸시다운됩니다. 포맷이 동적이거나
(`Const`가 아님), 지원되지 않는 키워드 또는 수정자를 포함하는 경우에는
해당 호출은 PostgreSQL에서 로컬로 평가됩니다 — 부분 변환으로는
푸시다운을 전혀 시도하지 않으므로 출력은 PG 호환성을 유지합니다.

`numeric`, `interval` 및 기타
타임스탬프가 아닌 타입에 대한 2인수 `to_char()` 형태는 절대로 푸시다운되지 않습니다. ClickHouse [formatDateTime]은
날짜-시간 값만 포맷합니다.

<div id="translated-keywords">
  #### 번역된 키워드
</div>

| PostgreSQL                 | ClickHouse | 의미                        |
| -------------------------- | ---------- | ------------------------- |
| `YYYY`, `yyyy`             | `%Y`       | 4자리 연도                    |
| `YY`, `yy`                 | `%y`       | 2자리 연도                    |
| `MM`, `mm`                 | `%m`       | 앞을 0으로 채운 월 (01–12)       |
| `DD`, `dd`                 | `%d`       | 앞을 0으로 채운 일자 (01–31)      |
| `DDD`, `ddd`               | `%j`       | 앞을 0으로 채운 연중 날짜 (001–366) |
| `HH24`, `hh24`             | `%H`       | 앞을 0으로 채운 24시간 (00–23)    |
| `HH`, `hh`, `HH12`, `hh12` | `%I`       | 앞을 0으로 채운 12시간 (01–12)    |
| `MI`, `mi`                 | `%i`       | 앞을 0으로 채운 분 (00–59)       |
| `SS`, `ss`                 | `%S`       | 앞을 0으로 채운 초 (00–59)       |
| `Q`, `q`                   | `%Q`       | 분기 (1–4)                  |
| `Mon`                      | `%b`       | 축약된 월 이름(예: `Oct`)        |
| `Dy`                       | `%a`       | 축약된 요일 이름(예: `Mon`)       |
| `AM`, `PM`                 | `%p`       | 오전/오후 표시, 항상 대문자          |

<div id="quoted-text-and-literals">
  #### 인용된 텍스트와 리터럴
</div>

`"..."`로 감싼 텍스트는 있는 그대로 전달되며, 리터럴 `%`는
ClickHouse의 지정자 접두사(prefix)를 이스케이프하기 위해 `%%`로
두 번 써야 합니다. 따옴표 밖의 `\"`도 리터럴 `"`로 그대로 전달됩니다.
`"..."` 내부에서는 백슬래시가 `"`에만 이스케이프 문자로 작동하며, 다른 백슬래시
시퀀스는 리터럴 텍스트로 처리됩니다.

<div id="authors">
  ## 작성자
</div>

[David E. Wheeler](https://justatheory.com/)

<div id="copyright">
  ## 저작권
</div>

저작권 (c) 2025-2026, ClickHouse

[foreign data wrapper]: https://www.postgresql.org/docs/current/fdwhandler.html "PostgreSQL 문서: Foreign Data Wrapper 작성"

[Docker image]: https://github.com/ClickHouse/pg_clickhouse/pkgs/container/pg_clickhouse "Docker Hub의 최신 버전"

[ClickHouse]: https://clickhouse.com/clickhouse

[Semantic Versioning]: https://semver.org/spec/v2.0.0.html "시맨틱 버전 관리 2.0.0"

[`pg_get_loaded_modules()`]: https://pgpedia.info/g/pg_get_loaded_modules.html "pgPedia: pg_get_loaded_modules()"

[DDL]: https://en.wikipedia.org/wiki/Data_definition_language "Wikipedia: 데이터 정의 언어"

[CREATE EXTENSION]: https://www.postgresql.org/docs/current/sql-createextension.html "PostgreSQL 문서: CREATE EXTENSION"

[ALTER EXTENSION]: https://www.postgresql.org/docs/current/sql-alterextension.html "PostgreSQL 문서: ALTER EXTENSION"

[DROP EXTENSION]: https://www.postgresql.org/docs/current/sql-dropextension.html "PostgreSQL 문서: DROP EXTENSION"

[CREATE SERVER]: https://www.postgresql.org/docs/current/sql-createserver.html "PostgreSQL 문서: CREATE SERVER"

[ALTER SERVER]: https://www.postgresql.org/docs/current/sql-alterserver.html "PostgreSQL 문서: ALTER SERVER"

[DROP SERVER]: https://www.postgresql.org/docs/current/sql-dropserver.html "PostgreSQL 문서: DROP SERVER"

[CREATE USER MAPPING]: https://www.postgresql.org/docs/current/sql-createusermapping.html "PostgreSQL 문서: CREATE USER MAPPING"

[ALTER USER MAPPING]: https://www.postgresql.org/docs/current/sql-alterusermapping.html "PostgreSQL 문서: ALTER USER MAPPING"

[DROP USER MAPPING]: https://www.postgresql.org/docs/current/sql-dropusermapping.html "PostgreSQL 문서: DROP USER MAPPING"

[IMPORT FOREIGN SCHEMA]: https://www.postgresql.org/docs/current/sql-importforeignschema.html "PostgreSQL 문서: IMPORT FOREIGN SCHEMA"

[CREATE FOREIGN TABLE]: https://www.postgresql.org/docs/current/sql-createforeigntable.html "PostgreSQL 문서: CREATE FOREIGN TABLE"

[table engine]: /reference/engines/table-engines "ClickHouse 문서: 테이블 엔진"

[AggregateFunction Type]: /reference/data-types/aggregatefunction "ClickHouse 문서: AggregateFunction 타입"

[SimpleAggregateFunction Type]: /reference/data-types/simpleaggregatefunction "ClickHouse 문서: SimpleAggregateFunction 타입"

[ALTER FOREIGN TABLE]: https://www.postgresql.org/docs/current/sql-alterforeigntable.html "PostgreSQL 문서: ALTER FOREIGN TABLE"

[DROP FOREIGN TABLE]: https://www.postgresql.org/docs/current/sql-dropforeigntable.html "PostgreSQL 문서: DROP FOREIGN TABLE"

[DML]: https://en.wikipedia.org/wiki/Data_manipulation_language "Wikipedia: 데이터 조작 언어"

[EXPLAIN]: https://www.postgresql.org/docs/current/sql-explain.html "PostgreSQL 문서: EXPLAIN"

[SELECT]: https://www.postgresql.org/docs/current/sql-select.html "PostgreSQL 문서: SELECT"

[PREPARE]: https://www.postgresql.org/docs/current/sql-prepare.html "PostgreSQL 문서: PREPARE"

[EXECUTE]: https://www.postgresql.org/docs/current/sql-execute.html "PostgreSQL 문서: EXECUTE"

[DEALLOCATE]: https://www.postgresql.org/docs/current/sql-deallocate.html "PostgreSQL 문서: DEALLOCATE"

[PREPARE]: https://www.postgresql.org/docs/current/sql-prepare.html "PostgreSQL 문서: PREPARE"

[INSERT]: https://www.postgresql.org/docs/current/sql-insert.html "PostgreSQL 문서: INSERT"

[COPY]: https://www.postgresql.org/docs/current/sql-copy.html "PostgreSQL 문서: COPY"

[LOAD]: https://www.postgresql.org/docs/current/sql-load.html "PostgreSQL 문서: LOAD"

[SET]: https://www.postgresql.org/docs/current/sql-set.html "PostgreSQL 문서: SET"

[ALTER ROLE]: https://www.postgresql.org/docs/current/sql-alterrole.html "PostgreSQL 문서: ALTER ROLE"

[shared library preloading]: https://www.postgresql.org/docs/current/runtime-config-client.html#RUNTIME-CONFIG-CLIENT-PRELOAD "PostgreSQL 문서: 공유 라이브러리 사전 로드"

[ordered-set aggregate functions]: https://www.postgresql.org/docs/current/functions-aggregate.html#FUNCTIONS-ORDEREDSET-TABLE

[Parametric aggregate functions]: /reference/functions/aggregate-functions/parametric-functions

[ClickHouse settings]: /reference/settings/session-settings "ClickHouse Docs: 세션 설정"

[dollar quoting]: https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING "PostgreSQL Docs: 달러 인용 문자열 상수"

[PREPARE notes]: https://www.postgresql.org/docs/current/sql-prepare.html#SQL-PREPARE-NOTES "PostgreSQL Docs: PREPARE 참고 사항"

[query parameters]: /guides/clickhouse/data-modelling/stored-procedures-and-prepared-statements#alternatives-to-prepared-statements-in-clickhouse "ClickHouse Docs: ClickHouse에서 prepared statement의 대안"

[underlying bug]: https://github.com/ClickHouse/ClickHouse/issues/85847 "ClickHouse/ClickHouse#85847 multipart form의 일부 쿼리가 설정을 읽지 못함"

[fixed]: https://github.com/ClickHouse/ClickHouse/pull/85570 "ClickHouse/ClickHouse#85570 multipart를 사용하는 HTTP 수정"

[BYTEA]: https://www.postgresql.org/docs/current/datatype-binary.html "PostgreSQL Docs: 바이너리 데이터 타입"

[GRANT]: https://www.postgresql.org/docs/current/sql-grant.html "PostgreSQL Docs: GRANT"

[String]: /reference/data-types/string "ClickHouse Docs: String"

[TEXT]: https://www.postgresql.org/docs/current/datatype-character.html "PostgreSQL Docs: 문자 데이터 타입"

[window functions]: https://www.postgresql.org/docs/current/functions-window.html "PostgreSQL Docs: 윈도우 함수"

[POSIX Regular Expressions]: https://www.postgresql.org/docs/18/functions-matching.html#FUNCTIONS-POSIX-REGEXP "PostgreSQL Docs: POSIX 정규식"

[Postgres flags]: https://www.postgresql.org/docs/18/functions-matching.html#POSIX-EMBEDDED-OPTIONS-TABLE "PostgreSQL Docs: ARE 내장 옵션 문자"

[RE2]: https://github.com/google/re2/wiki/Syntax "RE2 구문"

[re2 extension]: https://github.com/ClickHouse/pg_re2 "pg_re2: RE2를 사용하는 ClickHouse 호환 정규식 함수"

[intarray]: https://www.postgresql.org/docs/current/intarray.html "PostgreSQL Docs: intarray"

[fuzzystrmatch]: https://www.postgresql.org/docs/current/fuzzystrmatch.html "PostgreSQL Docs: fuzzystrmatch"

[`to_char()`]: https://www.postgresql.org/docs/current/functions-formatting.html "PostgreSQL Docs: 데이터 타입 서식 함수"

[formatDateTime]: /reference/functions/regular-functions/date-time-functions#formatDateTime "ClickHouse Docs: formatDateTime"
