PREWHERE pode tornar a filtragem mais eficiente ao reduzir a quantidade de dados lidos. Por padrão, o ClickHouse aplica essa otimização mesmo quando uma consulta não especifica PREWHERE explicitamente, movendo condições qualificadas de WHERE para PREWHERE. Você pode especificar PREWHERE explicitamente para controlar quais condições são aplicadas nessa etapa.
Com PREWHERE, o ClickHouse primeiro lê apenas as colunas necessárias para avaliar a condição. Em seguida, lê as demais colunas exigidas pela consulta apenas nos blocos que contêm pelo menos uma linha correspondente. Isso pode reduzir a quantidade de dados lidos quando a condição usa menos colunas do que o restante da consulta e elimina muitos blocos.
Controle manual de PREWHERE
Especifique PREWHERE manualmente quando uma condição fizer referência a poucas colunas e filtrar muitas linhas. Isso pode reduzir a quantidade de dados lidos das colunas restantes.
Uma consulta pode conter tanto PREWHERE quanto WHERE. Nesse caso, PREWHERE é avaliado primeiro.
Defina optimize_move_to_prewhere como 0 para impedir que o ClickHouse mova condições automaticamente de WHERE para PREWHERE.
Para consultas com o modificador FINAL, o ClickHouse move condições de WHERE para PREWHERE somente quando optimize_move_to_prewhere e optimize_move_to_prewhere_if_final estão habilitados.
PREWHERE com JOIN
Uma condição PREWHERE em uma consulta com um JOIN pode referenciar diretamente colunas de, no máximo, uma tabela. O ClickHouse aplica a condição às linhas dessa tabela antes que elas sejam incluídas na junção.
Por outro lado, uma condição WHERE filtra logicamente o resultado da junção, embora o otimizador possa aplicá-la antes da junção quando isso não altera o resultado. Portanto, usar a mesma condição em PREWHERE e WHERE pode produzir resultados diferentes, especialmente com junções externas.
O exemplo a seguir cria duas tabelas para demonstrar essa diferença:
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');Na primeira consulta, PREWHERE filtra table_2 antes do LEFT JOIN, de modo que a linha de table_1 com id = 1 fica sem correspondência:
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 │
└────┴───────┴───────────────┘Usar a mesma condição em WHERE filtra o resultado do JOIN, removendo a linha com 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 │
└────┴───────┴───────────────┘Limitações
PREWHERE é suportado apenas por tabelas da família *MergeTree.
Exemplo
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.