Transform operators

FuseQL transform operators reshape the result set produced by earlier pipeline stages: bucketing events by time, removing duplicates, pivoting columns, and shifting rows.

backshift

Returns the value of a numeric field from N rows back in a time-ordered result set. The default shift is 1 row. Use backshift to compute period-over-period deltas — subtract the shifted value from the current value to get the change between consecutive time buckets.

Syntax

| backshift <field> [, <n>] [as <alias>]
none

Parameters

Parameter Required Description

<field>

Required

The numeric field whose previous-row value is retrieved.

<n>

Optional

Number of rows to shift back. Defaults to 1. Use 2 to compare with two periods ago.

as <alias>

Optional

Output column name for the shifted value. Defaults to _backshift.

Example

Count nginx requests per 1-minute bucket, shift by 1 row to get the previous minute’s count, and compute the per-minute delta.

source="nginx"
| timeslice 1m
| count as requests by _timeslice
| backshift requests, 1 as prev_requests
| (requests - prev_requests) as delta
Expected output
_timeslice requests prev_requests delta

2026-06-27 18:53:00 UTC

61,441

(null)

(null)

2026-06-27 18:54:00 UTC

650,712

61,441

589,271

2026-06-27 18:55:00 UTC

474,040

650,712

-176,672

The first row always returns null for the shifted field because there is no preceding row. backshift operates on time-ordered rows produced by a preceding timeslice stage. It is designed for the Advanced Search time-series panel. Combine with smooth to smooth the delta series and reduce noise.

dedup

Removes duplicate log lines from the result set, retaining only one row per unique combination of the specified fields. Optionally, keep the first N duplicate rows per group instead of just one. Use dedup to collapse repeated log events — for example, keeping only the first occurrence of each unique error message per source.

Syntax

| dedup by <field1>[, <field2>, ...]
| dedup <n> by <field1>[, <field2>, ...]
none

Parameters

Parameter Required Description

by <field1>, …​

Required

One or more fields that define uniqueness. Rows with the same values for all listed fields are considered duplicates.

<n>

Optional

Number of rows to keep per unique group. Defaults to 1 (keep only the first occurrence).

Example

Parse nginx logs and keep only the first log line per unique combination of HTTP method and status code, eliminating repeated entries for the same method/status pair.

source="nginx"
| parse "* - - [*] \"* *\" * * *" as ip,date,method,url,status,bytes,rest
| dedup by method, status
Expected output (illustrative — one row per unique method+status pair)
method status url

GET

200

/index.html

POST

201

/api/users

GET

404

/missing.html

dedup operates in the Advanced Search log streaming view. When used in the tabular aggregation pipeline, prefer counting distinct combinations with count_unique or grouping with by. The default keeps 1 row per group; set <n> to keep the first N occurrences.

timeslice

Buckets each event’s timestamp into fixed-width time windows for use in time-series aggregation.

The bucket value is exposed as _timeslice (epoch milliseconds) and is the standard grouping field for log-based time-series aggregations.

Syntax
| timeslice <duration>

| timeslice <duration> as <alias>
none

<duration> accepts FuseQL duration literals such as 1m, 5m, 1h, 1d, 7d. When as <alias> is omitted, the bucket field is named _timeslice.

Examples

Count events per 5-minute bucket:

* | timeslice 5m | count by (_timeslice)
none

Group by an additional dimension to produce one series per source:

* | timeslice 1m | count by (_timeslice, source)
none

Use a custom alias for the bucket field:

* | timeslice 1m as bucket | count by (bucket, status_code)
none

To produce a time series, include _timeslice (or the alias) in the by clause. Without it, the aggregation collapses the entire query range into a single value:

* | count
* | timeslice 1m | count by (_timeslice)
none

The Window operators (accum, rollingstd, smooth, total) and Algorithmic operators all expect a timeslice-produced series upstream.

Bucket alignment

Buckets are anchored to the Unix epoch (1970-01-01 00:00:00 UTC), not to a calendar week, month, or year. Boundaries fall at every multiple of <duration> measured from the epoch:

  • Durations that evenly divide 24 hours (1m, 5m, 15m, 1h, 4h, 12h) align to UTC midnight, because UTC midnight is itself a multiple of the duration measured from the epoch.

  • Durations that do not divide 24 hours (7h, 13m) remain anchored to the epoch grid but their boundaries drift across the day — they do not land on UTC midnight.

  • Multi-day durations (2d, 7d, 10d) follow the epoch grid, not calendar boundaries. With timeslice 7d, week boundaries always fall on a Thursday because 1970-01-01 was a Thursday. With timeslice 10d, boundaries shift through the calendar and do not realign at the start of a month or year.

Multi-day buckets do not align to calendar weeks or months. For calendar-aligned buckets (for example, "Monday–Sunday" or "first of the month"), pre-process the timestamp with formatDate and group by the formatted value instead.

Bucket label

_timeslice is the bucket’s right (upper) edge, expressed as epoch milliseconds in UTC. An event with timestamp T lands in (B, B + <duration>] where B + <duration> is the smallest grid boundary ≥ T; that value is the label.

For example, with timeslice 5m and a query starting at 2026-05-04T12:03:00Z, bucket labels are 12:05, 12:10, 12:15, …​ An event at 12:07:42Z falls in (12:05, 12:10] and is labeled 12:10.

A small number of operators upstream of timeslice (json multi, parse multi, dedup, cat, backshift, compose) cause the engine to emit the bucket’s left edge instead. Plain aggregation queries use the right-edge labeling described here.

Bucket labels can fall in the future

Because the label is the right edge, the in-progress bucket is labeled slightly after the events it contains. For minute-scale buckets the offset is negligible; for hour- or day-scale buckets it can be significant, and the label can sit later than the current wall-clock time.

Example with timeslice 7d. If today is Friday 2026-05-01 and you ingest logs with current timestamps, the most recent 7-day boundary is Thursday 2026-04-30 00:00 UTC, so today’s logs land in (2026-04-30 00:00, 2026-05-07 00:00] — labeled 2026-05-07 00:00 UTC, six days in the future.

The same applies to any large <duration> (12h, 1d, 7d, 30d, …​): the in-progress bucket’s label leads its latest event by up to <duration>. Practical consequences:

  • Dashboards rendering _timeslice on the x-axis plot the in-progress bucket at a future timestamp.

  • To recover the bucket start, subtract the duration: _timeslice - <duration_ms>.

  • The in-progress bucket is partial; subsequent queries can change its value as more data arrives.

transpose

Converts aggregate query results from a long format into a wide tabular format by pivoting row values into column headers. Similar to a pivot table, transpose transforms a long list of rows into a wide table, making it easier to compare values across dimensions — for example, displaying request counts per status code as separate columns across time.

transpose dynamically creates columns for aggregate search results, allowing you to design queries without knowing the output schema in advance. This is particularly useful for formatting data for dashboard panels and charts.

Syntax

| transpose row <row_field1>[, <row_field2>, ...] column <column_field1>[, <column_field2>, ...]
none

Parameters

Parameter Required Description

row <row_field1>, …​

Required

One or more fields whose values become the row labels in the output table.

column <column_field1>, …​

Required

One or more fields whose unique values become column headers in the output table.

Example

Without transpose, the following query produces a long-format table that is difficult to read:

source="nginx"
| timeslice 5m
| count by _timeslice, status

With transpose, pivot the results to display status codes as columns and timeslices as rows:

source="nginx"
| timeslice 5m
| count by _timeslice, status
| transpose row _timeslice column status
Expected output (illustrative — columns created dynamically from status values)
_timeslice 200 404 500

2026-06-27 18:50:00 UTC

412,840

1,203

87

2026-06-27 18:55:00 UTC

389,120

987

62

transpose supports multiple aggregate functions. When using multiple aggregates (such as count and avg), each combination of column values generates separate columns for each aggregate — for example, _count|200, avg_response|200, _count|404, avg_response|404. The number of output columns equals the number of unique values in the column field(s).