Skip to main content

Derived Values with calculate()

calculate(expression: "...") computes a derived value server-side, so ratios, differences and percentages come back with the row instead of being assembled in your client.

buys: count(if: { Side: { is: "Buy" } })
all: count
ratio: calculate(expression: "$buys / $all")

$name refers to a response key

Inside an expression, $name refers to a key in the same selection set — the explicit alias when you give one, otherwise the field's own path with nested levels joined by underscore:

SelectionReference
all: count$all
Amounts { Base }$Amounts_Base
q: Amounts { Base }$q_Base

It reaches native fields, native metrics and other calculate results alike. The $ is required — a bare buys / all is passed through as raw column names and fails.

References are order-independent (a forward reference is fine) and chain to any depth. They compile to SQL aliases, so a self-reference fails with Code: 47 MISSING_COLUMNS and a mutual reference with Code: 174 CYCLIC_ALIASES.

When your query becomes aggregated

calculate never triggers a GROUP BY on its own. What matters is whether a native metric (count, sum, uniq, average, …) is present:

  • No native metric — the query stays in row mode. A bare column reference such as "Amounts_Base" evaluates per row and works fine.
  • A native metric is present — the API groups by the selected dimensions. Any bare column in your expression must now be one of those selected dimensions, or ClickHouse raises Code: 215 NOT_AN_AGGREGATE.
The error names the wrong field

Code: 215 names the dimension, not your expression — for example Column 'Price' is not under aggregate function and not in GROUP BY. The fix is usually to add a native metric, or to select the column your expression references.

An aggregate expression on its own is fine — calculate(expression: "sum(Amounts_Base)") with nothing else selected returns one row. It only needs a companion native metric once you also select a dimension.

What you can write

The expression is evaluated by ClickHouse, so the standard function library is available: arithmetic (+ - * / %), pow, sqrt, abs, log, round, greatest, least, conditionals (if, multiIf), aggregates (sum, count, avg, min, max, uniqExact, argMax, sumIf, countIf, quantile(0.5)(...)) and window forms such as row_number() OVER ().

buyShare: calculate(expression: "countIf(Side = 'Buy') / count()")

Use single quotes for SQL string literals — the expression is a GraphQL string, so a double quote must be escaped. Commas, nested parentheses, newlines and SQL comments are all fine.

Ranking by a derived value

orderBy accepts a calculate alias, and it composes with limitBy — this is the rank-by-derived-ratio pattern:

orderBy: { descendingByField: "buyShare" }
limitBy: { by: Pair_Token_Address, count: 1 }

calculate itself takes no selectWhere and no if:expression is its only argument. To gate a ranked ratio, put a selectWhere floor on a sibling aggregate (for example count(selectWhere: { ge: "100" })) and apply the final cut on the ratio in your client.

Traps

TrapBehaviour
Everything coerces to FloatThe expression is wrapped in toFloat64(...) and the field's type is Float. A string-producing expression fails with Code: 6 unless the string parses as a number — toString(42) works, concat('a','b') does not
^ is not exponentiation2^3 fails with Code: 62. Use pow(2,3)
The ternary ? : is unusable? collides with the SQL builder's placeholder — not enough args when interpolating. Use if(cond, a, b)
Booleans and dates flattenBool1/0, DateTime → unix seconds, Date → days since epoch
Infinity, NaN and NULL are silently returned as 0

A divide-by-zero ratio comes back as 0, indistinguishable from a genuine zero — and under ascendingByField those rows sort first.

The values really are non-finite inside ClickHouse (isInfinite(count()/0) returns 1), so guard the denominator yourself:

safe: calculate(expression: "$numerator / nullIf($denominator, 0)")

In subscriptions

The grammar is much narrower on a stream: arithmetic only. Every function call — round(), floor(), abs() — returns null, and a single unsigiled reference makes the whole expression null with no error. See what does not survive streaming.

Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.