title: TS.ADD

summary: Append a sample to a time series

title: Overview

text:
Append a sample to a time series

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

timestamp

is Unix time (integer, in milliseconds) specifying the sample timestamp or `*` to set the sample timestamp to the Unix time of the server's clock.

Unix time is the number of milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970, the Unix epoch, without adjustments made due to leap seconds.

value

is (double) numeric data value of the sample. The double number should follow [RFC 7159](https://tools.ietf.org/html/rfc7159) (JSON standard). In particular, the parser rejects overly large values that do not fit in binary64. It does not accept infinite values. NaN (Not a Number) values are supported starting from Redis 8.6.

<note>Notes:
- When specified key does not exist, a new time series is created.

  if a [COMPACTION_POLICY]() configuration parameter is defined, compacted time series would be created as well.

- If `timestamp` is older than the retention period compared to the maximum existing timestamp, the sample is discarded and an error is returned.
- When adding a sample to a time series for which compaction rules are defined:
  - If all the original samples for an affected aggregated time bucket are available, the compacted value is recalculated based on the reported sample and the original samples.
  - If only a part of the original samples for an affected aggregated time bucket is available due to trimming caused in accordance with the time series RETENTION policy, the compacted value is recalculated based on the reported sample and the available original samples.
  - If the original samples for an affected aggregated time bucket are not available due to trimming caused in accordance with the time series RETENTION policy, the compacted value bucket is not updated.
- Explicitly adding samples to a compacted time series (using `TS.ADD`, [`TS.MADD`](), [`TS.INCRBY`](), or [`TS.DECRBY`]()) may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples.
</note>

title: Optional arguments

text:
The following arguments are optional because they can be set by [`TS.CREATE`]().

RETENTION retentionPeriod

is maximum retention period, compared to the maximum existing timestamp, in milliseconds.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `RETENTION` in [`TS.CREATE`]().

ENCODING enc

specifies the series sample encoding format.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `ENCODING` in [`TS.CREATE`]().

CHUNK_SIZE size

is memory size, in bytes, allocated for each data chunk.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `CHUNK_SIZE` in [`TS.CREATE`]().

DUPLICATE_POLICY policy

is policy for handling insertion ([`TS.ADD`]() and [`TS.MADD`]()) of multiple samples with identical timestamps.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `DUPLICATE_POLICY` in [`TS.CREATE`]().

ON_DUPLICATE policy_ovr

is overwrite key and database configuration for [DUPLICATE_POLICY](), the policy for handling samples with identical timestamps.
This override is effective only for this single command and does not set the time series duplication policy (which can be set with [`TS.ALTER`]()).

`policy_ovr` can be one of the following values:
  - `BLOCK`: ignore any newly reported value and reply with an error
  - `FIRST`: ignore any newly reported value
  - `LAST`: override with the newly reported value
  - `MIN`: only override if the value is lower than the existing value
  - `MAX`: only override if the value is higher than the existing value
  - `SUM`: If a previous sample exists, add the new sample to it so that the updated value is set to (previous + new). If no previous sample exists, the value is set to the new value.

<note>NaN Handling (Redis 8.6+): When using `MIN`, `MAX`, or `SUM` policies, an error is returned if there is an existing value in the specified timestamp and either the previous or the new value (but not both) are NaN.</note>

This argument has no effect when a new time series is created by this command.

IGNORE ignoreMaxTimediff ignoreMaxValDiff

is the policy for handling duplicate samples. A new sample is considered a duplicate and is ignored if the following conditions are met:

  - The time series is not a compaction;
  - The time series' `DUPLICATE_POLICY` IS `LAST`;
  - The sample is added in-order (`timestamp ≥ max_timestamp`);
  - The difference of the current timestamp from the previous timestamp (`timestamp - max_timestamp`) is less than or equal to `IGNORE_MAX_TIME_DIFF`;
  - The absolute value difference of the current value from the value at the previous maximum timestamp (`abs(value - value_at_max_timestamp`) is less than or equal to `IGNORE_MAX_VAL_DIFF`.

where `max_timestamp` is the timestamp of the sample with the largest timestamp in the time series, and `value_at_max_timestamp` is the value at `max_timestamp`.

When not specified: set to the global [IGNORE_MAX_TIME_DIFF]() and [IGNORE_MAX_VAL_DIFF](), which are, by default, both set to 0.

These parameters are used when creating a new time series to set the per-key parameters, and are ignored when called with an existing time series (the existing per-key configuration parameters is used).

<note>NaN Handling (Redis 8.6+): NaN values are never regarded as duplicates when using the `IGNORE` parameters.</note>

LABELS {label value}...

is set of label-value pairs that represent metadata labels of the time series.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `LABELS` in [`TS.CREATE`]().

<note>Notes:
- You can use this command to create a new time series and add data to it in a single command.
  `RETENTION`, `ENCODING`, `CHUNK_SIZE`, `DUPLICATE_POLICY`, `IGNORE`, and `LABELS` are used only when creating a new time series, and ignored when adding samples to an existing time series.
- Setting `RETENTION` and `LABELS` introduces additional time complexity.
</note>

title: Complexity

role: performance

text:
If a compaction rule exists on a time series, the performance of `TS.ADD` can be reduced.
The complexity of `TS.ADD` is always `O(M)`, where `M` is the number of compaction rules or `O(1)` with no compaction.

title: Examples

text:
Append a sample to a temperature time series

Create a temperature time series, set its retention to 1 year, and append a sample.

127.0.0.1:6379> TS.ADD temperature:3:11 1548149183000 27 RETENTION 31536000000
(integer) 1548149183000

<note>Note: If a time series with such a name already exists, the sample is added, but the retention does not change.</note>

Add a sample to the time series, setting the sample's timestamp to the current Unix time of the server's clock.

127.0.0.1:6379> TS.ADD temperature:3:11 * 30
(integer) 1662042954573

text:
| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Supported
 | <nobr>✅ Flexible & Annual</nobr>
✅ Free & Fixed</nobr> |  |

title: Return information

text:
**RESP2:**

One of the following:
* [Integer reply](): the timestamp of the upserted sample. If the sample is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, duplication policy is `BLOCK`, or when `timestamp` is older than the retention period compared to the maximum existing timestamp.

**RESP3:**

One of the following:
* [Integer reply](): the timestamp of the upserted sample. If the sample is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, duplication policy is `BLOCK`, or when `timestamp` is older than the retention period compared to the maximum existing timestamp.

title: See also

text: [`TS.CREATE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.ALTER

page_type: content

title: Overview

role: overview

text:
Update the retention, chunk size, duplicate policy, and labels of an existing time series

[Examples](#examples)

title: Required arguments

role: content

text:
key

is key name for the time series.

<note>Note: This command alters only the specified element. For example, if you specify only `RETENTION` and `LABELS`, the chunk size and the duplicate policy are not altered. </note>

title: Optional arguments

role: parameters

text:
RETENTION retentionPeriod

is maximum retention period, compared to the maximum existing timestamp, in milliseconds. See `RETENTION` in [`TS.CREATE`]().

CHUNK_SIZE size

is the initial allocation size, in bytes, for the data part of each new chunk. Actual chunks may consume more memory. See `CHUNK_SIZE` in [`TS.CREATE`](). Changing this value does not affect existing chunks.

DUPLICATE_POLICY policy

is policy for handling multiple samples with identical timestamps. See `DUPLICATE_POLICY` in [`TS.CREATE`]().

LABELS [{label value}...]

is set of label-value pairs that represent metadata labels of the key and serve as a secondary index.

If `LABELS` is specified, the given label list is applied. Labels that are not present in the given list are removed implicitly. Specifying `LABELS` with no label-value pairs removes all existing labels. See `LABELS` in [`TS.CREATE`]().

title: Examples

role: example

text:
Alter a temperature time series

Create a temperature time series.

127.0.0.1:6379> TS.CREATE temperature:2:32 RETENTION 60000 DUPLICATE_POLICY MAX LABELS sensor_id 2 area_id 32
OK

Alter the labels in the time series.

127.0.0.1:6379> TS.ALTER temperature:2:32 LABELS sensor_id 2 area_id 32 sub_area_id 15
OK

title: Redis Software and Redis Cloud compatibility

text:
| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Supported
 | ✅ Flexible & Annual
✅ Free & Fixed</nobr> |  |

title: Return information

role: returns

text:
**RESP2:**

One of the following:
* [Simple string reply](): `OK` when the time series is altered successfully.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, key does not exist, etc.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` when the time series is altered successfully.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, key does not exist, etc.

title: See also

role: related

text: [`TS.CREATE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.CREATE

summary: Create a new time series

title: Overview

text:
Create a new time series

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

<note>Notes:

- If a key already exists, you get a Redis error reply, `TSDB: key already exists`. You can check for the existence of a key with the [`EXISTS`]() command.
- Other commands that also create a new time series when called with a key that does not exist are [`TS.ADD`](), [`TS.INCRBY`](), and [`TS.DECRBY`]().
</note>

title: Optional arguments

text:
RETENTION retentionPeriod

is maximum age for samples compared to the highest reported timestamp, in milliseconds. Samples are expired based solely on the difference between their timestamp and the timestamps passed to subsequent [`TS.ADD`](), [`TS.MADD`](), [`TS.INCRBY`](), and [`TS.DECRBY`]() calls with this key.

When set to 0, samples never expire. When not specified, the option is set to the global [RETENTION_POLICY]() configuration of the database, which by default is 0.

ENCODING enc

specifies the series samples encoding format as one of the following values:
 - `COMPRESSED`, applies compression to the series samples.
 - `UNCOMPRESSED`, keeps the raw samples in memory. Adding this flag keeps data in an uncompressed form.

`COMPRESSED` is almost always the right choice. Compression not only saves memory but usually improves performance due to a lower number of memory accesses. It can result in about 90% memory reduction. The exception are highly irregular timestamps or values, which occur rarely.

When not specified, the option is set to `COMPRESSED`.

CHUNK_SIZE size

is initial allocation size, in bytes, for the data part of each new chunk. Actual chunks may consume more memory. Changing chunkSize (using [`TS.ALTER`]()) does not affect existing chunks.

Must be a multiple of 8 in the range [48 .. 1048576]. When not specified, it is set to the global [CHUNK_SIZE_BYTES]() configuration of the database, which by default is 4096 (a single memory page).

Note: Before v1.6.10 no minimum was enforced. Between v1.6.10 and v1.6.17 and in v1.8.0 the minimum value was 128. Since v1.8.1 the minimum value is 48.

The data in each key is stored in chunks. Each chunk contains header and data for a given timeframe. An index contains all chunks. Iterations occur inside each chunk. Depending on your use case, consider these tradeoffs for having smaller or larger sizes of chunks:

  - Insert performance: Smaller chunks result in slower inserts (more chunks need to be created).
  - Query performance: Queries for a small subset when the chunks are very large are slower, as we need to iterate over the chunk to find the data.
  - Larger chunks may take more memory when you have a very large number of keys and very few samples per key, or less memory when you have many samples per key.

 If you are unsure about your use case, select the default.

DUPLICATE_POLICY policy

is policy for handling insertion ([`TS.ADD`]() and [`TS.MADD`]()) of multiple samples with identical timestamps, with one of the following values:
  - `BLOCK`: ignore any newly reported value and reply with an error
  - `FIRST`: ignore any newly reported value
  - `LAST`: override with the newly reported value
  - `MIN`: only override if the value is lower than the existing value
  - `MAX`: only override if the value is higher than the existing value
  - `SUM`: If a previous sample exists, add the new sample to it so that the updated value is equal to (previous + new). If no previous sample exists, set the updated value equal to the new value.

  When not specified: set to the global [DUPLICATE_POLICY]() configuration of the database (which, by default, is `BLOCK`).

`BLOCK` is often used to avoid accidental changes. `FIRST` can be used as an optimization when duplicate reports are possible. `LAST` can be used when updates are being reported. `SUM` is used for counters (e.g., the number of cars entering a parking lot per minute when there are multiple reporting counting devices). `MIN` and `MAX` can be used, for example, to store the minimal/maximal stock price per minute (instead of storing all the samples and defining a compaction rule).

IGNORE ignoreMaxTimediff ignoreMaxValDiff

is the policy for handling duplicate samples. A new sample is considered a duplicate and is ignored if the following conditions are met:

  - The time series is not a compaction;
  - The time series' `DUPLICATE_POLICY` IS `LAST`;
  - The sample is added in-order (`timestamp ≥ max_timestamp`);
  - The difference of the current timestamp from the previous timestamp (`timestamp - max_timestamp`) is less than or equal to `IGNORE_MAX_TIME_DIFF`;
  - The absolute value difference of the current value from the value at the previous maximum timestamp (`abs(value - value_at_max_timestamp`) is less than or equal to `IGNORE_MAX_VAL_DIFF`.

where `max_timestamp` is the timestamp of the sample with the largest timestamp in the time series, and `value_at_max_timestamp` is the value at `max_timestamp`.

When not specified: set to the global [IGNORE_MAX_TIME_DIFF]() and [IGNORE_MAX_VAL_DIFF](), which are, by default, both set to 0.

LABELS {label value}...

is set of label-value pairs that represent metadata labels of the key and serve as a secondary index.

The [`TS.MGET`](), [`TS.MRANGE`](), and [`TS.MREVRANGE`]() commands operate on multiple time series based on their labels. The [`TS.QUERYINDEX`]() command returns all time series keys matching a given filter based on their labels.

title: Examples

text:
Create a temperature time series

127.0.0.1:6379> TS.CREATE temperature:2:32 RETENTION 60000 DUPLICATE_POLICY MAX LABELS sensor_id 2 area_id 32
OK

title: Return information

text:
**RESP2:**

One of the following:
* [Simple string reply](): `OK` when the time series is created successfully.
* [Simple error reply]() in these cases: invalid arguments, key already exists, etc.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` when the time series is created successfully.
* [Simple error reply]() in these cases: invalid arguments, key already exists, etc.

title: See also

text: [`TS.ADD`]() | [`TS.INCRBY`]() | [`TS.DECRBY`]() | [`TS.MGET`]() | [`TS.MRANGE`]() | [`TS.MREVRANGE`]() | [`TS.QUERYINDEX`]()

title: Related topics

text:
- [RedisTimeSeries]()
- [RedisTimeSeries Version 1.2 Is Here!](https://redis.com/blog/redistimeseries-version-1-2-is-here/)


title: TS.CREATERULE

summary: Create a compaction rule.

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Create a compaction rule

[Examples](#examples)

title: Required arguments

text:
sourceKey

is key name for the source time series.

destKey

is key name for destination (compacted) time series. It must be created before `TS.CREATERULE` is called.

AGGREGATION aggregator bucketDuration

aggregates results into time buckets.

  - `aggregator` takes one of the following aggregation types:

    | `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>Notes

- Only new samples that are added into the source series after the creation of the rule will be aggregated.
- Calling `TS.CREATERULE` with a nonempty `destKey` may result in inconsistencies between the raw and the compacted data.
- Explicitly adding samples to a compacted time series (using [`TS.ADD`](), [`TS.MADD`](), [`TS.INCRBY`](), or [`TS.DECRBY`]()) may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples.
- If no samples are added to the source time series during a bucket period. no _compacted sample_ is added to the destination time series.
- The timestamp of a compacted sample added to the destination time series is set to the start timestamp the appropriate compaction bucket. For example, for a 10-minute compaction bucket with no alignment, the compacted samples timestamps are `x:00`, `x:10`, `x:20`, and so on.
- Deleting `destKey` will cause the compaction rule to be deleted as well.

In a clustered environment, you must use [hash tags](#hash-tags) to force `sourceKey` and `destKey` to be stored in the same hash slot. If you don't, Redis may fail to compact the data without displaying any error messages.

</note>

title: Optional arguments

text:
alignTimestamp (since RedisTimeSeries 1.8)

ensures that there is a bucket that starts exactly at `alignTimestamp` and aligns all other buckets accordingly. It is expressed in milliseconds. The default value is 0: aligned with the Unix epoch.

For example, if `bucketDuration` is 24 hours (`24 * 3600 * 1000`), setting `alignTimestamp` to 6 hours after the Unix epoch (`6 * 3600 * 1000`) ensures that each bucket’s timeframe is `[06:00 .. 06:00)`.

title: Examples

text:
Create a compaction rule

Create a time series to store the temperatures measured in Tel Aviv.

127.0.0.1:6379> TS.CREATE temp:{TLV} LABELS type temp location TLV
OK

Next, create a compacted time series named _dailyAvgTemp_ containing one compacted sample per 24 hours: the time-weighted average of all measurements taken from midnight to next midnight.

127.0.0.1:6379> TS.CREATE dailyAvgTemp:{TLV} LABELS type temp location TLV
127.0.0.1:6379> TS.CREATERULE temp:{TLV} dailyAvgTemp:{TLV} AGGREGATION twa 86400000

Now, also create a compacted time series named _dailyDiffTemp_. This time series will contain one compacted sample per 24 hours: the difference between the minimum and the maximum temperature measured between 06:00 and 06:00 next day.
 Here, 86400000 is the number of milliseconds in 24 hours, 21600000 is the number of milliseconds in 6 hours.

127.0.0.1:6379> TS.CREATE dailyDiffTemp:{TLV} LABELS type temp location TLV
127.0.0.1:6379> TS.CREATERULE temp:{TLV} dailyDiffTemp:{TLV} AGGREGATION range 86400000 21600000

title: Return information

text:
**RESP2:**

One of the following:
* [Simple string reply](): `OK` when the compaction rule is created successfully.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, `sourceKey` does not exist, `destKey` does not exist, `sourceKey` is already a destination of a compaction rule, `destKey` is already a source or a destination of a compaction rule, or `sourceKey` and `destKey` are identical.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` when the compaction rule is created successfully.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, `sourceKey` does not exist, `destKey` does not exist, `sourceKey` is already a destination of a compaction rule, `destKey` is already a source or a destination of a compaction rule, or `sourceKey` and `destKey` are identical.

title: See also

text: [`TS.DELETERULE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.DECRBY

title: Overview

text:
Decrease the value of the sample with the maximum existing timestamp, or create a new sample with a value equal to the value of the sample with the maximum existing timestamp with a given decrement

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

subtrahend

is numeric value of the subtrahend (double). An error is returned if the subtrahend is NaN.

<note>Notes
- When specified key does not exist, a new time series is created.
- You can use this command as a counter or gauge that automatically gets history as a time series.
- If a policy for handling duplicate samples (`IGNORE`) is defined for this time series - `TS.DECRBY` operations are affected as well (sample additions/modifications can be filtered).
- Explicitly adding samples to a compacted time series (using [`TS.ADD`](), [`TS.MADD`](), [`TS.INCRBY`](), or `TS.DECRBY`) may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples.
</note>

title: Optional arguments

text:
TIMESTAMP timestamp

is Unix time (integer, in milliseconds) specifying the sample timestamp or `*` to set the sample timestamp to the Unix time of the server's clock.

Unix time is the number of milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970, the Unix epoch, without adjustments made due to leap seconds.

`timestamp` must be equal to or higher than the maximum existing timestamp. When equal, the value of the sample with the maximum existing timestamp is decreased. If it is higher, a new sample with a timestamp set to `timestamp` is created, and its value is set to the value of the sample with the maximum existing timestamp minus `subtrahend`.

If the time series is empty, the value is set to `subtrahend`.

When not specified, the timestamp is set to the Unix time of the server's clock.

<note>NaN Handling (Redis 8.6+): An error is returned if the current value at the maximum existing timestamp is NaN.</note>

RETENTION retentionPeriod

is maximum retention period, compared to the maximum existing timestamp, in milliseconds.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `RETENTION` in [`TS.CREATE`]().

ENCODING enc

specifies the series sample encoding format.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `ENCODING` in [`TS.CREATE`]().

CHUNK_SIZE size

is memory size, in bytes, allocated for each data chunk.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `CHUNK_SIZE` in [`TS.CREATE`]().

DUPLICATE_POLICY policy

is policy for handling insertion ([`TS.ADD`]() and [`TS.MADD`]()) of multiple samples with identical timestamps.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `DUPLICATE_POLICY` in [`TS.CREATE`]().

IGNORE ignoreMaxTimediff ignoreMaxValDiff

is the policy for handling duplicate samples. A new sample is considered a duplicate and is ignored if the following conditions are met:

  - The time series is not a compaction;
  - The time series' `DUPLICATE_POLICY` IS `LAST`;
  - The sample is added in-order (`timestamp ≥ max_timestamp`);
  - The difference of the current timestamp from the previous timestamp (`timestamp - max_timestamp`) is less than or equal to `IGNORE_MAX_TIME_DIFF`;
  - The absolute value difference of the current value from the value at the previous maximum timestamp (`abs(value - value_at_max_timestamp`) is less than or equal to `IGNORE_MAX_VAL_DIFF`.

where `max_timestamp` is the timestamp of the sample with the largest timestamp in the time series, and `value_at_max_timestamp` is the value at `max_timestamp`.

When not specified: set to the global [IGNORE_MAX_TIME_DIFF]() and [IGNORE_MAX_VAL_DIFF](), which are, by default, both set to 0.

These parameters are used when creating a new time series to set the per-key parameters, and are ignored when called with an existing time series (the existing per-key configuration parameters are used).

LABELS [{label value}...]

is set of label-value pairs that represent metadata labels of the key and serve as a secondary index.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `LABELS` in [`TS.CREATE`]().

<note>Notes
- You can use this command to create a new time series and add a sample to it in a single command.
  `RETENTION`, `ENCODING`, `CHUNK_SIZE`, `DUPLICATE_POLICY`, `IGNORE`, and `LABELS` are used only when creating a new time series, and ignored when adding or modifying samples in an existing time series.
- Setting `RETENTION` and `LABELS` introduces additional time complexity.
</note>

title: Return information

text:
**RESP2:**

One of the following:
* [Integer reply](): the timestamp of the upserted sample. If the sample is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, or when `timestamp` is not equal to or higher than the maximum existing timestamp.

**RESP3:**

One of the following:
* [Integer reply](): the timestamp of the upserted sample. If the sample is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, or when `timestamp` is not equal to or higher than the maximum existing timestamp.

title: See also

text: [`TS.INCRBY`]() | [`TS.CREATE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.DEL

title: Overview

text:
Delete all samples between two timestamps for a given time series

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

fromTimestamp

is start timestamp for the range deletion.

toTimestamp

is end timestamp for the range deletion.

The given timestamp interval is closed (inclusive), meaning that samples whose timestamp eqauls the `fromTimestamp` or `toTimestamp` are also deleted.

<note>Notes:

- If fromTimestamp is older than the retention period compared to the maximum existing timestamp, the deletion is discarded and an error is returned.
- When deleting a sample from a time series for which compaction rules are defined:
  - If all the original samples for an affected compaction bucket are available, the compacted value is recalculated based on the remaining original samples, or removed if all original samples within the compaction bucket  were deleted.
  - If original samples for an affected compaction bucket were expired, the deletion is discarded and an error is returned.
- Explicitly deleting samples from a compacted time series may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples. That being said, it is safe to explicitly delete samples from a compacted time series beyond the retention period of the original time series.

</note>

title: Examples

text:
Delete range of data points

Create time series for temperature in Tel Aviv and Jerusalem, then add different temperature samples.

127.0.0.1:6379> TS.CREATE temp:TLV LABELS type temp location TLV
OK
127.0.0.1:6379> TS.CREATE temp:JLM LABELS type temp location JLM
OK
127.0.0.1:6379> TS.MADD temp:TLV 1000 30 temp:TLV 1010 35 temp:TLV 1020 9999 temp:TLV 1030 40
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
4) (integer) 1030
127.0.0.1:6379> TS.MADD temp:JLM 1005 30 temp:JLM 1015 35 temp:JLM 1025 9999 temp:JLM 1035 40
1) (integer) 1005
2) (integer) 1015
3) (integer) 1025
4) (integer) 1035

Delete the range of data points for temperature in Tel Aviv.

127.0.0.1:6379> TS.DEL temp:TLV 1000 1030
(integer) 4

text:
| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Supported
 | ✅ Flexible & Annual
✅ Free & Fixed</nobr> |  |

title: Return information

text:
**RESP2:**

One of the following:
* [Integer reply](): the number of samples that were deleted.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, `timestamp` is older than the retention period compared to the maximum existing timestamp, or when an affected compaction bucket cannot be recalculated.

**RESP3:**

One of the following:
* [Integer reply](): the number of samples that were deleted.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, `timestamp` is older than the retention period compared to the maximum existing timestamp, or when an affected compaction bucket cannot be recalculated.

title: See also

text: [`TS.ADD`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.DELETERULE

summary: Delete a compaction rule

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Delete a compaction rule

title: Required arguments

text:
sourceKey

is key name for the source time series.

destKey

is key name for destination (compacted) time series.

<note>Note: This command does not delete the compacted series.</note>

title: Return information

text:
**RESP2:**

One of the following:
* [Simple string reply](): `OK` when the compaction rule is deleted successfully.
* [Simple error reply]() in these cases: invalid arguments, or when such rule does not exist.

**RESP3:**

One of the following:
* [Simple string reply](): `OK` when the compaction rule is deleted successfully.
* [Simple error reply]() in these cases: invalid arguments, or when such rule does not exist.

title: See also

text: [`TS.CREATERULE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.GET

title: Overview

text:
Get the sample with the highest timestamp from a given time series. Starting from Redis 8.6, NaN values are included in the results.

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

title: Optional arguments

text:
LATEST (since RedisTimeSeries v1.8)

is used when a time series is a compaction. With `LATEST`, TS.GET reports the compacted value of the latest (possibly partial) bucket. Without `LATEST`, TS.GET 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 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`.

title: Examples

text:
Get latest measured temperature for a city

Create a time series to store the temperatures measured in Tel Aviv and add four measurements for Sun Jan 01 2023

127.0.0.1:6379> TS.CREATE temp:TLV LABELS type temp location TLV
OK
127.0.0.1:6379> TS.MADD temp:TLV 1672534800 12 temp:TLV 1672556400 16 temp:TLV 1672578000 21 temp:TLV 1672599600 14

Next, get the latest measured temperature (the temperature with the highest timestamp)

127.0.0.1:6379> TS.GET temp:TLV
1) (integer) 1672599600
2) 14

Get latest maximal daily temperature for a city

Create a time series to store the temperatures measured in Jerusalem

127.0.0.1:6379> TS.CREATE temp:JLM LABELS type temp location JLM
OK

Next, create a compacted time series named _dailyAvgTemp:JLM_ containing one compacted sample per 24 hours: the maximum of all measurements taken from midnight to next midnight.

127.0.0.1:6379> TS.CREATE dailyMaxTemp:JLM LABELS type temp location JLM
OK
127.0.0.1:6379> TS.CREATERULE temp:JLM dailyMaxTemp:JLM AGGREGATION max 86400000
OK

Add four measurements for Sun Jan 01 2023 and three measurements for Mon Jan 02 2023

127.0.0.1:6379> TS.MADD temp:JLM 1672534800000 12 temp:JLM 1672556400000 16 temp:JLM 1672578000000 21 temp:JLM 1672599600000 14
1) (integer) 1672534800000
2) (integer) 1672556400000
3) (integer) 1672578000000
4) (integer) 1672599600000
127.0.0.1:6379> TS.MADD temp:JLM 1672621200000 11 temp:JLM 1672642800000 21 temp:JLM 1672664400000 26
1) (integer) 1672621200000
2) (integer) 1672642800000
3) (integer) 1672664400000

Next, get the latest maximum daily temperature; do not report the latest, possibly partial, bucket

127.0.0.1:6379> TS.GET dailyMaxTemp:JLM
1) (integer) 1672531200000
2) 21

Get the latest maximum daily temperature (the temperature with the highest timestamp); report the latest, possibly partial, bucket

127.0.0.1:6379> TS.GET dailyMaxTemp:JLM LATEST
1) (integer) 1672617600000
2) 26

title: Return information

text:
**RESP2:**

One of the following:
* [Array reply]() of a single ([Integer reply](), [Simple string reply]()) pair representing (timestamp, value) of the sample with the highest timestamp.
* An empty [Array reply]() when the time series is empty.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, key does not exist, etc.

**RESP3:**

One of the following:
* [Array reply]() of a single ([Integer reply](), [Double reply]()) pair representing (timestamp, value) of the sample with the highest timestamp.
* An empty [Array reply]() when the time series is empty.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, key does not exist, etc.

title: See also

text: [`TS.MGET`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.INCRBY

title: Overview

text:
Increase the value of the sample with the maximum existing timestamp, or create a new sample with a value equal to the value of the sample with the maximum existing timestamp with a given increment

[Examples](#examples)

title: Required arguments

text:
key

is key name for the time series.

addend

is numeric value of the addend (double). An error is returned if the addend is NaN.

<note>Notes
- When specified key does not exist, a new time series is created.
- You can use this command as a counter or gauge that automatically gets history as a time series.
- If a policy for handling duplicate samples (`IGNORE`) is defined for this time series - `TS.INCRBY` operations are affected as well (sample additions/modifications can be filtered).
- Explicitly adding samples to a compacted time series (using [`TS.ADD`](), [`TS.MADD`](), `TS.INCRBY`, or [`TS.DECRBY`]()) may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples.
</note>

title: Optional arguments

text:
TIMESTAMP timestamp

is Unix time (integer, in milliseconds) specifying the sample timestamp or `*` to set the sample timestamp to the Unix time of the server's clock.

Unix time is the number of milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970, the Unix epoch, without adjustments made due to leap seconds.

`timestamp` must be equal to or higher than the maximum existing timestamp. When equal, the value of the sample with the maximum existing timestamp is increased. If it is higher, a new sample with a timestamp set to `timestamp` is created, and its value is set to the value of the sample with the maximum existing timestamp plus `addend`.

If the time series is empty, the value is set to `addend`.

When not specified, the timestamp is set to the Unix time of the server's clock.

<note>NaN Handling (Redis 8.6+): An error is returned if the current value at the maximum existing timestamp is NaN.</note>

RETENTION retentionPeriod</summmary>

is maximum retention period, compared to the maximum existing timestamp, in milliseconds.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `RETENTION` in [`TS.CREATE`]().

ENCODING enc

specifies the series sample encoding format.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `ENCODING` in [`TS.CREATE`]().

CHUNK_SIZE size

is memory size, in bytes, allocated for each data chunk.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `CHUNK_SIZE` in [`TS.CREATE`]().

DUPLICATE_POLICY policy

is policy for handling insertion ([`TS.ADD`]() and [`TS.MADD`]()) of multiple samples with identical timestamps.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `DUPLICATE_POLICY` in [`TS.CREATE`]().

IGNORE ignoreMaxTimediff ignoreMaxValDiff

is the policy for handling duplicate samples. A new sample is considered a duplicate and is ignored if the following conditions are met:

  - The time series is not a compaction;
  - The time series' `DUPLICATE_POLICY` IS `LAST`;
  - The sample is added in-order (`timestamp ≥ max_timestamp`);
  - The difference of the current timestamp from the previous timestamp (`timestamp - max_timestamp`) is less than or equal to `IGNORE_MAX_TIME_DIFF`;
  - The absolute value difference of the current value from the value at the previous maximum timestamp (`abs(value - value_at_max_timestamp`) is less than or equal to `IGNORE_MAX_VAL_DIFF`.

where `max_timestamp` is the timestamp of the sample with the largest timestamp in the time series, and `value_at_max_timestamp` is the value at `max_timestamp`.

When not specified: set to the global [IGNORE_MAX_TIME_DIFF]() and [IGNORE_MAX_VAL_DIFF](), which are, by default, both set to 0.

These parameters are used when creating a new time series to set the per-key parameters, and are ignored when called with an existing time series (the existing per-key configuration parameters are used).

LABELS [{label value}...]

is set of label-value pairs that represent metadata labels of the key and serve as a secondary index.

Use it only if you are creating a new time series. It is ignored if you are adding samples to an existing time series. See `LABELS` in [`TS.CREATE`]().

<note>Notes
- You can use this command to create a new time series and add a sample to it in a single command.
  `RETENTION`, `ENCODING`, `CHUNK_SIZE`, `DUPLICATE_POLICY`, `IGNORE`, and `LABELS` are used only when creating a new time series, and ignored when adding or modifying samples in an existing time series.
- Setting `RETENTION` and `LABELS` introduces additional time complexity.
</note>

title: Examples

text:
Store sum of data from several sources

Suppose you are getting number of orders or total income per minute from several points of sale, and you want to store only the combined value. Call TS.INCRBY for each point-of-sale report.

127.0.0.1:6379> TS.INCRBY a 232 TIMESTAMP 1657811829000		// point-of-sale #1
(integer) 1657811829000
127.0.0.1:6379> TS.INCRBY a 157 TIMESTAMP 1657811829000		// point-of-sale #2
(integer) 1657811829000
127.0.0.1:6379> TS.INCRBY a 432 TIMESTAMP 1657811829000		// point-of-sale #3
(integer) 1657811829000

Note that the timestamps must arrive in non-decreasing order.

127.0.0.1:6379> ts.incrby a 100 TIMESTAMP 50
(error) TSDB: timestamp must be equal to or higher than the maximum existing timestamp

You can achieve similar results without such protection using `TS.ADD key timestamp value ON_DUPLICATE sum`.

Count sensor captures

Suppose a sensor ticks whenever a car is passed on a road, and you want to count occurrences. Whenever you get a tick from the sensor you can simply call:

127.0.0.1:6379> TS.INCRBY a 1
(integer) 1658431553109

The timestamp is filled automatically.

title: Return information

title: See also

text: [`TS.DECRBY`]() | [`TS.CREATE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.INFO

summary: Returns information and statistics for a time series

title: Overview

text:
Return information and statistics for a time series.

[Examples](#examples)

title: Required arguments

text:
key
is key name of the time series.

title: Optional arguments

text:
[DEBUG]

is an optional flag to get a more detailed information about the chunks.

title: Return information

text:
**RESP2:**

[Array reply]() with information about the time series as flattened name-value pairs:

| Name
[Simple string reply]() | Description
| ---------------------------- | -
| `totalSamples`    | [Integer reply]()
 Total number of samples in this time series
| `memoryUsage`     | [Integer reply]()
 Total number of bytes allocated for this time series, which is the sum of
 - The memory used for storing the series' configuration parameters (retention period, duplication policy, etc.)
- The memory used for storing the series' compaction rules
- The memory used for storing the series' labels (key-value pairs)
- The memory used for storing the chunks (chunk header + compressed/uncompressed data)
| `firstTimestamp`  | [Integer reply]()
 First timestamp present in this time series (Unix timestamp in milliseconds)
| `lastTimestamp`   | [Integer reply]()
 Last timestamp present in this time series  (Unix timestamp in milliseconds)
| `retentionTime`   | [Integer reply]()
 The retention period, in milliseconds, for this time series
| `chunkCount`      | [Integer reply]()
 Number of chunks used for this time series
| `chunkSize`       | [Integer reply]()
 The initial allocation size, in bytes, for the data part of each new chunk.
Actual chunks may consume more memory. Changing the chunk size (using [`TS.ALTER`]()) does not affect existing chunks.
| `chunkType`       | [Simple string reply]()
 The chunks type: `compressed` or `uncompressed`
| `duplicatePolicy` | [Simple string reply]() or [Nil reply]()
 The [duplicate policy]() of this time series
| `labels`          | [Array reply]() or [Nil reply]()
 Metadata labels of this time series
 Each element is a 2-elements [Array reply]() of ([Bulk string reply](), [Bulk string reply]()) representing (label, value)
| `sourceKey`       | [Bulk string reply]() or [Nil reply]()
Key name for source time series in case the current series is a target of a [compaction rule]()
| `rules`           | [Array reply]()
 [Compaction rules]() defined in this time series
 Each rule is an [Array reply]() with 4 elements:
- [Bulk string reply](): The compaction key
- [Integer reply](): The bucket duration
- [Simple string reply](): The aggregator
- [Integer reply](): The alignment (since RedisTimeSeries v1.8)

When [`DEBUG`]() is specified, the response also contains:

| Name
[Simple string reply]() | Description
| ---------------------------- | -
| `keySelfName`     | [Bulk string reply]()
 Name of the key
| `Chunks`          | [Array reply]() with information about the chunks
Each element is an [Array reply]() of information about a single chunk in a name([Simple string reply]())-value pairs:
- `startTimestamp` - [Integer reply]() - First timestamp present in the chunk
- `endTimestamp` - [Integer reply]() - Last timestamp present in the chunk
- `samples` - [Integer reply]() - Total number of samples in the chunk
- `size` - [Integer reply]() - the chunk's internal data size (without overheads) in bytes
- `bytesPerSample` - [Bulk string reply]() (double) - Ratio of `size` and `samples`

**RESP3:**

[Map reply]() with information about the time series. The map contains the same fields as described in the RESP2 response, but organized as key-value pairs in a map structure rather than a flattened array.

When [`DEBUG`]() is specified, the response also contains the additional `keySelfName` and `Chunks` fields as described above.

title: Examples

text:
Find information about a temperature/humidity time series by location and sensor type

Create a set of sensors to measure temperature and humidity in your study and kitchen.

127.0.0.1:6379> TS.CREATE telemetry:study:temperature LABELS room study type temperature
OK
127.0.0.1:6379> TS.CREATE telemetry:study:humidity LABELS room study type humidity
OK
127.0.0.1:6379> TS.CREATE telemetry:kitchen:temperature LABELS room kitchen type temperature
OK
127.0.0.1:6379> TS.CREATE telemetry:kitchen:humidity LABELS room kitchen type humidity
OK

Find information about the time series for temperature in the kitchen.

127.0.0.1:6379> TS.INFO telemetry:kitchen:temperature
 1) totalSamples
 2) (integer) 0
 3) memoryUsage
 4) (integer) 4246
 5) firstTimestamp
 6) (integer) 0
 7) lastTimestamp
 8) (integer) 0
 9) retentionTime
10) (integer) 0
11) chunkCount
12) (integer) 1
13) chunkSize
14) (integer) 4096
15) chunkType
16) compressed
17) duplicatePolicy
18) (nil)
19) labels
20) 1) 1) "room"
       2) "kitchen"
    2) 1) "type"
       2) "temperature"
21) sourceKey
22) (nil)
23) rules
24) (empty array)

Query the time series using DEBUG to get more information about the chunks.

127.0.0.1:6379> TS.INFO telemetry:kitchen:temperature DEBUG
 1) totalSamples
 2) (integer) 0
 3) memoryUsage
 4) (integer) 4246
 5) firstTimestamp
 6) (integer) 0
 7) lastTimestamp
 8) (integer) 0
 9) retentionTime
10) (integer) 0
11) chunkCount
12) (integer) 1
13) chunkSize
14) (integer) 4096
15) chunkType
16) compressed
17) duplicatePolicy
18) (nil)
19) labels
20) 1) 1) "room"
       2) "kitchen"
    2) 1) "type"
       2) "temperature"
21) sourceKey
22) (nil)
23) rules
24) (empty array)
25) keySelfName
26) "telemetry:kitchen:temperature"
27) Chunks
28) 1)  1) startTimestamp
        2) (integer) 0
        3) endTimestamp
        4) (integer) 0
        5) samples
        6) (integer) 0
        7) size
        8) (integer) 4096
        9) bytesPerSample
       10) "inf"

title: See also

text: [`TS.RANGE`]() | [`TS.QUERYINDEX`]() | [`TS.GET`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.MADD

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Append new samples to one or more time series

[Examples](#examples)

title: Required arguments

text:
key

is the key name for the time series.

timestamp

is Unix time (integer, in milliseconds) specifying the sample timestamp or `*` to set the sample timestamp to the Unix time of the server's clock.

Unix time is the number of milliseconds that have elapsed since 00:00:00 UTC on 1 January 1970, the Unix epoch, without adjustments made due to leap seconds.

value

is numeric data value of the sample (double). The double number should follow RFC 7159 (JSON standard). The parser rejects overly large values that would not fit in binary64. It does not accept infinite values. NaN (Not a Number) values are supported starting from Redis 8.6.

<note>Notes:
- If `timestamp` is older than the retention period compared to the maximum existing timestamp, the sample is discarded and an error is returned.
- Explicitly adding samples to a compacted time series (using [`TS.ADD`](), `TS.MADD`, [`TS.INCRBY`](), or [`TS.DECRBY`]()) may result in inconsistencies between the raw and the compacted data. The compaction process may override such samples.
- `ignoreMaxTimeDiff` and `ignoreMaxValDiff` cannot be specified as is the case with `TS.ADD`. However, the same logic still applies based on the values of the per-key configuration parameters. See the [`TS.ADD`]() command page for more information.
</note>

title: Complexity

text:
If a compaction rule exits on a time series, TS.MADD performance might be reduced.
The complexity of TS.MADD is always `O(N*M)`, where `N` is the amount of series updated and `M` is the number of compaction rules or `O(N)` with no compaction.

title: Examples

text:
Add stock prices at different timestamps

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 stock:B 1000 120 stock:B 1010 110 stock:B 1020 100
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
4) (integer) 1000
5) (integer) 1010
6) (integer) 1020

text:
| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Supported
 | ✅ Flexible & Annual
✅ Free & Fixed</nobr> |  |

title: Return information

text:
**RESP2:**

One of the following:
* [Array reply](), where each element is an [Integer reply]() representing the timestamp of a upserted sample. For each element that is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply element value will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, duplication policy is `BLOCK`, or when `timestamp` is older than the retention period compared to the maximum existing timestamp.

**RESP3:**

One of the following:
* [Array reply](), where each element is an [Integer reply]() representing the timestamp of a upserted sample. For each element that is ignored (see `IGNORE` in [`TS.CREATE`]()), the reply element value will be the largest timestamp in the time series.
* [Simple error reply]() in these cases: invalid arguments, wrong key type, duplication policy is `BLOCK`, or when `timestamp` is older than the retention period compared to the maximum existing timestamp.

title: See also

text: [`TS.MRANGE`]() | [`TS.RANGE`]() | [`TS.MREVRANGE`]() | [`TS.REVRANGE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.MGET

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Get the sample with the highest timestamp from each time series matching a specific filter. Starting from Redis 8.6, NaN values are included in the results.

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)

title: Required arguments

text:
FILTER filterExpr...

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>Notes:
   - 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 a time series is a temperature time series of a study room.
   - Whitespaces are unallowed in a filter expression except between quotes or double quotes in values - e.g., `x="y y"` or `x='(y y,z z)'`.
   </note>

title: Optional arguments

text:
LATEST (since RedisTimeSeries v1.8)

is used when a time series is a compaction. With `LATEST`, TS.MGET 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.MGET 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`.

WITHLABELS

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.

SELECTED_LABELS label... (since RedisTimeSeries v1.6)

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.

<note>Note: The [`MGET`]() command cannot be part of a transaction when running on a Redis cluster.</note>

title: Examples

text:
Select labels to retrieve

Create time series for temperature in Tel Aviv and Jerusalem, then add different temperature samples.

127.0.0.1:6379> TS.CREATE temp:TLV LABELS type temp location TLV
OK
127.0.0.1:6379> TS.CREATE temp:JLM LABELS type temp location JLM
OK
127.0.0.1:6379> TS.MADD temp:TLV 1000 30 temp:TLV 1010 35 temp:TLV 1020 9999 temp:TLV 1030 40
1) (integer) 1000
2) (integer) 1010
3) (integer) 1020
4) (integer) 1030
127.0.0.1:6379> TS.MADD temp:JLM 1005 30 temp:JLM 1015 35 temp:JLM 1025 9999 temp:JLM 1035 40
1) (integer) 1005
2) (integer) 1015
3) (integer) 1025
4) (integer) 1035

Get all the labels associated with the last sample.

127.0.0.1:6379> TS.MGET WITHLABELS FILTER type=temp
1) 1) "temp:JLM"
   2) 1) 1) "type"
         2) "temp"
      2) 1) "location"
         2) "JLM"
   3) 1) (integer) 1035
      2) 40
2) 1) "temp:TLV"
   2) 1) 1) "type"
         2) "temp"
      2) 1) "location"
         2) "TLV"
   3) 1) (integer) 1030
      2) 40

To get only the `location` label for each last sample, use `SELECTED_LABELS`.

127.0.0.1:6379> TS.MGET SELECTED_LABELS location FILTER type=temp
1) 1) "temp:JLM"
   2) 1) 1) "location"
         2) "JLM"
   3) 1) (integer) 1035
      2) 40
2) 1) "temp:TLV"
   2) 1) 1) "location"
         2) "TLV"
   3) 1) (integer) 1030
      2) 40

title: Return information

text:
**RESP2:**

[Array reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Array reply](): label-value pairs ([Bulk string reply](), [Bulk string reply]())
  - 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](): a single timestamp-value pair ([Integer reply](), [Simple string reply]())

**RESP3:**

[Map reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Map reply]() or [Array reply](): label-value pairs
  - By default, an empty array 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)
- [Array reply](): a single timestamp-value pair ([Integer reply](), [Double reply]())

title: See also

title: Related topics

text: [RedisTimeSeries]()


title: TS.MRANGE

summary: Query a range across multiple time series by filters in forward direction

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [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).

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)

title: Required arguments

text:
fromTimestamp

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...`.

toTimestamp

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...`.

FILTER filterExpr...

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>Notes:
   - 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 a time series is a temperature time series of a study room.
   - Whitespaces are unallowed in a filter expression except between quotes or double quotes in values - e.g., `x="y y"` or `x='(y y,z z)'`.
   </note>

title: Optional arguments

text:
LATEST (since RedisTimeSeries 1.8)

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`.

FILTER_BY_TS ts... (since RedisTimeSeries 1.6)

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.

FILTER_BY_VALUE min max (since RedisTimeSeries 1.6)

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.

WITHLABELS

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.

SELECTED_LABELS label... (since RedisTimeSeries 1.6)

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.

COUNT count

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

When used together with `AGGREGATION`: limits the number of reported buckets.

ALIGN align (since RedisTimeSeries 1.6)

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>Note: When not provided, alignment is set to `0`.</note>

AGGREGATION aggregator bucketDuration

per time series, aggregates samples into time buckets, where:

  - `aggregator` takes one of the following aggregation types:

    | `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.

  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`.

[BUCKETTIMESTAMP bt] (since RedisTimeSeries 1.8)

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)        |

[EMPTY] (since RedisTimeSeries 1.8)

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.

GROUPBY label REDUCE reducer (since RedisTimeSeries 1.6)

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.

  - `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>Notes:
  - 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>

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

title: Return information

text:
**RESP2:**

If `GROUPBY label REDUCE reducer` is not specified:

[Array reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Array reply](): label-value pairs ([Bulk string reply](), [Bulk string reply]())
  - 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](): timestamp-value pairs ([Integer reply](), [Simple string reply]()) representing all samples/aggregations matching the range

If `GROUPBY label REDUCE reducer` is specified:

[Array reply](): for each group of time series matching the specified filters, the following is reported:
- [Bulk string reply]() with the format `label=value` where `label` is the `GROUPBY` label argument
- [Array reply](): label-value pairs ([Bulk string reply](), [Bulk string reply]()):
  - 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](): either a single pair ([Bulk string reply](), [Bulk string reply]()): the `GROUPBY` label argument and value, or empty array
- [Array reply](): a single pair ([Bulk string reply](), [Bulk string reply]()):  the string `__reducer__` and the reducer argument
- [Array reply](): a single pair ([Bulk string reply](), [Bulk string reply]()): the string `__source__` and the time series key names separated by ","
- [Array reply](): timestamp-value pairs ([Integer reply](), [Simple string reply]()) representing all samples/aggregations matching the range

**RESP3:**

If `GROUPBY label REDUCE reducer` is not specified:

[Map reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Map reply]() or [Array reply](): 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](): timestamp-value pairs ([Integer reply](), [Double reply]()) representing all samples/aggregations matching the range

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.

title: Examples

text:
Retrieve maximum stock price per timestamp

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.

Calculate average stock price and retrieve maximum average

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

Group query results

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

Filter query by value

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

Query using a label

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

title: See also

text: [`TS.RANGE`]() | [`TS.MREVRANGE`]() | [`TS.REVRANGE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.MREVRANGE

summary: Query a range across multiple time-series by filters in reverse direction

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

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

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)

title: Required arguments

title: Optional arguments

text:
LATEST (since RedisTimeSeries 1.8)

is used when a time series is a compaction. With `LATEST`, TS.MREVRANGE 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.MREVRANGE 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`.

FILTER_BY_TS ts... (since RedisTimeSeries 1.6)

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.

FILTER_BY_VALUE min max (since RedisTimeSeries 1.6)

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.

WITHLABELS

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.

SELECTED_LABELS label... (since RedisTimeSeries 1.6)

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.

COUNT count

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

When used together with `AGGREGATION`: limits the number of reported buckets.

ALIGN align (since RedisTimeSeries 1.6)

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>Note: When not provided, alignment is set to `0`.</note>

AGGREGATION aggregator bucketDuration

per time series, aggregates samples into time buckets, where:

  - `aggregator` takes one of the following aggregation types:

    | `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.

  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`.

[BUCKETTIMESTAMP bt] (since RedisTimeSeries 1.8)

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)        |

[EMPTY] (since RedisTimeSeries 1.8)

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.

GROUPBY label REDUCE reducer (since RedisTimeSeries 1.6)

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.

  - `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 the 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>Notes:
  - 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>

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

title: Examples

text:
Retrieve maximum stock price per timestamp

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.MREVRANGE - + 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) 1020
         2) 120
      2) 1) (integer) 1010
         2) 110
      3) 1) (integer) 1000
         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.

Calculate average stock price and retrieve maximum average

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 in reverse direction.

127.0.0.1:6379> TS.MREVRANGE - + 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) 3000
         2) 310
      2) 1) (integer) 2000
         2) 210
      3) 1) (integer) 1000
         2) 110

Group query results

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.MREVRANGE - + 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) 1548149185000
         2) 45
      2) 1) (integer) 1548149180000
         2) 90
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

Filter query by value

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.MREVRANGE - + 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

Query using a label

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.MREVRANGE - + SELECTED_LABELS team FILTER metric=cpu
1) 1) "ts1"
   2) 1) 1) "team"
         2) (nil)
   3) 1) 1) (integer) 1548149185000
         2) 45
      2) 1) (integer) 1548149180000
         2) 90
2) 1) "ts2"
   2) 1) 1) "team"
         2) (nil)
   3) 1) 1) (integer) 1548149180000
         2) 99

title: Return information

text:
**RESP2:**

If `GROUPBY label REDUCE reducer` is not specified:

[Array reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Array reply](): label-value pairs ([Bulk string reply](), [Bulk string reply]())
  - 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
- [Array reply](): timestamp-value pairs ([Integer reply](), [Simple string reply]()) representing all samples/aggregations matching the range in reverse chronological order

If `GROUPBY label REDUCE reducer` is specified:

[Array reply](): for each group of time series matching the specified filters, the following is reported:
- [Bulk string reply]() with the format `label=value` where `label` is the `GROUPBY` label argument
- [Array reply](): a single pair ([Bulk string reply](), [Bulk string reply]()): the `GROUPBY` label argument and value
- [Array reply](): a single pair ([Bulk string reply](), [Bulk string reply]()):  the string `__reducer__` and the reducer argument
- [Array reply](): a single pair ([Bulk string reply](), [Bulk string reply]()): the string `__source__` and the time series key names separated by ","
- [Array reply](): timestamp-value pairs ([Integer reply](), [Simple string reply]()) representing all samples/aggregations matching the range in reverse chronological order

**RESP3:**

If `GROUPBY label REDUCE reducer` is not specified:

[Map reply](): for each time series matching the specified filters, the following is reported:
- [Bulk string reply](): The time series key name
- [Map reply]() or [Array reply](): 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
- Additional metadata including aggregators information
- [Array reply](): timestamp-value pairs ([Integer reply](), [Double reply]()) representing all samples/aggregations matching the range in reverse chronological order

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.

title: See also

text: [`TS.MRANGE`]() | [`TS.RANGE`]() | [`TS.REVRANGE`]()

title: Related topics

text: [RedisTimeSeries]()


title: TS.QUERYINDEX

title: Overview

text:
This command's behavior varies in clustered Redis environments. See the [multi-key operations]() page for more information.

Get all time series keys matching a filter list. Note: all matching keys will be listed, whether or not the user has read access.

[Examples](#examples)

title: Required arguments

text:
filterExpr...

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>Notes:
   - 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 a time series is a temperature time series of a study room.
   - Whitespaces are unallowed in a filter expression except between quotes or double quotes in values - e.g., `x="y y"` or `x='(y y,z z)'`.
   </note>

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

title: Examples

text:
Find keys by location and sensor type

Create a set of sensors to measure temperature and humidity in your study and kitchen.

127.0.0.1:6379> TS.CREATE telemetry:study:temperature LABELS room study type temperature
OK
127.0.0.1:6379> TS.CREATE telemetry:study:humidity LABELS room study type humidity
OK
127.0.0.1:6379> TS.CREATE telemetry:kitchen:temperature LABELS room kitchen type temperature
OK
127.0.0.1:6379> TS.CREATE telemetry:kitchen:humidity LABELS room kitchen type humidity
OK

Retrieve keys of all time series representing sensors located in the kitchen.

127.0.0.1:6379> TS.QUERYINDEX room=kitchen
1) "telemetry:kitchen:humidity"
2) "telemetry:kitchen:temperature"

To retrieve the keys of all time series representing sensors that measure temperature, use this query:

127.0.0.1:6379> TS.QUERYINDEX type=temperature
1) "telemetry:kitchen:temperature"
2) "telemetry:study:temperature"

text:
| Redis
Software | Redis
Cloud | Notes |
|:----------------------|:-----------------|:------|
| ✅ Supported
 | ✅ Flexible & Annual
✅ Free & Fixed</nobr> |  |

title: Return information

text:
**RESP2:**

One of the following:
* [Array reply]() where each element is a [Bulk string reply](): a time series key. The array is empty if no time series matches the filter.
* [Simple error reply]() in these cases: invalid filter expression.

**RESP3:**

One of the following:
* [Set reply]() where each element is a [Bulk string reply](): a time series key. The set is empty if no time series matches the filter.
* [Simple error reply]() in these cases: invalid filter expression.

title: See also

text: [`TS.CREATE`]() | [`TS.MRANGE`]() | [`TS.MREVRANGE`]() | [`TS.MGET`]()

title: Related topics

text: [RedisTimeSeries]()