# TS.MRANGE

```json metadata
{
  "schema_version": 2,
  "title": "TS.MRANGE",
  "description": "Query a range across multiple time series by filters in forward direction",
  "categories": ["docs","develop","stack","oss","rs","rc","oss","kubernetes","clients"],
  "arguments": [{"name":"fromTimestamp","type":"string"},{"name":"toTimestamp","type":"string"},{"name":"LATEST","optional":true,"since":"1.8.0","type":"string"},{"multiple":true,"name":"Timestamp","optional":true,"token":"FILTER_BY_TS","type":"integer"},{"arguments":[{"name":"FILTER_BY_VALUE","token":"FILTER_BY_VALUE","type":"pure-token"},{"name":"min","type":"double"},{"name":"max","type":"double"}],"name":"fbv","optional":true,"type":"block"},{"arguments":[{"name":"WITHLABELS","token":"WITHLABELS","type":"pure-token"},{"arguments":[{"name":"SELECTED_LABELS","token":"SELECTED_LABELS","type":"pure-token"},{"multiple":true,"name":"label1","type":"string"}],"name":"SELECTED_LABELS_BLOCK","type":"block"}],"name":"labels","optional":true,"type":"oneof"},{"name":"count","optional":true,"token":"COUNT","type":"integer"},{"arguments":[{"name":"value","optional":true,"token":"ALIGN","type":"integer"},{"name":"aggregationType","token":"AGGREGATION","type":"string"},{"name":"bucketDuration","type":"integer"},{"name":"buckettimestamp","optional":true,"since":"1.8.0","token":"BUCKETTIMESTAMP","type":"pure-token"},{"name":"empty","optional":true,"since":"1.8.0","token":"EMPTY","type":"pure-token"}],"name":"aggregation","optional":true,"type":"block"},{"arguments":[{"name":"l=v","type":"string"},{"name":"l!=v","type":"string"},{"name":"l=","type":"string"},{"name":"l!=","type":"string"},{"name":"l=(v1,v2,...)","type":"string"},{"name":"l!=(v1,v2,...)","type":"string"}],"multiple":true,"name":"filterExpr","token":"FILTER","type":"oneof"},{"arguments":[{"name":"GROUPBY","token":"GROUPBY","type":"pure-token"},{"name":"label","type":"string"},{"name":"REDUCE","type":"string"},{"name":"reducer","type":"string"}],"name":"groupby","optional":true,"type":"block"},{"name":"EXCLUDEEMPTY","optional":true,"since":"8.10.0","token":"EXCLUDEEMPTY","type":"pure-token"}],
  "syntax_fmt": "TS.MRANGE fromTimestamp toTimestamp [LATEST] [FILTER_BY_TS Timestamp\n  [Timestamp ...]] [FILTER_BY_VALUE min max] [WITHLABELS |\n  SELECTED_LABELS label1 [label1 ...]] [COUNT count] [[ALIGN value]\n  AGGREGATION aggregators bucketDuration [BUCKETTIMESTAMP]\n  [EMPTY]] FILTER \u003cl=v | l!=v | l= | l!= | l=(v1,v2,...) |\n  l!=(v1,v2,...) [l=v | l!=v | l= | l!= | l=(v1,v2,...) |\n  l!=(v1,v2,...) ...]\u003e [GROUPBY label REDUCE reducer] [EXCLUDEEMPTY]",
  "complexity": "O(n/m+k) where n = Number of data points, m = Chunk size (data points per chunk), k = Number of data points that are in the requested ranges",
  "group": "timeseries",
  "acl_categories": ["@timeseries","@read","@slow"],
  "since": "1.0.0",
  "tableOfContents": {"sections":[{"id":"required-arguments","title":"Required arguments"},{"id":"optional-arguments","title":"Optional arguments"},{"id":"examples","title":"Examples"},{"id":"redis-software-and-redis-cloud-compatibility","title":"Redis Software and Redis Cloud compatibility"},{"id":"return-information","title":"Return information"},{"id":"see-also","title":"See also"},{"id":"related-topics","title":"Related topics"}]}

,
  "codeExamples": []
}
```

> [!NOTE]
> This command's behavior varies in clustered Redis environments. See the [multi-key operations](https://redis.io/docs/latest/develop/using-commands/multi-key-operations) page for more information.



Query a range across multiple time series by filters in the forward direction. Starting from Redis 8.6, NaN values are included in raw measurement reports (queries without aggregation).

> [!NOTE]
> This command will reply only if the current user has read access to all keys that match the filter.
> Otherwise, it will reply with "*(error): current user doesn't have read permission to one or more keys that match the specified filter*".

[Examples](#examples)

## Required arguments

<details open>
<summary><code>fromTimestamp</code></summary>

is the start timestamp for the range query (integer Unix timestamp in milliseconds) or `-` to denote the timestamp of the earliest sample among all the time series that passes `FILTER filterExpr...`.
</details>

<details open>
<summary><code>toTimestamp</code></summary>

is the end timestamp for the range query (integer Unix timestamp in milliseconds) or `+` to denote the timestamp of the latest sample among all the time series that passes `FILTER filterExpr...`.
</details>

<details open>
<summary><code>FILTER filterExpr...</code></summary>

filters time series based on their labels and label values.

Each filter expression has one of the following syntaxes:

  - `label!=` - the time series has a label named `label`
  - `label=value` - the time series has a label named `label` with a value equal to `value`
  - `label=(value1,value2,...)` - the time series has a label named `label` with a value equal to one of the values in the list
  - `label=` - the time series does not have a label named `label`
  - `label!=value` - the time series does not have a label named `label` with a value equal to `value`
  - `label!=(value1,value2,...)` - the time series does not have a label named `label` with a value equal to any of the values in the list

  <note><b>Notes:</b>
   - At least one filter expression with a syntax `label=value` or `label=(value1,value2,...)` is required.
   - Filter expressions are conjunctive. For example, the filter `type=temperature room=study` means that `type=temperature` AND `room=study`.
   - Whitespace is not allowed in a filter expression except between quotes or double quotes in values - for example, `x="y y"` or `x='(y y,z z)'`.
   </note>

</details>

## Optional arguments

<details open>
<summary><code>LATEST</code> (since RedisTimeSeries 1.8)</summary>

is used when a time series is a compaction. With `LATEST`, TS.MRANGE also reports the compacted value of the latest (possibly partial) bucket, given that this bucket's start time falls within `[fromTimestamp, toTimestamp]`. Without `LATEST`, TS.MRANGE does not report the latest (possibly partial) bucket. When a time series is not a compaction, `LATEST` is ignored.

The data in the latest bucket of a compaction is possibly partial. A bucket is _closed_ and compacted only upon the arrival of a new sample that _opens_ a new _latest_ bucket. There are cases, however, when the compacted value of the latest (possibly partial) bucket is also required. In such a case, use `LATEST`.
</details>

<details open>
<summary><code>FILTER_BY_TS ts...</code> (since RedisTimeSeries 1.6)</summary>

filters samples by a list of specific timestamps. A sample passes the filter if its exact timestamp is specified and falls within `[fromTimestamp, toTimestamp]`.

When used together with `AGGREGATION`: samples are filtered before being aggregated.
</details>

<details open>
<summary><code>FILTER_BY_VALUE min max</code> (since RedisTimeSeries 1.6)</summary>

filters samples by minimum and maximum values. `min` and `max` cannot be NaN values.

When used together with `AGGREGATION`: samples are filtered before being aggregated.
</details>

<details open>
<summary><code>WITHLABELS</code></summary>

includes in the reply all label-value pairs representing metadata labels of the time series. 
If `WITHLABELS` or `SELECTED_LABELS` are not specified, by default, an empty list is reported as label-value pairs.
</details>

<details open>
<summary><code>SELECTED_LABELS label...</code> (since RedisTimeSeries 1.6)</summary>

returns a subset of the label-value pairs that represent metadata labels of the time series. 
Use when a large number of labels exists per series, but only the values of some of the labels are required. 
If `WITHLABELS` or `SELECTED_LABELS` are not specified, by default, an empty list is reported as label-value pairs.
</details>

<details open>
<summary><code>COUNT count</code></summary>

When used without `AGGREGATION`: limits the number of reported samples per time series.

When used together with `AGGREGATION`: limits the number of reported buckets.
</details>

<details open>
<summary><code>ALIGN align</code> (since RedisTimeSeries 1.6)</summary>

is a time bucket alignment control for `AGGREGATION`. It controls the time bucket timestamps by changing the reference timestamp on which a bucket is defined. 

Values include:
   
 - `start` or `-`: The reference timestamp will be the query start interval time (`fromTimestamp`) which can't be `-`
 - `end` or `+`: The reference timestamp will be the query end interval time (`toTimestamp`) which can't be `+`
 - A specific timestamp: align the reference timestamp to a specific time
   
<note><b>Note:</b> When not provided, alignment is set to `0`.</note>
</details>

<details open>
<summary><code>AGGREGATION aggregators bucketDuration</code></summary>

for each time series, aggregates samples into time buckets, where:

  - `aggregators` is one or more comma-separated aggregators from the following table:

    | aggregator   | Description                                                     |
    | ------------ | --------------------------------------------------------------- |
    | `avg`        | Arithmetic mean of all non-NaN values                           |
    | `sum`        | Sum of all non-NaN values                                       |
    | `min`        | Minimum non-NaN value                                           |
    | `max`        | Maximum non-NaN value                                           |
    | `range`      | Difference between the maximum and the minimum non-NaN values   |
    | `count`      | Number of non-NaN values                                        |
    | `countNaN`   | Number of NaN values (since Redis 8.6)                          |
    | `countAll`   | Number of values, including NaN and non-NaN (since Redis 8.6)   |
    | `first`      | The non-NaN value with the lowest timestamp in the bucket       |
    | `last`       | The non-NaN value with the highest timestamp in the bucket      |
    | `std.p`      | Population standard deviation of the non-NaN values             |
    | `std.s`      | Sample standard deviation of the non-NaN values                 |
    | `var.p`      | Population variance of the non-NaN values                       |
    | `var.s`      | Sample variance of the non-NaN values                           |
    | `twa`        | Time-weighted average over the bucket's timeframe (ignores NaN values) (since RedisTimeSeries 1.8) |

  - `bucketDuration` is duration of each bucket, in milliseconds.

  Note: `aggregators` can either be a single aggregator or multiple aggregators separated by commas as shown below.
  No whitespace is allowed between aggregators.

  AGGREGATION min,avg,max
  
  Without `ALIGN`, bucket start times are multiples of `bucketDuration`.
  
  With `ALIGN align`, bucket start times are multiples of `bucketDuration` with remainder `align % bucketDuration`.
  
  The first bucket start time is less than or equal to `fromTimestamp`.  
</details>

<details open>
<summary><code>[BUCKETTIMESTAMP bt]</code> (since RedisTimeSeries 1.8)</summary>

controls how bucket timestamps are reported.

| `bt`             | Timestamp reported for each bucket                            |
| ---------------- | ------------------------------------------------------------- |
| `-` or `start`   | the bucket's start time (default)                             |
| `+` or `end`     | the bucket's end time                                         |
| `~` or `mid`     | the bucket's mid time (rounded down if not an integer)        |
</details>

<details open>
<summary><code>[EMPTY]</code> (since RedisTimeSeries 1.8)</summary>

is a flag, which, when specified, reports aggregations also for empty buckets.

| aggregator           | Value reported for each empty bucket |
| -------------------- | ------------------------------------ |
| `sum`, `count`       | `0`                                  |
| `last`               | The value of the last sample before the bucket's start. `NaN` when no such sample. |
| `twa`                | Average value over the bucket's timeframe based on linear interpolation of the last sample before the bucket's start and the first sample after the bucket's end. `NaN` when no such samples. |
| `min`, `max`, `range`, `avg`, `first`, `std.p`, `std.s` | `NaN` |

Regardless of the values of `fromTimestamp` and `toTimestamp`, no data is reported for buckets that end before the earliest sample or begin after the latest sample in the time series.
</details>

<details open>
<summary><code>GROUPBY label REDUCE reducer</code> (since RedisTimeSeries 1.6)</summary>

splits time series into groups, each group contains time series that share the same value for the provided label name, then aggregates results in each group.

When combined with `AGGREGATION` the `GROUPBY`/`REDUCE` is applied post aggregation stage.
`GROUPBY`/`REDUCE` is not permitted when multiple aggregators are specified.

  - `label` is label name. A group is created for all time series that share the same value for this label.

  - `reducer` is an aggregation type used to aggregate the results in each group.

    | `reducer` | Description                                                                                                      |
    | --------- | ---------------------------------------------------------------------------------------------------------------- |
    | `avg`     | Arithmetic mean of all non-NaN values; NaN if no such values (since RedisTimeSeries 1.8)                         |
    | `sum`     | Sum of all non-NaN values; NaN if no such values                                                                 |
    | `min`     | Minimum non-NaN value; NaN if no such values                                                                     |
    | `max`     | Maximum non-NaN value; NaN if no such values                                                                     |
    | `range`   | Difference between the maximum and the minimum non-NaN values; NaN if no such values (since RedisTimeSeries 1.8) |
    | `count`   | Number of non-NaN values (since RedisTimeSeries 1.8)                                                             |
    | `countNaN`| Number of NaN values (since Redis 8.6)                                                                           |
    | `countAll`| Number of values, including NaN and non-NaN (since Redis 8.6)                                                     |
    | `std.p`   | Population standard deviation of the non-NaN values; NaN if no such values (since RedisTimeSeries 1.8)           |
    | `std.s`   | Sample standard deviation of thel non-NaN values; NaN if no such values (since RedisTimeSeries 1.8)              |
    | `var.p`   | Population variance of the non-NaN values; NaN if no such values (since RedisTimeSeries 1.8)                     |
    | `var.s`   | Sample variance of the non-NaN values; NaN if no such values (since RedisTimeSeries 1.8)                         |

<note><b>Notes:</b> 
  - The produced time series is named `<label>=<value>`
  - The produced time series contains two labels with these label array structures:
    - `__reducer__`, the reducer used (e.g., `"count"`)
    - `__source__`, the list of time series keys used to compute the grouped series (e.g., `"key1,key2,key3"`)
</note>

</details>

<details open>
<summary><code>EXCLUDEEMPTY</code> (since Redis 8.10)</summary>

excludes from the reply any time series that has no samples in the requested range. By default, every time series that passes `FILTER filterExpr...` is reported, even those with no samples in the range (reported with an empty samples list).

A time series whose only samples in the range are NaN is not considered empty and is still reported.

`EXCLUDEEMPTY` cannot be used together with `GROUPBY label REDUCE reducer`; combining them replies with an error.
</details>

<note><b>Note:</b> An `MRANGE` command cannot be part of a transaction when running on a Redis cluster.</note>

## Examples

<details open>
<summary><b>Retrieve maximum stock price per timestamp</b></summary>

Create two stocks and add their prices at three different timestamps.


127.0.0.1:6379> TS.CREATE stock:A LABELS type stock name A
OK
127.0.0.1:6379> TS.CREATE stock:B LABELS type stock name B
OK
127.0.0.1:6379> TS.MADD stock:A 1000 100 stock:A 1010 110 stock:A 1020 120
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
127.0.0.1:6379> TS.MADD stock:B 1000 120 stock:B 1010 110 stock:B 1020 100
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020


You can now retrieve the maximum stock price per timestamp.


127.0.0.1:6379> TS.MRANGE - + WITHLABELS FILTER type=stock GROUPBY type REDUCE max
1) 1) "type=stock"
   2) 1) 1) "type"
         2) "stock"
      2) 1) "__reducer__"
         2) "max"
      3) 1) "__source__"
         2) "stock:A,stock:B"
   3) 1) 1) (integer) 1000
         2) 120
      2) 1) (integer) 1010
         2) 110
      3) 1) (integer) 1020
         2) 120


The `FILTER type=stock` clause returns a single time series representing stock prices. The `GROUPBY type REDUCE max` clause splits the time series into groups with identical type values, and then, for each timestamp, aggregates all series that share the same type value using the max aggregator.
</details>

<details open>
<summary><b>Calculate average stock price and retrieve maximum average</b></summary> 

Create two stocks and add their prices at nine different timestamps.


127.0.0.1:6379> TS.CREATE stock:A LABELS type stock name A
OK
127.0.0.1:6379> TS.CREATE stock:B LABELS type stock name B
OK
127.0.0.1:6379> TS.MADD stock:A 1000 100 stock:A 1010 110 stock:A 1020 120
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
127.0.0.1:6379> TS.MADD stock:B 1000 120 stock:B 1010 110 stock:B 1020 100
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
127.0.0.1:6379> TS.MADD stock:A 2000 200 stock:A 2010 210 stock:A 2020 220
1) (integer) 2000
2) (integer) 2010
3) (integer) 2020
127.0.0.1:6379> TS.MADD stock:B 2000 220 stock:B 2010 210 stock:B 2020 200
1) (integer) 2000
2) (integer) 2010
3) (integer) 2020
127.0.0.1:6379> TS.MADD stock:A 3000 300 stock:A 3010 310 stock:A 3020 320
1) (integer) 3000
2) (integer) 3010
3) (integer) 3020
127.0.0.1:6379> TS.MADD stock:B 3000 320 stock:B 3010 310 stock:B 3020 300
1) (integer) 3000
2) (integer) 3010
3) (integer) 3020


Now, for each stock, calculate the average stock price per a 1000-millisecond timeframe, and then retrieve the stock with the maximum average for that timeframe.


127.0.0.1:6379> TS.MRANGE - + WITHLABELS AGGREGATION avg 1000 FILTER type=stock GROUPBY type REDUCE max
1) 1) "type=stock"
   2) 1) 1) "type"
         2) "stock"
      2) 1) "__reducer__"
         2) "max"
      3) 1) "__source__"
         2) "stock:A,stock:B"
   3) 1) 1) (integer) 1000
         2) 110
      2) 1) (integer) 2000
         2) 210
      3) 1) (integer) 3000
         2) 310

</details>

<details open>
<summary><b>Group query results</b></summary>

Query all time series with the metric label equal to `cpu`, then group the time series by the value of their `metric_name` label value and for each group return the maximum value and the time series keys (_source_) with that value.


127.0.0.1:6379> TS.ADD ts1 1548149180000 90 labels metric cpu metric_name system
(integer) 1548149180000
127.0.0.1:6379> TS.ADD ts1 1548149185000 45
(integer) 1548149185000
127.0.0.1:6379> TS.ADD ts2 1548149180000 99 labels metric cpu metric_name user
(integer) 1548149180000
127.0.0.1:6379> TS.MRANGE - + WITHLABELS FILTER metric=cpu GROUPBY metric_name REDUCE max
1) 1) "metric_name=system"
   2) 1) 1) "metric_name"
         2) "system"
      2) 1) "__reducer__"
         2) "max"
      3) 1) "__source__"
         2) "ts1"
   3) 1) 1) (integer) 1548149180000
         2) 90
      2) 1) (integer) 1548149185000
         2) 45
2) 1) "metric_name=user"
   2) 1) 1) "metric_name"
         2) "user"
      2) 1) "__reducer__"
         2) "max"
      3) 1) "__source__"
         2) "ts2"
   3) 1) 1) (integer) 1548149180000
         2) 99

</details>

<details open>
<summary><b>Filter query by value</b></summary>

Query all time series with the metric label equal to `cpu`, then filter values larger or equal to 90.0 and smaller or equal to 100.0.


127.0.0.1:6379> TS.ADD ts1 1548149180000 90 labels metric cpu metric_name system
(integer) 1548149180000
127.0.0.1:6379> TS.ADD ts1 1548149185000 45
(integer) 1548149185000
127.0.0.1:6379> TS.ADD ts2 1548149180000 99 labels metric cpu metric_name user
(integer) 1548149180000
127.0.0.1:6379> TS.MRANGE - + FILTER_BY_VALUE 90 100 WITHLABELS FILTER metric=cpu
1) 1) "ts1"
   2) 1) 1) "metric"
         2) "cpu"
      2) 1) "metric_name"
         2) "system"
   3) 1) 1) (integer) 1548149180000
         2) 90
2) 1) "ts2"
   2) 1) 1) "metric"
         2) "cpu"
      2) 1) "metric_name"
         2) "user"
   3) 1) 1) (integer) 1548149180000
         2) 99

</details>

<details open>
<summary><b>Query using a label</b></summary>

Query all time series with the metric label equal to `cpu`, but only return the team label.


127.0.0.1:6379> TS.ADD ts1 1548149180000 90 labels metric cpu metric_name system team NY
(integer) 1548149180000
127.0.0.1:6379> TS.ADD ts1 1548149185000 45
(integer) 1548149185000
127.0.0.1:6379> TS.ADD ts2 1548149180000 99 labels metric cpu metric_name user team SF
(integer) 1548149180000
127.0.0.1:6379> TS.MRANGE - + SELECTED_LABELS team FILTER metric=cpu
1) 1) "ts1"
   2) 1) 1) "team"
         2) "NY"
   3) 1) 1) (integer) 1548149180000
         2) 90
      2) 1) (integer) 1548149185000
         2) 45
2) 1) "ts2"
   2) 1) 1) "team"
         2) "SF"
   3) 1) 1) (integer) 1548149180000
         2) 99

</details>

<details open>
<summary><b>Exclude empty time series from the reply</b></summary>

Create three time series that share the label `s=1`, then add samples so that only two of them have data in the `- 500` range.


127.0.0.1:6379> TS.CREATE s LABELS s 1 t 1
OK
127.0.0.1:6379> TS.CREATE t LABELS s 1 t 1
OK
127.0.0.1:6379> TS.CREATE u LABELS s 1 t 1
OK
127.0.0.1:6379> TS.MADD s 100 100 t 100 100 s 200 200 t 300 300 s 400 400 t 400 400 u 2000 2000
1) (integer) 100
2) (integer) 100
3) (integer) 200
4) (integer) 300
5) (integer) 400
6) (integer) 400
7) (integer) 2000


Query the `- 500` range with `EXCLUDEEMPTY`. Time series `u`, whose only sample is at timestamp `2000`, has no samples in the range and is omitted from the reply.


127.0.0.1:6379> TS.MRANGE - 500 WITHLABELS EXCLUDEEMPTY FILTER s=1
1) 1) "s"
   2) 1) 1) "s"
         2) "1"
      2) 1) "t"
         2) "1"
   3) 1) 1) (integer) 100
         2) 100
      2) 1) (integer) 200
         2) 200
      3) 1) (integer) 400
         2) 400
2) 1) "t"
   2) 1) 1) "s"
         2) "1"
      2) 1) "t"
         2) "1"
   3) 1) 1) (integer) 100
         2) 100
      2) 1) (integer) 300
         2) 300
      3) 1) (integer) 400
         2) 400


Without `EXCLUDEEMPTY`, `u` is also reported, with an empty samples list.


127.0.0.1:6379> TS.MRANGE - 500 WITHLABELS FILTER s=1
1) 1) "s"
   ...
2) 1) "t"
   ...
3) 1) "u"
   2) 1) 1) "s"
         2) "1"
      2) 1) "t"
         2) "1"
   3) (empty array)

</details>

## Redis Software and Redis Cloud compatibility

| Redis<br />Software | Redis<br />Cloud | <span style="min-width: 9em; display: table-cell">Notes</span> |
|:----------------------|:-----------------|:------|
| <span title="Supported">&#x2705; Supported</span><br /> | <span title="Supported">&#x2705; Flexible & Annual</span><br /><span title="Supported">&#x2705; Free & Fixed</nobr></span> |  |

## Return information

**RESP2:**

If `GROUPBY label REDUCE reducer` is not specified:

[Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings): The time series key name
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): label-value pairs ([Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings), [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings))
  - By default, an empty array is reported
  - If `WITHLABELS` is specified, all labels associated with this time series are reported
  - If `SELECTED_LABELS label...` is specified, the selected labels are reported (null value when no such label defined)
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): representing all samples/aggregations matching the range:
  - Without `AGGREGATION` or with a single aggregator:
    timestamp-value pairs ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), [Simple string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-strings)) representing (timestamp, value)
  - With multiple aggregators:
    timestamp-value tuples ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), multiple [Simple string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-strings)) representing (timestamp, value...)

If `GROUPBY label REDUCE reducer` is specified:

[Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): for each group of time series matching the specified filters, the following is reported:
- [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings) with the format `label=value` where `label` is the `GROUPBY` label argument
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): label-value pairs ([Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings), [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings)):
  - By default, an empty array is reported
  - If `WITHLABELS` is specified, the `GROUPBY` label argument and value are reported
  - If `SELECTED_LABELS label...` is specified, the selected labels are reported (null value when no such label defined or label does not have the same value for all grouped time series)
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): either a single pair ([Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings), [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings)): the `GROUPBY` label argument and value, or empty array
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): a single pair ([Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings), [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings)):  the string `__reducer__` and the reducer argument
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): a single pair ([Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings), [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings)): the string `__source__` and the time series key names separated by ","
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): representing all samples/aggregations matching the range:
  - Without `AGGREGATION` or with a single aggregator:
    timestamp-value pairs ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), [Simple string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-strings)) representing (timestamp, value)
  - With multiple aggregators:
    timestamp-value tuples ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), multiple [Simple string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-strings)) representing (timestamp, value...)

**RESP3:**

If `GROUPBY label REDUCE reducer` is not specified:

[Map reply](https://redis.io/docs/latest/develop/reference/protocol-spec#maps): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#bulk-strings): The time series key name
- [Map reply](https://redis.io/docs/latest/develop/reference/protocol-spec#maps) or [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): label-value pairs
  - By default, an empty map is reported
  - If `WITHLABELS` is specified, all labels associated with this time series are reported as a map
  - If `SELECTED_LABELS label...` is specified, the selected labels are reported as a map (null value when no such label defined)
- Additional metadata including aggregators information
- [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays): representing all samples/aggregations matching the range:
  - Without `AGGREGATION` or with a single aggregator:
    timestamp-value pairs ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), [Double reply](https://redis.io/docs/latest/develop/reference/protocol-spec#doubles)) representing (timestamp, value)
  - With multiple aggregators:
    timestamp-value tuples ([Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers), multiple [Double reply](https://redis.io/docs/latest/develop/reference/protocol-spec#doubles)) representing (timestamp, value...)

If `GROUPBY label REDUCE reducer` is specified:

Similar structure as RESP2 but with map-based organization for labels and metadata, and double values instead of string values.



## See also

[`TS.RANGE`](https://redis.io/docs/latest/commands/ts.range) | [`TS.MREVRANGE`](https://redis.io/docs/latest/commands/ts.mrevrange) | [`TS.REVRANGE`](https://redis.io/docs/latest/commands/ts.revrange)

## Related topics

[RedisTimeSeries](https://redis.io/docs/latest/develop/data-types/timeseries)

