$bucket (aggregation)
Definition
$bucketCategorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries and outputs a document per each bucket. Each output document contains an
_idfield whose value specifies the inclusive lower bound of the bucket. The output option specifies the fields included in each output document.$bucketonly produces output documents for buckets that contain at least one input document.
Considerations
$bucket and Memory Restrictions
The $bucket stage has a limit of 100 megabytes of RAM. By
default, if the stage exceeds this limit, $bucket returns an
error. To allow more space for stage processing, use the
allowDiskUse option to enable
aggregation pipeline stages to write data to temporary files.
See also:
Syntax
{ $bucket: { groupBy: <expression>, boundaries: [ <lowerbound1>, <lowerbound2>, ... ], default: <literal>, output: { <output1>: { <$accumulator expression> }, ... <outputN>: { <$accumulator expression> } } } }
The $bucket document contains the following fields:
Field | Type | Description | |||
|---|---|---|---|---|---|
expression | An expression to group
documents by. To specify a field path, prefix the field name with a
dollar sign Unless | ||||
array | An array of values based on the groupBy expression that specify the boundaries for each bucket. Each adjacent pair of values acts as the inclusive lower boundary and the exclusive upper boundary for the bucket. You must specify at least two boundaries. The specified values must be in ascending order and all of the same type. The exception is if the values are of mixed numeric types, such as:
For example, an array of
| ||||
literal | Optional. A literal that specifies the If unspecified, each input document must resolve the
The The | ||||
document | Optional. A document that specifies the fields to include in
the output documents in addition to the If you do not specify an If you specify an |
Behavior
$bucket requires at least one of the following conditions to be met
or the operation throws an error:
Each input document resolves the groupBy expression to a value within one of the bucket ranges specified by boundaries, or
A default value is specified to bucket documents whose
groupByvalues are outside of theboundariesor of a different BSON type than the values inboundaries.
If the groupBy expression resolves to an array or a document,
$bucket arranges the input documents into buckets using the
comparison logic from $sort.
Examples
Bucket by Year and Filter by Bucket Results
In mongosh, create a sample collection named
artists with the following documents:
db.artists.insertMany([ { "_id" : 1, "last_name" : "Bernard", "first_name" : "Emil", "year_born" : 1868, "year_died" : 1941, "nationality" : "France" }, { "_id" : 2, "last_name" : "Rippl-Ronai", "first_name" : "Joszef", "year_born" : 1861, "year_died" : 1927, "nationality" : "Hungary" }, { "_id" : 3, "last_name" : "Ostroumova", "first_name" : "Anna", "year_born" : 1871, "year_died" : 1955, "nationality" : "Russia" }, { "_id" : 4, "last_name" : "Van Gogh", "first_name" : "Vincent", "year_born" : 1853, "year_died" : 1890, "nationality" : "Holland" }, { "_id" : 5, "last_name" : "Maurer", "first_name" : "Alfred", "year_born" : 1868, "year_died" : 1932, "nationality" : "USA" }, { "_id" : 6, "last_name" : "Munch", "first_name" : "Edvard", "year_born" : 1863, "year_died" : 1944, "nationality" : "Norway" }, { "_id" : 7, "last_name" : "Redon", "first_name" : "Odilon", "year_born" : 1840, "year_died" : 1916, "nationality" : "France" }, { "_id" : 8, "last_name" : "Diriks", "first_name" : "Edvard", "year_born" : 1855, "year_died" : 1930, "nationality" : "Norway" } ])
The following operation groups the documents into buckets
according to the year_born field and filters based on the count
of documents in the buckets:
db.artists.aggregate( [ // First Stage { $bucket: { groupBy: "$year_born", // Field to group by boundaries: [ 1840, 1850, 1860, 1870, 1880 ], // Boundaries for the buckets default: "Other", // Bucket ID for documents which do not fall into a bucket output: { // Output for each bucket "count": { $sum: 1 }, "artists" : { $push: { "name": { $concat: [ "$first_name", " ", "$last_name"] }, "year_born": "$year_born" } } } } }, // Second Stage { $match: { count: {$gt: 3} } } ] )
- First Stage
The
$bucketstage groups the documents into buckets by theyear_bornfield. The buckets have the following boundaries:[1840, 1850) with inclusive lowerbound
1840and exclusive upper bound1850.[1850, 1860) with inclusive lowerbound
1850and exclusive upper bound1860.[1860, 1870) with inclusive lowerbound
1860and exclusive upper bound1870.[1870, 1880) with inclusive lowerbound
1870and exclusive upper bound1880.If a document did not contain the
year_bornfield or itsyear_bornfield was outside the ranges above, it would be placed in the default bucket with the_idvalue"Other".
The stage includes the output document to determine the fields to return:
FieldDescription_idInclusive lower bound of the bucket.
countCount of documents in the bucket.
artistsArray of documents containing information on each artist in the bucket. Each document contains the artist's
name, which is a concatenation (i.e.$concat) of the artist'sfirst_nameandlast_name.year_born
This stage passes the following documents to the next stage:
{ "_id" : 1840, "count" : 1, "artists" : [ { "name" : "Odilon Redon", "year_born" : 1840 } ] } { "_id" : 1850, "count" : 2, "artists" : [ { "name" : "Vincent Van Gogh", "year_born" : 1853 }, { "name" : "Edvard Diriks", "year_born" : 1855 } ] } { "_id" : 1860, "count" : 4, "artists" : [ { "name" : "Emil Bernard", "year_born" : 1868 }, { "name" : "Joszef Rippl-Ronai", "year_born" : 1861 }, { "name" : "Alfred Maurer", "year_born" : 1868 }, { "name" : "Edvard Munch", "year_born" : 1863 } ] } { "_id" : 1870, "count" : 1, "artists" : [ { "name" : "Anna Ostroumova", "year_born" : 1871 } ] } - Second Stage
The
$matchstage filters the output from the previous stage to only return buckets which contain more than 3 documents.The operation returns the following document:
{ "_id" : 1860, "count" : 4, "artists" : [ { "name" : "Emil Bernard", "year_born" : 1868 }, { "name" : "Joszef Rippl-Ronai", "year_born" : 1861 }, { "name" : "Alfred Maurer", "year_born" : 1868 }, { "name" : "Edvard Munch", "year_born" : 1863 } ] }
Use $bucket with $facet to Bucket by Multiple Fields
You can use the $facet stage to perform multiple
$bucket aggregations in a single stage.
In mongosh, create a sample collection named
artwork with the following documents:
db.artwork.insertMany([ { "_id" : 1, "title" : "The Pillars of Society", "artist" : "Grosz", "year" : 1926, "price" : NumberDecimal("199.99") }, { "_id" : 2, "title" : "Melancholy III", "artist" : "Munch", "year" : 1902, "price" : NumberDecimal("280.00") }, { "_id" : 3, "title" : "Dancer", "artist" : "Miro", "year" : 1925, "price" : NumberDecimal("76.04") }, { "_id" : 4, "title" : "The Great Wave off Kanagawa", "artist" : "Hokusai", "price" : NumberDecimal("167.30") }, { "_id" : 5, "title" : "The Persistence of Memory", "artist" : "Dali", "year" : 1931, "price" : NumberDecimal("483.00") }, { "_id" : 6, "title" : "Composition VII", "artist" : "Kandinsky", "year" : 1913, "price" : NumberDecimal("385.00") }, { "_id" : 7, "title" : "The Scream", "artist" : "Munch", "year" : 1893 /* No price*/ }, { "_id" : 8, "title" : "Blue Flower", "artist" : "O'Keefe", "year" : 1918, "price" : NumberDecimal("118.42") } ])
The following operation uses two $bucket stages within a
$facet stage to create two groupings, one by price and
the other by year:
db.artwork.aggregate( [ { $facet: { // Top-level $facet stage "price": [ // Output field 1 { $bucket: { groupBy: "$price", // Field to group by boundaries: [ 0, 200, 400 ], // Boundaries for the buckets default: "Other", // Bucket ID for documents which do not fall into a bucket output: { // Output for each bucket "count": { $sum: 1 }, "artwork" : { $push: { "title": "$title", "price": "$price" } }, "averagePrice": { $avg: "$price" } } } } ], "year": [ // Output field 2 { $bucket: { groupBy: "$year", // Field to group by boundaries: [ 1890, 1910, 1920, 1940 ], // Boundaries for the buckets default: "Unknown", // Bucket ID for documents which do not fall into a bucket output: { // Output for each bucket "count": { $sum: 1 }, "artwork": { $push: { "title": "$title", "year": "$year" } } } } } ] } } ] )
- First Facet
The first facet groups the input documents by
price. The buckets have the following boundaries:[0, 200) with inclusive lowerbound
0and exclusive upper bound200.[200, 400) with inclusive lowerbound
200and exclusive upper bound400."Other", the
defaultbucket containing documents without prices or prices outside the ranges above.
The
$bucketstage includes the output document to determine the fields to return:FieldDescription_idInclusive lower bound of the bucket.
countCount of documents in the bucket.
artworkArray of documents containing information on each artwork in the bucket.
averagePriceEmploys the
$avgoperator to display the average price of all artwork in the bucket.- Second Facet
The second facet groups the input documents by
year. The buckets have the following boundaries:[1890, 1910) with inclusive lowerbound
1890and exclusive upper bound1910.[1910, 1920) with inclusive lowerbound
1910and exclusive upper bound1920.[1920, 1940) with inclusive lowerbound
1910and exclusive upper bound1940."Unknown", the
defaultbucket containing documents without years or years outside the ranges above.
The
$bucketstage includes the output document to determine the fields to return:FieldDescriptioncountCount of documents in the bucket.
artworkArray of documents containing information on each artwork in the bucket.
- Output
The operation returns the following document:
{ "price" : [ // Output of first facet { "_id" : 0, "count" : 4, "artwork" : [ { "title" : "The Pillars of Society", "price" : NumberDecimal("199.99") }, { "title" : "Dancer", "price" : NumberDecimal("76.04") }, { "title" : "The Great Wave off Kanagawa", "price" : NumberDecimal("167.30") }, { "title" : "Blue Flower", "price" : NumberDecimal("118.42") } ], "averagePrice" : NumberDecimal("140.4375") }, { "_id" : 200, "count" : 2, "artwork" : [ { "title" : "Melancholy III", "price" : NumberDecimal("280.00") }, { "title" : "Composition VII", "price" : NumberDecimal("385.00") } ], "averagePrice" : NumberDecimal("332.50") }, { // Includes documents without prices and prices greater than 400 "_id" : "Other", "count" : 2, "artwork" : [ { "title" : "The Persistence of Memory", "price" : NumberDecimal("483.00") }, { "title" : "The Scream" } ], "averagePrice" : NumberDecimal("483.00") } ], "year" : [ // Output of second facet { "_id" : 1890, "count" : 2, "artwork" : [ { "title" : "Melancholy III", "year" : 1902 }, { "title" : "The Scream", "year" : 1893 } ] }, { "_id" : 1910, "count" : 2, "artwork" : [ { "title" : "Composition VII", "year" : 1913 }, { "title" : "Blue Flower", "year" : 1918 } ] }, { "_id" : 1920, "count" : 3, "artwork" : [ { "title" : "The Pillars of Society", "year" : 1926 }, { "title" : "Dancer", "year" : 1925 }, { "title" : "The Persistence of Memory", "year" : 1931 } ] }, { // Includes documents without a year "_id" : "Unknown", "count" : 1, "artwork" : [ { "title" : "The Great Wave off Kanagawa" } ] } ] }
The C# examples on this page use the sample_mflix database
from the Atlas sample datasets. To learn how to create a
free MongoDB Atlas cluster and load the sample datasets, see
Get Started in the MongoDB .NET/C#
Driver documentation.
The following Movie class models the documents in the sample_mflix.movies
collection:
public class Movie { public string Id { get; set; } public int Runtime { get; set; } public string Title { get; set; } public string Rated { get; set; } public List<string> Genres { get; set; } public string Plot { get; set; } public ImdbData Imdb { get; set; } public int Year { get; set; } public int Index { get; set; } public string[] Comments { get; set; } [] public DateTime LastUpdated { get; set; } }
To use the MongoDB .NET/C# driver to add a $bucket stage to an aggregation
pipeline, call the Bucket() method on a PipelineDefinition object.
The following example creates a pipeline stage that groups incoming documents by the value of their Runtime field, inclusive
of the lower boundary and exclusive of the upper boundary:
var pipeline = new EmptyPipelineDefinition<Movie>() .Bucket( groupBy: m => m.Runtime, boundaries: new List<int>() { 0, 71, 91, 121, 151, 201, 999 });
To customize the $bucket operation, pass an
AggregateBucketOptions
object to the Bucket() method.
The following example performs the same $bucket operation as the previous example,
but groups all documents with a Runtime value greater than 999 into the
default bucket, named "Other":
var bucketOptions = new AggregateBucketOptions<BsonValue>() { DefaultBucket = (BsonValue)"Other" }; var pipeline = new EmptyPipelineDefinition<Movie>() .Bucket( groupBy: m => m.Runtime, boundaries: new List<BsonValue>() { 0, 71, 91, 121, 151, 201, 999 }, options: bucketOptions);
See also: