# Aggregation operators

Aggregation operators enable you to create log-based metrics. Log-based metrics help you cut through the noise of high-volume logs to identify trends and patterns in your application activity.

FuseQL groups aggregations by time buckets and supports additional grouping dimensions.

## avg

Computes the arithmetic mean of a numeric field across matched log lines within the selected query time window. The `avg` operator ignores null or missing values. Use it to understand the typical value of a metric — for example, the mean response body size per HTTP method over the last hour.

### Syntax

```none
| avg(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to average. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_avg`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and compute the average response body size (in bytes) per HTTP method. A much higher average for `GET` than `POST` reflects that GET requests return full resource payloads while POST responses are typically small acknowledgements.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| avg(bytes) as avg_bytes by method
```

| method | avg_bytes |
| --- | --- |
| GET | 87,284.33 |
| POST | 37.77 |
| OPTIONS | 69.0 |

Expected output

|     |     |
| --- | --- |
|  | `avg` aggregates over every log line that matches the query within the selected time window (for example, the last 1 hour). A single returned row represents the mean across all matching events in that window. After extracting fields with `parse`, FuseQL coerces the string values to numbers automatically — no explicit conversion is needed. |

## count_unique

Counts the number of distinct values of a field across matched log lines. `count_unique` works on string, UUID, and IP address typed facets — it cannot be applied to purely numeric fields. Use it to answer questions like "how many distinct URLs were requested per HTTP method?" or "how many unique user sessions made requests in this window?"

### Syntax

```none
| count_unique(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The string, UUID, or IP address facet to count distinct values of. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_count_unique`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example: distinct URLs per HTTP method

Parse nginx access logs and count how many unique URLs were requested for each HTTP method. A high count for `GET` compared to `POST` indicates broad read access across many resources.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| count_unique(url) by method
```

| method | _count_unique |
| --- | --- |
| DELETE | 2 |
| GET | 3,353 |
| POST | 197,143 |

Expected output

|     |     |
| --- | --- |
|  | `count_unique` works on string, UUID, and IP address typed facets. Fields extracted by `parse` are treated as strings, so they work directly with `count_unique` without any conversion. |

## count

Counts the total number of log lines matched by the query. Unlike most aggregation operators, `count` takes no field argument — it counts rows, not field values. Use it to measure request volume, error frequency, or event occurrence across any grouping.

### Syntax

```none
| count [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `as <alias>` | Optional | Renames the output column. Defaults to `_count`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example: request count by HTTP method

Parse nginx access logs and count requests per HTTP method to understand traffic distribution.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| count by method
```

| method | _count |
| --- | --- |
| DELETE | 4 |
| GET | 92,253 |
| POST | 67,267,496 |

Expected output

### Example: time-bucketed request counts

Count requests per minute to spot traffic spikes or drops over time.

```none
source="nginx"
| timeslice 1m
| count as req_count by _timeslice
```

| _timeslice | req_count |
| --- | --- |
| 2024-06-10 09:00 | 38,412 |
| 2024-06-10 09:01 | 41,987 |
| 2024-06-10 09:02 | 39,631 |

Expected output

|     |     |
| --- | --- |
|  | `count` counts every matched log line, including lines where a particular field is null. To count only lines where a specific field has a value, use `count_unique` on that field, or add a `where` filter before counting. |

## first

Returns the value of a field from the chronologically earliest log line within the query window. When grouped with `by`, `first` returns the earliest value observed per group. Use it to capture the initial state — for example, the first URL requested via each HTTP method at the start of a traffic window.

### Syntax

```none
| first(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The field whose earliest value to return. Works with string and numeric fields. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_first`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and retrieve the first URL requested for each HTTP method. This shows the first request path seen for each method within the selected time window.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| first(url) by method
```

| method | _first |
| --- | --- |
| DELETE | /grafana/apis/…​/dashboards/dcb2ef50 |
| GET | /oauth2/sign_in?rd=%2F HTTP/1.1 |
| POST | /ingester/otlp/metrics HTTP/2.0 |

Expected output

|     |     |
| --- | --- |
|  | `first` is determined by log ingestion order within the query window, which reflects the timestamp on each log line. It is most useful for baselining or detecting cold-start behaviour. Compare `first` against `last` to see whether values changed significantly over the window. |

## last

Returns the value of a field from the chronologically latest log line within the query window. When grouped with `by`, `last` returns the most recent value observed per group. Use it to capture the current or final state — for example, the most recent URL requested via each HTTP method at the end of a traffic window.

### Syntax

```none
| last(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The field whose latest value to return. Works with string and numeric fields. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_last`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and retrieve the most recent URL requested for each HTTP method. This shows the last request path seen for each method within the selected time window.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| last(url) by method
```

| method | _last |
| --- | --- |
| DELETE | /grafana/apis/…​/dashboards/fc05b8c4 |
| GET | /ingester/health/ HTTP/2.0 |
| POST | /ingester/otlp/v1/logs HTTP/2.0 |

Expected output

|     |     |
| --- | --- |
|  | `last` is the counterpart of `first` — together they let you compare the earliest and most recent observations within a window to detect trends or state changes. `last` is most useful for capturing the final outcome of a process or the steady-state value after a warm-up period. |

## max

Returns the highest value of a numeric field across matched log lines. The `max` operator ignores null or missing values. Use it to surface worst-case conditions such as the highest HTTP status code, peak response latency, or maximum error count observed in a window.

### Syntax

```none
| max(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to find the maximum of. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_max`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and find the largest response body size (in bytes) per HTTP method. A very large `max_bytes` for `GET` reveals the biggest single payload served — useful for identifying unexpectedly large responses.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| max(bytes) as max_bytes by method
```

| method | max_bytes |
| --- | --- |
| GET | 35,590,999 |
| POST | 10,422,627 |
| OPTIONS | 138 |

Expected output

|     |     |
| --- | --- |
|  | `max` surfaces worst-case conditions. A single outlier can cause `max` to spike even when `avg` looks healthy — pair `max` with `p99` to distinguish rare extreme outliers from the general tail. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically. |

## min

Returns the lowest value of a numeric field across matched log lines. The `min` operator ignores null or missing values. Use it to find the best-case response time, the lowest HTTP status code, or the minimum resource usage observed in a window.

### Syntax

```none
| min(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to find the minimum of. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_min`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and find the smallest response body size (in bytes) per HTTP method. A `min_bytes` of 0 for `GET` and `POST` indicates that some responses sent no body — for example, 204 No Content or 304 Not Modified responses.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| min(bytes) as min_bytes by method
```

| method | min_bytes |
| --- | --- |
| GET | 0 |
| POST | 0 |
| OPTIONS | 0 |

Expected output

|     |     |
| --- | --- |
|  | `min` is useful for establishing a lower bound. A result of 0 bytes does not mean the response failed — status codes like `204 No Content` and `304 Not Modified` legitimately send no body. Combine `min` and `max` in a single query to see the full response size range in one result set. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically. |

## percentiles

Computes a percentile of a numeric field across matched log lines. Seven percentile operators are available: `p16`, `p50`, `p75`, `p84`, `p90`, `p95`, and `p99`. Multiple percentile operators can be combined in a single query pipe stage to produce several percentile columns at once. Null or missing field values are ignored.

### Syntax

```none
| p50(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p75(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p90(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p95(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p99(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p84(<field>) [as <alias>] [by <field1>, <field2>, ...]
| p16(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to compute the percentile of. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_p<N>` (e.g. `_p95`). |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example: median and tail percentiles for HTTP status codes

Parse nginx access logs and compute p50, p90, and p99 of response body size per HTTP method. The large gap between `GET` p50 (138 bytes) and p99 (3,678,129 bytes) reveals that while most GET responses are small, the top 1% are multi-megabyte payloads — exactly the kind of tail behaviour that `avg` would understate.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| p50(bytes) as p50_bytes, p90(bytes) as p90_bytes, p99(bytes) as p99_bytes by method
```

| method | p50_bytes | p90_bytes | p99_bytes |
| --- | --- | --- | --- |
| GET | 138 | 3,026 | 3,678,129 |
| POST | 2 | 2 | 2 |
| OPTIONS | 69 | 124 | 137 |

Expected output

|     |     |
| --- | --- |
|  | Use `p95` or `p99` for SLO threshold checks and `p50` (the median) for a robust measure of typical behaviour that is not skewed by outliers. A large gap between `p50` and `p99` indicates a long-tail distribution — common for endpoints that serve both small API responses and large file downloads. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically. |

## stddev

Computes the standard deviation of a numeric field across matched log lines. `stddev` measures how spread out values are around the mean — a low value indicates that most log lines have similar values, while a high value indicates erratic or inconsistent behaviour. Null or missing field values are ignored.

### Syntax

```none
| stddev(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to compute the standard deviation of. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_stddev`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and measure the consistency of response body sizes per HTTP method. A very high standard deviation for `GET` (1,073,687 bytes) reflects that GET responses range from zero-byte 304 responses to multi-megabyte payloads. The low standard deviation for `POST` (1,894 bytes) shows that POST responses are consistently small.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| stddev(bytes) as stddev_bytes by method
```

| method | stddev_bytes |
| --- | --- |
| GET | 1,073,687.61 |
| POST | 1,894.84 |
| OPTIONS | 69.0 |

Expected output

|     |     |
| --- | --- |
|  | `stddev` is expressed in the same unit as the input field (bytes, in this case), making it easy to interpret alongside `avg`. A high `stddev` relative to `avg` signals a wide mix of response sizes — this is normal for `GET` endpoints that serve both small API responses and large file downloads. Prefer `stddev` over `stdvar` when you need a human-readable measure of spread. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically. |

## stdvar

Computes the variance of a numeric field across matched log lines. Variance is the square of the standard deviation and quantifies the spread of values around the mean. It is primarily used in statistical calculations that combine variance across groups; for human-readable interpretation, prefer `stddev`. Null or missing field values are ignored.

### Syntax

```none
| stdvar(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to compute the variance of. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_stdvar`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and compute the variance in response body size per HTTP method. The very high variance for `GET` (1,149,810,367,291 bytes²) versus `POST` (3,591,132 bytes²) reflects the wide range of payload sizes returned by GET requests compared to the consistently small POST responses.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| stdvar(bytes) as var_bytes by method
```

| method | var_bytes |
| --- | --- |
| GET | 1,149,810,367,291.90 |
| POST | 3,591,132.22 |
| OPTIONS | 4,761.0 |

Expected output

|     |     |
| --- | --- |
|  | Because variance squares the unit of the input field (bytes², in this case), the result is difficult to interpret directly. Use `stdvar` when you need to mathematically combine variance across groups (for example, pooled variance calculations). For dashboards and alerts where you want a spread figure in the original unit (bytes), use `stddev` instead. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically. |

## sum

Computes the total sum of a numeric field across matched log lines. The `sum` operator ignores null or missing values. Use it to measure cumulative load — for example, the total number of requests handled per HTTP method or the aggregate bytes transferred in a window.

### Syntax

```none
| sum(<field>) [as <alias>] [by <field1>, <field2>, ...]
```

### Parameters

| Parameter | Required | Description |
| --- | --- | --- |
| `<field>` | Required | The numeric field to sum. Null or missing values are ignored. |
| `as <alias>` | Optional | Renames the output column. Defaults to `_sum`. |
| `by <field1>, …​` | Optional | Groups results by one or more fields. Without `by`, returns a single aggregate row for all matched logs. |

### Example

Parse nginx access logs and compute the total bytes transferred per HTTP method. Summing response body sizes gives the total data volume served by each method during the query time window — useful for bandwidth analysis and capacity planning.

```none
source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| sum(bytes) as total_bytes by method
```

| method | total_bytes |
| --- | --- |
| GET | 7,913,715,573 |
| POST | 2,524,318,781 |
| OPTIONS | 138 |

Expected output

|     |     |
| --- | --- |
|  | `sum` is most meaningful for fields that represent quantities that accumulate, such as bytes transferred, processing time, or event counts. The values above are totals across all log lines in the selected time window. After extracting fields with `parse`, FuseQL coerces string values to numbers automatically.
