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.

Pasted in
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
Formatted output
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.