ClickHouse SQL Formatter
Paste a ClickHouse query and get it formatted instantly. This formatter uses a real ClickHouse grammar rather than approximating it with MySQL, so ClickHouse-only syntax survives intact — and it reads {name:Type} query parameters, so you can fill them in and get a runnable query back.
A ClickHouse query, before and after
The unformatted query below is the sample loaded into the editor above, and the output is exactly what this formatter returns for it with the default options. Nothing was tidied up by hand.
WITH matched AS (SELECT fingerprint FROM traces_resource WHERE simpleJSONExtractString(labels, 'service.name') = {service:String}), ranked AS (SELECT resource_string_service$$name AS svc, toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts, attributes_string['http.method'] AS method, quantile(0.95)(duration_nano) AS p95 FROM signoz_index_v3 PREWHERE timestamp >= {start_ts:UInt64} AND timestamp <= {end_ts:UInt64} WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM matched) GROUP BY svc, ts, method) SELECT * FROM ranked WHERE p95 > {threshold:UInt64} ORDER BY ts DESC SETTINGS max_threads = 8 WITH
matched AS (
SELECT
fingerprint
FROM
traces_resource
WHERE
simpleJSONExtractString(labels, 'service.name') = {service:String}
),
ranked AS (
SELECT
resource_string_service$$name AS svc,
toStartOfInterval(timestamp, INTERVAL 60 SECOND) AS ts,
attributes_string['http.method'] AS method,
quantile(0.95)(duration_nano) AS p95
FROM
signoz_index_v3
PREWHERE
timestamp >= {start_ts:UInt64}
AND timestamp <= {end_ts:UInt64}
WHERE
resource_fingerprint GLOBAL IN (
SELECT
fingerprint
FROM
matched
)
GROUP BY
svc,
ts,
method
)
SELECT
*
FROM
ranked
WHERE
p95 > {threshold:UInt64}
ORDER BY
ts DESC
SETTINGS
max_threads = 8 ClickHouse syntax it handles
A generic formatter mangles dialect-specific syntax or refuses it outright. These are the ClickHouse constructs this formatter is tested against.
{name:Type} ClickHouse query parameters are detected as real parameters, named after the parameter rather than its type. Fill start_ts once and every use updates; DateTime64, Array and Nullable types map to the right input.
PREWHERE Discovered as its own editable filter clause alongside WHERE, so you can tune the clause that actually drives ClickHouse's read path.
ARRAY JOIN Kept as a single clause instead of being split into a phantom JOIN.
attributes['key'] Map and array subscripts are preserved exactly, including dotted keys such as attributes_string['http.method'].
quantile(0.95)(x) Parametric aggregate functions keep both argument lists tight together.
column$$name Materialised columns containing $$ are treated as identifiers, not as an unterminated dollar-quoted string.
GLOBAL IN Distributed-query operators are left verbatim, and the subquery inside them stays editable.
SETTINGS / FORMAT Trailing clauses are recognised as clause boundaries, so editing a WHERE never swallows them.
ClickHouse formatter FAQ
Does it understand ClickHouse query parameters?
Yes. ClickHouse's {name:Type} parameters are detected, grouped by name, and typed from their declared type. Two parameters that share a type — say {start_ts:UInt64} and {end_ts:UInt64} — stay separate, and filling one does not overwrite the other. Switch the output to filled SQL and you get a query you can run directly.
Can it format a query built from several CTEs?
Yes, and it can edit them. Every WHERE and PREWHERE clause in the query is listed by the CTE it belongs to, so you can pick one and edit it as an AND/OR tree or node graph. Edits are spliced back into that exact clause and nothing else in the query moves.
Is my ClickHouse query sent to a server?
No. Formatting runs entirely in your browser, so production queries, table names and embedded values never leave your machine.
Why doesn't editing a WHERE clause swallow the SETTINGS at the end?
Because SETTINGS and FORMAT are matched by their argument syntax rather than by the bare keyword. That distinction matters both ways: a query filtering on a column literally named settings still parses correctly, and a trailing SETTINGS max_threads = 8 is recognised as a clause boundary instead of being pulled into the condition you are editing.
Are ARRAY JOIN and map columns handled?
Yes. ARRAY JOIN stays a single clause instead of being split into a phantom JOIN, and map subscripts keep their exact text — including dotted keys such as attributes_string['http.method'], which simpler formatters break by inserting spaces around the dot.
Does it cope with materialised column names containing a double dollar sign?
Yes. A name like resource_string_service$$name is treated as an identifier, not as the opening of a Postgres-style dollar-quoted string. Getting that wrong blanks out the rest of the query, so every parameter and WHERE clause after that point silently disappears — which is exactly the failure you hit pasting SigNoz or observability schemas into a generic formatter.