{
  "id": "ts.nrange",
  "title": "TS.NRANGE",
  "url": "https://redis.io/docs/latest/commands/ts.nrange/",
  "summary": "Query a range across multiple time series in forward direction, returning the results grouped by timestamp",
  "tags": [
    "docs",
    "develop",
    "stack",
    "oss",
    "rs",
    "rc",
    "oss",
    "kubernetes",
    "clients"
  ],
  "last_updated": "2026-07-30T08:54:30-07:00",
  "page_type": "content",
  "content_hash": "3aeda8ae571fc9b32468cdc7a5d975c98a3d473914011d7cfc33991658c81bca",
  "sections": [
    {
      "id": "overview",
      "title": "Overview",
      "role": "overview",
      "text": "Query a range across an explicit list of time series in the forward direction. `TS.NRANGE` groups results by timestamp: for each timestamp, it returns the timestamp followed by one value per time series, in the order the keys are given. With `AGGREGATION`, a time series can contribute more than one value per timestamp — one for each aggregator requested. A time series with no sample at a given timestamp returns `NaN` for that timestamp.\n\n\nIn a Redis cluster, all specified keys must map to the same hash slot. `TS.NRANGE` is a [single hash slot](https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec#key-distribution-model) command; it does not split a request across shards or merge replies from multiple hash slots.\n\n\n[Examples](#examples)"
    },
    {
      "id": "required-arguments",
      "title": "Required arguments",
      "role": "content",
      "text": "<details open>\n<summary><code>numkeys</code></summary>\n\nis the number of time series keys that follow. It must be a positive integer and must equal the number of `key` arguments.\n</details>\n\n<details open>\n<summary><code>key [key ...]</code></summary>\n\nare the explicit time series keys to query. Key order and duplicate keys are significant: the reply has one value per `key` argument, in the order the keys are given. A repeated key produces a separate value for each occurrence.\n</details>\n\n<details open>\n<summary><code>fromTimestamp</code></summary>\n\nis the start timestamp for the range query (integer Unix timestamp in milliseconds) or `-` to denote the timestamp of the earliest sample among the specified time series. The range is inclusive.\n</details>\n\n<details open>\n<summary><code>toTimestamp</code></summary>\n\nis the end timestamp for the range query (integer Unix timestamp in milliseconds) or `+` to denote the timestamp of the latest sample among the specified time series. The range is inclusive.\n</details>"
    },
    {
      "id": "optional-arguments",
      "title": "Optional arguments",
      "role": "parameters",
      "text": "<details open>\n<summary><code>LATEST</code></summary>\n\nis used when a time series is a compaction. With `LATEST`, TS.NRANGE 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.NRANGE does not report the latest, possibly partial, bucket. When a time series is not a compaction, `LATEST` is ignored.\n\nThe 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`.\n</details>\n\n<details open>\n<summary><code>FILTER_BY_TS ts...</code></summary>\n\nfilters samples by a list of specific timestamps. A sample passes the filter if its exact timestamp is specified and falls within `[fromTimestamp, toTimestamp]`. Samples are filtered before pivoting.\n\nWhen used together with `AGGREGATION`: samples are filtered before being aggregated.\n</details>\n\n<details open>\n<summary><code>FILTER_BY_VALUE min max</code></summary>\n\nfilters samples by minimum and maximum values. A sample passes the filter if its value is within the inclusive range `[min, max]`. `min` and `max` cannot be NaN values. Samples are filtered before pivoting.\n\nWhen used together with `AGGREGATION`: samples are filtered before being aggregated.\n</details>\n\n<details open>\n<summary><code>COUNT count</code></summary>\n\nlimits the number of returned entries, keeping those with the lowest timestamps. The limit is applied after the per-timestamp merge.\n</details>\n\n<details open>\n<summary><code>ALIGN align</code></summary>\n\nis a time bucket alignment control for `AGGREGATION`. It controls the time bucket timestamps by changing the reference timestamp on which a bucket is defined.\n\n`align` values include:\n\n - `start` or `-`: The reference timestamp will be the query start interval time (`fromTimestamp`) which can't be `-`\n - `end` or `+`: The reference timestamp will be the query end interval time (`toTimestamp`) which can't be `+`\n - A specific timestamp: align the reference timestamp to a specific time\n\n<note><b>Note:</b> When not provided, alignment is set to `0`.</note>\n</details>\n\n<details open>\n<summary><code>AGGREGATION aggregators [aggregators ...] bucketDuration</code></summary>\n\naggregates samples into time buckets.\n\nProvide one or more aggregators per key, as a separate argument in the same order as the keys: the first argument applies to the first key, and so on. The number of these arguments must equal `numkeys`, and all keys share the same `bucketDuration`.\n\nEach per-key argument is either a single aggregator or a comma-separated list of aggregators (for example `avg,max`), exactly as in [`TS.RANGE`](https://redis.io/docs/latest/commands/ts.range/); no whitespace is allowed. A key contributes one value for each aggregator you list for it, and a key's values appear together in the reply, in the order you list its aggregators. To compute several aggregations for one series, give that key a comma-separated list such as `min,max`.\n\n  - each `aggregator` is one of the following:\n\n    | aggregator   | Description                                                     |\n    | ------------ | --------------------------------------------------------------- |\n    | `avg`        | Arithmetic mean of all non-NaN values                           |\n    | `sum`        | Sum of all non-NaN values                                       |\n    | `min`        | Minimum non-NaN value                                           |\n    | `max`        | Maximum non-NaN value                                           |\n    | `range`      | Difference between the maximum and the minimum non-NaN values   |\n    | `count`      | Number of non-NaN values                                        |\n    | `countNaN`   | Number of NaN values                                            |\n    | `countAll`   | Number of values, including NaN and non-NaN                     |\n    | `first`      | The non-NaN value with the lowest timestamp in the bucket       |\n    | `last`       | The non-NaN value with the highest timestamp in the bucket      |\n    | `std.p`      | Population standard deviation of the non-NaN values             |\n    | `std.s`      | Sample standard deviation of the non-NaN values                 |\n    | `var.p`      | Population variance of the non-NaN values                       |\n    | `var.s`      | Sample variance of the non-NaN values                           |\n    | `twa`        | Time-weighted average over the bucket's timeframe (ignores NaN values) |\n\n  - `bucketDuration` is the duration of each bucket, in milliseconds.\n\n  Without `ALIGN`, bucket start times are multiples of `bucketDuration`.\n\n  With `ALIGN align`, bucket start times are multiples of `bucketDuration` with remainder `align % bucketDuration`.\n\n  The first bucket start time is less than or equal to `fromTimestamp`.\n</details>\n\n<details open>\n<summary><code>[BUCKETTIMESTAMP bt]</code></summary>\n\ncontrols how bucket timestamps are reported.\n\n| `bt`             | Timestamp reported for each bucket                            |\n| ---------------- | ------------------------------------------------------------- |\n| `-` or `start`   | the bucket's start time (default)                             |\n| `+` or `end`     | the bucket's end time                                         |\n| `~` or `mid`     | the bucket's mid time (rounded down if not an integer)        |\n</details>\n\n<details open>\n<summary><code>[EMPTY]</code></summary>\n\nis a flag, which, when specified, reports aggregations also for empty buckets.\n\n| aggregator           | Value reported for each empty bucket |\n| -------------------- | ------------------------------------ |\n| `sum`, `count`       | `0`                                  |\n| `last`               | The value of the last sample before the bucket's start. `NaN` when no such sample. |\n| `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. |\n| `min`, `max`, `range`, `avg`, `first`, `std.p`, `std.s` | `NaN` |\n\nRegardless 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. When a bucket is reported for one key but a different key has no data in that bucket, that key's value is `NaN`.\n</details>"
    },
    {
      "id": "examples",
      "title": "Examples",
      "role": "example",
      "text": "<details open>\n<summary><b>Pivot raw samples from multiple series by timestamp</b></summary>\n\nCreate two time series and add samples at partially overlapping timestamps.\n\n[code example]\n\nQuery both series and group the samples by timestamp. One entry is returned for every distinct timestamp produced by at least one key, with the values ordered by input key. A key with no sample at that timestamp has a `NaN` value.\n\n[code example]\n</details>\n\n<details open>\n<summary><b>Aggregate each series with a per-key aggregator</b></summary>\n\nIn aggregation mode, supply one aggregator per key, in key order. Here `{sensor}:1` is aggregated with `avg` and `{sensor}:2` with `sum`, both over 1000-millisecond buckets.\n\n[code example]\n</details>\n\n<details open>\n<summary><b>Apply multiple aggregators to a series</b></summary>\n\nTo compute several aggregators for a single key, pass them as a comma-separated list. Here `{sensor}:1` uses `avg,max` (two values) and `{sensor}:2` uses `sum` (one value). Each timestamp's values are a single flat list: `{sensor}:1`'s `avg`, then its `max`, then `{sensor}:2`'s `sum`.\n\n[code example]\n</details>"
    },
    {
      "id": "details",
      "title": "Details",
      "role": "content",
      "text": ""
    },
    {
      "id": "semantics",
      "title": "Semantics",
      "role": "content",
      "text": "`TS.NRANGE` behaves like running a compatible [`TS.RANGE`](https://redis.io/docs/latest/commands/ts.range/) over each input key and then performing a server-side outer join by timestamp. Rows are returned from the lowest timestamp to the highest.\n\nIn raw mode (no `AGGREGATION`):\n\n- One entry is returned for every distinct timestamp produced by at least one key.\n- If a key has no sample at that timestamp, that key's value is `NaN`.\n\nIn aggregation mode (with `AGGREGATION`):\n\n- One aggregation applies to each key, in key order; the number of these arguments must equal `numkeys`, and all keys share one `bucketDuration`.\n- Each per-key argument is a single aggregator or a comma-separated list of aggregators, and a key contributes one value per aggregator.\n- Each timestamp's values form a single flat list, ordered by key and, within each key, by the order its aggregators are listed. For example, `avg,max` for the first key and `sum` for the second produce values of the form `[avg, max, sum]`.\n- When a key has no data for a given bucket, all of that key's values are `NaN`.\n- With `EMPTY`, empty buckets can produce rows in which every value is `NaN`."
    },
    {
      "id": "nan-values",
      "title": "NaN values",
      "role": "content",
      "text": "A `NaN` value can mean that a key had no sample at that timestamp or no samples at that time bucket, or that the key stored or aggregated to a real `NaN`. These two cases are indistinguishable in the reply.\n\n| Case                                            | Value                                           |\n| ----------------------------------------------- | ----------------------------------------------- |\n| Key has a sample at that timestamp               | The sample value                                |\n| Key has no sample at that timestamp              | `NaN`                                           |\n| Key has aggregated data for that time bucket     | The aggregated value                            |\n| Key has no samples at that time bucket           | `NaN`                                           |\n| Key stores or aggregates to a real `NaN`         | `NaN`, indistinguishable from no data           |"
    },
    {
      "id": "redis-software-and-redis-cloud-compatibility",
      "title": "Redis Software and Redis Cloud compatibility",
      "role": "content",
      "text": "| Redis<br />Software | Redis<br />Cloud | <span style=\"min-width: 9em; display: table-cell\">Notes</span> |\n|:----------------------|:-----------------|:------|\n| <span title=\"Not supported\">&#x274c; Not supported</span><br /> | <span title=\"Not supported\">&#x274c; Flexible & Annual</span><br /><span title=\"Not supported\">&#x274c; Free & Fixed</nobr></span> |  |"
    },
    {
      "id": "return-information",
      "title": "Return information",
      "role": "returns",
      "text": "**RESP2:**\n\nOne of the following:\n* [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) with one entry per timestamp, ordered by increasing timestamp. Each entry is an [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) composed of an [Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers) (the timestamp) and a flat [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) of [Simple string reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-strings) values. The values are concatenated across keys in input order; with `AGGREGATION`, each key contributes one value per aggregator, otherwise one value per key. A missing value is reported as `NaN`. The reply is an empty array when no samples match.\n* [Simple error reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-errors) in these cases: invalid arguments, wrong number of aggregators, unknown aggregation type, wrong key type, etc.\n\n**RESP3:**\n\nOne of the following:\n* [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) with one entry per timestamp, ordered by increasing timestamp. Each entry is an [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) composed of an [Integer reply](https://redis.io/docs/latest/develop/reference/protocol-spec#integers) (the timestamp) and a flat [Array reply](https://redis.io/docs/latest/develop/reference/protocol-spec#arrays) of [Double reply](https://redis.io/docs/latest/develop/reference/protocol-spec#doubles) values. The values are concatenated across keys in input order; with `AGGREGATION`, each key contributes one value per aggregator, otherwise one value per key. A missing value is reported as `NaN`. The reply is an empty array when no samples match.\n* [Simple error reply](https://redis.io/docs/latest/develop/reference/protocol-spec#simple-errors) in these cases: invalid arguments, wrong number of aggregators, unknown aggregation type, wrong key type, etc."
    },
    {
      "id": "see-also",
      "title": "See also",
      "role": "related",
      "text": "[`TS.NREVRANGE`](https://redis.io/docs/latest/commands/ts.nrevrange/) | [`TS.RANGE`](https://redis.io/docs/latest/commands/ts.range/) | [`TS.MRANGE`](https://redis.io/docs/latest/commands/ts.mrange/)"
    },
    {
      "id": "related-topics",
      "title": "Related topics",
      "role": "related",
      "text": "[RedisTimeSeries](https://redis.io/docs/latest/develop/data-types/timeseries/)"
    }
  ],
  "examples": [
    {
      "id": "examples-ex0",
      "language": "plaintext",
      "code": "127.0.0.1:6379> TS.CREATE {sensor}:1\nOK\n127.0.0.1:6379> TS.CREATE {sensor}:2\nOK\n127.0.0.1:6379> TS.MADD {sensor}:1 1000 10 {sensor}:1 2000 12\n1) (integer) 1000\n2) (integer) 2000\n127.0.0.1:6379> TS.MADD {sensor}:2 1000 20 {sensor}:2 3000 25\n1) (integer) 1000\n2) (integer) 3000",
      "section_id": "examples"
    },
    {
      "id": "examples-ex1",
      "language": "plaintext",
      "code": "127.0.0.1:6379> TS.NRANGE 2 {sensor}:1 {sensor}:2 - +\n1) 1) (integer) 1000\n   2) 1) 10\n      2) 20\n2) 1) (integer) 2000\n   2) 1) 12\n      2) NaN\n3) 1) (integer) 3000\n   2) 1) NaN\n      2) 25",
      "section_id": "examples"
    },
    {
      "id": "examples-ex2",
      "language": "plaintext",
      "code": "127.0.0.1:6379> TS.MADD {sensor}:1 1000 10 {sensor}:1 1100 20 {sensor}:1 2000 30\n1) (integer) 1000\n2) (integer) 1100\n3) (integer) 2000\n127.0.0.1:6379> TS.MADD {sensor}:2 1000 5 {sensor}:2 1100 15 {sensor}:2 2000 25\n1) (integer) 1000\n2) (integer) 1100\n3) (integer) 2000\n127.0.0.1:6379> TS.NRANGE 2 {sensor}:1 {sensor}:2 - + AGGREGATION avg sum 1000\n1) 1) (integer) 1000\n   2) 1) 15\n      2) 20\n2) 1) (integer) 2000\n   2) 1) 30\n      2) 25",
      "section_id": "examples"
    },
    {
      "id": "examples-ex3",
      "language": "plaintext",
      "code": "127.0.0.1:6379> TS.NRANGE 2 {sensor}:1 {sensor}:2 - + AGGREGATION avg,max sum 1000\n1) 1) (integer) 1000\n   2) 1) 15\n      2) 20\n      3) 20\n2) 1) (integer) 2000\n   2) 1) 30\n      2) 30\n      3) 25",
      "section_id": "examples"
    }
  ]
}
