PREWHERE 절

PREWHERE 절

PREWHERE는 읽어들이는 데이터의 양을 줄여 필터링을 더 효율적으로 만들 수 있어요. 기본적으로 ClickHouse는 쿼리가 명시적으로 PREWHERE를 지정하지 않아도 WHERE의 조건 중 적격한 것을 PREWHERE로 옮기는 이 최적화를 적용해요. PREWHERE를 명시적으로 지정하면 어떤 조건을 이 단계에서 적용할지 직접 제어할 수 있어요.

출처: 문서

본문

PREWHERE를 사용하면 ClickHouse는 먼저 조건을 평가하는 데 필요한 열만 읽어요. 그런 다음 조건과 일치하는 행이 하나 이상 포함된 블록에 대해서만 쿼리에 필요한 다른 열을 읽어요. 조건이 나머지 쿼리보다 적은 열을 사용하고 많은 블록을 걸러낼 때 읽는 데이터 양을 줄일 수 있어요.

Controlling PREWHERE manually (PREWHERE 수동 제어)

조건이 적은 수의 열을 참조하면서 많은 행을 걸러낼 때는 PREWHERE를 수동으로 지정해요. 그러면 나머지 열에 대해 읽어들이는 데이터 양을 줄일 수 있어요. 쿼리에는 PREWHEREWHERE를 모두 포함할 수 있어요. 이 경우 PREWHERE가 먼저 평가돼요. optimize_move_to_prewhere0으로 설정하면 ClickHouse가 WHERE의 조건을 PREWHERE로 자동 이동하지 않게 해요. FINAL 수정자를 사용하는 쿼리에서는 optimize_move_to_prewhereoptimize_move_to_prewhere_if_final이 모두 활성화되어 있을 때만 ClickHouse가 WHERE의 조건을 PREWHERE로 옮겨요. 기본적으로 PREWHEREFINAL보다 먼저 평가되므로, PREWHERE가 테이블의 ORDER BY 키 밖에 있는 열을 참조하면 FROM ... FINAL 쿼리는 예상치 못한 결과를 낼 수 있어요.

PREWHERE with JOIN (JOIN과 함께 쓰는 PREWHERE)

JOIN이 포함된 쿼리에서 PREWHERE 조건은 최대 한 개 테이블의 열만 직접 참조할 수 있어요. ClickHouse는 그 행들이 조인에 도달하기 전에 해당 조건을 그 테이블의 행에 적용해요. 반면 WHERE 조건은 논리적으로 조인된 결과를 필터링해요. 다만 옵티마이저는 결과가 변하지 않을 때 이를 조인 전에 적용할 수도 있어요. 따라서 같은 조건이라도 PREWHEREWHERE에 사용하면 특히 outer join에서 서로 다른 결과가 나올 수 있어요. 다음 예제는 이 차이를 보여주기 위해 두 테이블을 만드는 예시예요.

CREATE TABLE table_1
(
    `id` UInt32,
    `value` String
)
ENGINE = MergeTree
ORDER BY id;

CREATE TABLE table_2
(
    `id` UInt32,
    `value` String
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO table_1 VALUES (1, 'a'), (2, 'b'), (3, 'c');
INSERT INTO table_2 VALUES (1, 'x'), (2, 'y'), (3, 'z');

첫 번째 쿼리에서는 PREWHERELEFT JOIN 전에 table_2를 필터링하므로, table_1에서 id = 1인 행은 매칭되지 않은 채 남아요.

SELECT
    table_1.id,
    table_1.value,
    table_2.value
FROM table_1
LEFT JOIN table_2 ON table_1.id = table_2.id
PREWHERE table_2.id >= 2
ORDER BY table_1.id;
   ┌─id─┬─value─┬─table_2.value─┐
1. │  1 │ a     │               │
2. │  2 │ b     │ y             │
3. │  3 │ c     │ z             │
   └────┴───────┴───────────────┘

같은 조건을 WHERE에 사용하면 조인된 결과가 필터링되어 id = 1인 행이 제거돼요.

SELECT
    table_1.id,
    table_1.value,
    table_2.value
FROM table_1
LEFT JOIN table_2 ON table_1.id = table_2.id
WHERE table_2.id >= 2
ORDER BY table_1.id;
   ┌─id─┬─value─┬─table_2.value─┐
1. │  2 │ b     │ y             │
2. │  3 │ c     │ z             │
   └────┴───────┴───────────────┘

Limitations (제한 사항)

PREWHERE*MergeTree 계열 테이블에서만 지원돼요.

Example (예제)

CREATE TABLE mydata
(
    `A` Int64,
    `B` Int8,
    `C` String
)
ENGINE = MergeTree
ORDER BY A AS
SELECT
    number,
    0,
    if(number between 1000 and 2000, 'x', toString(number))
FROM numbers(10000000);

SELECT count()
FROM mydata
WHERE (B = 0) AND (C = 'x');

1 row in set. Elapsed: 0.074 sec. Processed 10.00 million rows, 168.89 MB (134.98 million rows/s., 2.28 GB/s.)

-- Enable tracing to see which predicates are moved to PREWHERE.
set send_logs_level='debug';

MergeTreeWhereOptimizer: condition "B = 0" moved to PREWHERE
-- ClickHouse automatically moves B = 0 to PREWHERE, but this condition does not filter any rows because B is always 0.

-- Move the more selective C = 'x' predicate to PREWHERE.

SELECT count()
FROM mydata
PREWHERE C = 'x'
WHERE B = 0;

1 row in set. Elapsed: 0.069 sec. Processed 10.00 million rows, 158.89 MB (144.90 million rows/s., 2.30 GB/s.)

-- The query with manually specified PREWHERE processes slightly less data: 158.89 MB instead of 168.89 MB.

더 알아보기 (Learn more)