PREWHERE を使用すると、読み取るデータ量を削減し、フィルタリングを効率化できます。デフォルトでは、クエリで PREWHERE を明示的に指定していなくても、ClickHouse は対象となる条件を WHERE から PREWHERE に移動して、この最適化を適用します。この段階で適用する条件を制御するには、PREWHERE を明示的に指定します。
PREWHERE では、ClickHouse はまず条件の評価に必要なカラムだけを読み取ります。次に、一致する行を少なくとも 1 行含むブロックに対してのみ、クエリで必要な他のカラムを読み取ります。条件で使用するカラム数がクエリの他の部分より少なく、多くのブロックを除外できる場合、読み取るデータ量を削減できます。
PREWHERE を手動で制御する
条件が少数のカラムのみを参照し、多くの行を除外する場合は、PREWHERE を手動で指定します。これにより、残りのカラムから読み取るデータ量を削減できます。
クエリには PREWHERE と WHERE の両方を含めることができます。この場合、PREWHERE が先に評価されます。
ClickHouse が条件を WHERE から PREWHERE に自動的に移動しないようにするには、optimize_move_to_prewhere を 0 に設定します。
FINAL 修飾子を使用するクエリでは、optimize_move_to_prewhere と optimize_move_to_prewhere_if_final の両方が有効な場合にのみ、ClickHouse は条件を WHERE から PREWHERE に移動します。
JOIN を使用する PREWHERE
JOIN を含むクエリでは、PREWHERE 条件が直接参照できるカラムは最大で1つのテーブルに限られます。ClickHouse は、結合前にそのテーブルの行に条件を適用します。
一方、WHERE 条件は論理上、結合結果をフィルタリングします。ただし、結果が変わらない場合、オプティマイザによって結合前に適用されることがあります。そのため、同じ条件を PREWHERE と WHERE で使用すると、特に外部結合では結果が異なる場合があります。
次の例では、この違いを示すために2つのテーブルを作成します。
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');最初のクエリでは、PREWHERE により LEFT 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 │
└────┴───────┴───────────────┘制限事項
PREWHERE は、*MergeTree ファミリーのテーブルでのみサポートされています。
例
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.