In MongoDB, the $range
aggregation pipeline operator returns a generated sequence of numbers in an array.
This sequence of numbers is based on the input values you provide.
Syntax
The syntax goes like this:
{ $range: [ <start>, <end>, <non-zero step> ] }
Where <start>
is the start and <end>
is the end of the sequence. Each of these can be any valid expression that resolves to an integer.
<non-zero step>
is an optional argument that defaults to 1. This argument allows you to specify an increment value. If provided, it must be a valid expression that resolves to a non-zero integer.
Example
Suppose we have a collection called range
with the following documents:
{ "_id" : 1, "start" : 0, "end" : 5 } { "_id" : 2, "start" : 1, "end" : 5 }
We can use the $range
operator to return an array based on the values in those documents.
db.range.aggregate(
[
{ $match: { _id: { $in: [ 1, 2 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
result: { $range: [ "$start", "$end" ] }
}
}
]
)
Result:
{ "start" : 0, "end" : 5, "result" : [ 0, 1, 2, 3, 4 ] } { "start" : 1, "end" : 5, "result" : [ 1, 2, 3, 4 ] }
In this case, we didn’t provide a third argument, and so $range
uses its default step value of 1. Therefore, the array elements increment by 1.
Add an Explicit Increment
We can add a third argument to explicitly specify how much each array element should increment by.
Suppose our collection contains the following documents:
{ "_id" : 3, "start" : 0, "end" : 5, "step" : 1 } { "_id" : 4, "start" : 0, "end" : 10, "step" : 2 } { "_id" : 5, "start" : 1, "end" : 10, "step" : 2 } { "_id" : 6, "start" : 100, "end" : 150, "step" : 10 }
These documents have got a step
field, and so we can use that field for the incrementing value for the respective document.
Now let’s apply $range
to those documents, and include the step
field as a third argument:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 3, 4, 5, 6 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
step: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
{ "start" : 0, "end" : 5, "step" : 1, "result" : [ 0, 1, 2, 3, 4 ] } { "start" : 0, "end" : 10, "step" : 2, "result" : [ 0, 2, 4, 6, 8 ] } { "start" : 1, "end" : 10, "step" : 2, "result" : [ 1, 3, 5, 7, 9 ] } { "start" : 100, "end" : 150, "step" : 10, "result" : [ 100, 110, 120, 130, 140 ] }
Negative Step Values
The step can be a negative value, although this needs to be done in the context of decrementing from a higher start
number to a lower end
number.
Let’s add a few more documents to our collection:
{ "_id" : 7, "start" : 0, "end" : 5, "step" : -1 } { "_id" : 8, "start" : 5, "end" : 0, "step" : -1 } { "_id" : 9, "start" : 0, "end" : -5, "step" : -1 }
Now let’s apply $range
to those documents:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 7, 8, 9 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
step: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
{ "start" : 0, "end" : 5, "step" : -1, "result" : [ ] } { "start" : 5, "end" : 0, "step" : -1, "result" : [ 5, 4, 3, 2, 1 ] } { "start" : 0, "end" : -5, "step" : -1, "result" : [ 0, -1, -2, -3, -4 ] }
We can see that the first document returned an empty array because the negative step value is outside the range provided by the start
and end
fields.
However, the subsequent documents produced a decrementing range of values.
When the Step is Zero
The step value must be a non-zero integer. Providing a step of 0
returns an error.
Suppose we add the following document to our collection:
{ "_id" : 10, "start" : 1, "end" : 5, "step" : 0 }
Here’s what happens when we apply $range
to that document:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 10 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
uncaught exception: Error: command failed: { "ok" : 0, "errmsg" : "$range requires a non-zero step value", "code" : 34449, "codeName" : "Location34449" } : aggregate failed : _getErrorWithCode@src/mongo/shell/utils.js:25:13 doassert@src/mongo/shell/assert.js:18:14 _assertCommandWorked@src/mongo/shell/assert.js:639:17 assert.commandWorked@src/mongo/shell/assert.js:729:16 DB.prototype._runAggregate@src/mongo/shell/db.js:266:5 DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12 @(shell):1:1
The error message explicitly tells us that $range requires a non-zero step value
.
Null Steps
The step can’t be null
either.
Suppose we have the following document:
{ "_id" : 11, "start" : 1, "end" : 5, "step" : null }
And we apply $range
to it:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 11 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
uncaught exception: Error: command failed: { "ok" : 0, "errmsg" : "$range requires a numeric step value, found value of type:null", "code" : 34447, "codeName" : "Location34447" } : aggregate failed : _getErrorWithCode@src/mongo/shell/utils.js:25:13 doassert@src/mongo/shell/assert.js:18:14 _assertCommandWorked@src/mongo/shell/assert.js:639:17 assert.commandWorked@src/mongo/shell/assert.js:729:16 DB.prototype._runAggregate@src/mongo/shell/db.js:266:5 DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12 @(shell):1:1
This tells us that $range requires a numeric step value, found value of type:null
.
Null Ranges
If the start
and/or end
fields are null
, then an error is returned.
Suppose we have the following document:
{ "_id" : 11, "start" : 1, "end" : 5, "step" : null }
And apply $range
to it:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 11 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
uncaught exception: Error: command failed: { "ok" : 0, "errmsg" : "$range requires a numeric starting value, found value of type: null", "code" : 34443, "codeName" : "Location34443" } : aggregate failed : _getErrorWithCode@src/mongo/shell/utils.js:25:13 doassert@src/mongo/shell/assert.js:18:14 _assertCommandWorked@src/mongo/shell/assert.js:639:17 assert.commandWorked@src/mongo/shell/assert.js:729:16 DB.prototype._runAggregate@src/mongo/shell/db.js:266:5 DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12 @(shell):1:1
This tells us that $range requires a numeric starting value, found value of type: null
.
A similar message would appear if the end value was null.
Here’s a document with a null
end value:
{ "_id" : 13, "start" : 1, "end" : null, "step" : 1 }
Let’s apply $range
:
db.range.aggregate(
[
{ $match: { _id: { $in: [ 13 ] } } },
{
$project:
{
_id: 0,
start: 1,
end: 1,
result: { $range: [ "$start", "$end", "$step" ] }
}
}
]
)
Result:
uncaught exception: Error: command failed: { "ok" : 0, "errmsg" : "$range requires a numeric ending value, found value of type: null", "code" : 34445, "codeName" : "Location34445" } : aggregate failed : _getErrorWithCode@src/mongo/shell/utils.js:25:13 doassert@src/mongo/shell/assert.js:18:14 _assertCommandWorked@src/mongo/shell/assert.js:639:17 assert.commandWorked@src/mongo/shell/assert.js:729:16 DB.prototype._runAggregate@src/mongo/shell/db.js:266:5 DBCollection.prototype.aggregate@src/mongo/shell/collection.js:1058:12 @(shell):1:1
This time it tells us that $range requires a numeric ending value, found value of type: null
.