OQL Functions
OQL's SELECT clause supports a fixed set of aggregate functions. An aggregate collapses
many rows into one value — per group when you use BY, per time bucket when you use
EVERY, or over the whole window otherwise.
Aggregate functions
| Function | What it returns |
|---|---|
avg(col) |
Arithmetic mean of col |
sum(col) |
Sum of col |
min(col) |
Smallest value |
max(col) |
Largest value |
count(*) |
Number of rows |
count(col) |
Number of rows where col is non-null |
any(col) |
One arbitrary value of col from the group |
p50(col) |
50th percentile (median) |
p90(col) |
90th percentile |
p95(col) |
95th percentile |
p99(col) |
99th percentile |
A few notes:
count(*)vscount(col)—count(*)counts every row;count(col)counts only rows wherecolhas a value. Usecount(*)for "how many log lines / spans / events".any(col)is handy when a column is constant within a group and you just want to carry it through alongside other aggregates.
-- Row counts
SELECT count(*) FROM logs WHERE level = 'ERROR' LAST 1h
-- Min / max / avg together
SELECT min(cpu_user_pct), avg(cpu_user_pct), max(cpu_user_pct)
FROM host BY host LAST 1h
Percentiles
p50, p90, p95, and p99 answer "what value is the Nth-percentile sample?" — the
standard way to talk about latency, because an average hides the slow tail.
-- p50 / p95 / p99 request latency over time
SELECT p50(duration_ns), p95(duration_ns), p99(duration_ns)
FROM spans
WHERE service = 'checkout'
EVERY 1m LAST 1h
Read it as: "99% of requests in each minute completed faster than the p99 line."
Percentiles are computed per group and per bucket, exactly like the other aggregates.
Counters vs gauges
Metrics come in two flavours, and it matters which aggregate you pick.
Gauges are point-in-time readings that go up and down — CPU percentage, memory in use,
queue depth, temperature. Aggregate them directly with avg, min, max, percentiles:
Counters only ever increase — bytes sent, requests served, operations completed. Their
absolute value is rarely interesting; what you usually want is how much they grew
over a window. In OQL you get that growth with max(col) - min(col)-style thinking, or by
summing already-derived values. In the schema, counter columns are marked as such (see
Tables) — for example everything in network (rx_bytes, tx_bytes) and
disk (read_bytes, write_bytes) is a counter.
-- Network traffic per interface (counters): sum the byte counters per bucket
SELECT sum(rx_bytes), sum(tx_bytes) FROM network
BY interface EVERY 5m LAST 6h TOP 10
External-metric tables handle this for you
On the database / cache / mq virtual tables,
each native metric column knows whether it is a gauge or a counter, and a bare column
reduces accordingly over each bucket:
- gauge →
avg - monotonic counter → the window increase (
max − min)
You can override that default by naming an explicit aggregate:
-- Default reduction: avg for the gauge, increase for the counter
SELECT postgresql.backends, postgresql.commits
FROM database WHERE engine = 'postgresql'
EVERY 1m LAST 1h
-- Explicit override: peak backends instead of the average
SELECT max(postgresql.backends)
FROM database WHERE engine = 'postgresql'
EVERY 1m LAST 1h
There is no rate() function. To get a per-second rate, take the counter increase over a
bucket and divide by the bucket length yourself.