How do access values in a nested documents in MongoDB? - mongodb

I am trying to find the average sell of each console xbox, ps4, and wii. I am working with nested documents, and I try to access type to filter "sell" using db.console.find({"market.type":"sell"}); but I end up getting "online" type values as well.
Document 1:
_id:("111111111111111111111111")
market:Array
0:Object
type:"sell"
console:"Xbox"
amount:399
1:Object
type:"online"
console:"PS4"
amount:359
2:Object
type:"sell"
console:"xbox"
amount:349

Since you need to filter the sub-documents from a documents so simply find will not work to filter the sub-documents.
You have to use aggregation pipeline as below:
> db.st9.aggregate([
{
$unwind:"$market"
},
{
$match: {"market.type":"sell"}
},
{
$group: {_id:"$market.console", "avg": {$avg:"$market.amount"}, "count": {$sum:1}, "totalSum": {$sum: "$market.amount"} }
}
])
Output:
{ "_id" : "PS4", "avg" : 300, "count" : 1, "totalSum" : 300 }
{ "_id" : "Xbox", "avg" : 359, "count" : 3, "totalSum" : 1077 }
>
For more reference on aggregation pipeline check below official mongo db documentations:
$unwind
$match
$group

Related

MongoDB Sorting: Equivalent Aggregation Query

I have following students collection
{ "_id" : ObjectId("5f282eb2c5891296d8824130"), "name" : "Rajib", "mark" : "1000" }
{ "_id" : ObjectId("5f282eb2c5891296d8824131"), "name" : "Rahul", "mark" : "1200" }
{ "_id" : ObjectId("5f282eb2c5891296d8824132"), "name" : "Manoj", "mark" : "1000" }
{ "_id" : ObjectId("5f282eb2c5891296d8824133"), "name" : "Saroj", "mark" : "1400" }
My requirement is to sort the collection basing on 'mark' field in descending order. But it should not display 'mark' field in final result. Result should come as:
{ "name" : "Saroj" }
{ "name" : "Rahul" }
{ "name" : "Rajib" }
{ "name" : "Manoj" }
Following query I tried and it works fine.
db.students.find({},{"_id":0,"name":1}).sort({"mark":-1})
My MongoDB version is v4.2.8. Now question is what is the equivalent Aggregation Query of the above query. I tried following two queries. But both didn't give me desired result.
db.students.aggregate([{"$project":{"name":1,"_id":0}},{"$sort":{"mark":-1}}])
db.students.aggregate([{"$project":{"name":1,"_id":0,"mark":1}},{"$sort":{"mark":-1}}])
Why it is working in find()?
As per Cursor.Sort, When a set of results are both sorted and projected, the MongoDB query engine will always apply the sorting first.
Why it isn't working in aggregate()?
As per Aggregation Pipeline, The MongoDB aggregation pipeline consists of stages. Each stage transforms the documents as they pass through the pipeline. Pipeline stages do not need to produce one output document for every input document; e.g., some stages may generate new documents or filter out documents.
You need to correct:
You should change pipeline order, because if you have not selected mark field in $project then it will no longer available in further pipelines and it will not affect $sort operation.
db.students.aggregate([
{ "$sort": { "mark": -1 } },
{ "$project": { "name": 1, "_id": 0 } }
])
Playground: https://mongoplayground.net/p/xtgGl8AReeH

Mongodb array of object data conversion

This is my single document of a collection. I want to convert this format data to
{
"_id" : ObjectId("5e311e8bb94999f1be0d5ead"),
"dealer_code" : "123",
"mappinginfo" : [
{
"territory" : "MORADABAD",
"area" : "UPH",
"zone" : "N"
}
],
"active" : NumberInt(1),
}
like this data
{
"_id" : ObjectId("5e311e8bb94999f1be0d5ead"),
"dealer_code" : "123",
"territory" : "MORADABAD",
"area" : "UPH",
"zone" : "N"
"active" : NumberInt(1),
}
Here is the Query.
db.collection.aggregate([
{
$unwind: "$mappinginfo"
},
{
$project: {
_id: 1,
active: 1,
dealer_code: 1,
territory: "$mappinginfo.territory",
area: "$mappinginfo.area",
zone: "$mappinginfo.zone",
}
},
])
db.collection.aggregate([
{ $unwind: "$mappinginfo" },
{
$project:
{
_id: 1,
dealer_code: 1,
active: 1,
territory: "$mappinginfo.territory",
area: "$mappinginfo.area",
zone: "$mappinginfo.zone"
}
},
{ $out: "existing_collection_name" }
])
Make use of aggregation pipeline.
Solution -
db.test.aggregate([
{ $unwind: "$mappinginfo" },
{ $project: {
dealer_code: 1,
territory: "$mappinginfo.territory",
area: "$mappinginfo.area",
zone: "$mappinginfo.zone",
active: 1
}}
]);
Explanation
Aggregation pipeline as the name is, works on data in pipeline. A pipeline has an input, an operator and an output. For the example above -
First stage is an unwind stage which will be fed with an entire collection. The unwind operator will iterate on individual documents and for each document it will create as many copies as are elements in mappinginfo field. For your case it will only create just one copy. After the unwind stage the resulting document would look like this -
[{
"_id" : ObjectId("5e311e8bb94999f1be0d5ead"),
"dealer_code" : "123",
"mappinginfo": {
"territory" : "MORADABAD",
"area" : "UPH",
"zone" : "N"
}
"active" : NumberInt(1),
}]
Notice that the mappinginfo is no more a list.
Next stage is a projection stage. $project operator will simply take the above list of documents as input and for each document either it will simply project the field as it is or will change the value of the field based on what is available in the current document. { "active": 1 } implies projecting the value as it is. { "zone": "$mappinginfo.zone" } implies projecting the value of zone inside mappinginfo field under the name zone at the root level.
More info on both operators -
- $unwind
- $project

MongoDB Group by field and show array of grouped items?

I have a collection of Projects in where projects are like this:
{
"_id" : ObjectId("57e3e55c638cb8b971"),
"allocInvestor" : "Example Investor",
"fieldFoo" : "foo bar",
"name" : "GTP 3 (Roof Lease)"
}
I want to receive a list of projects grouped by allocInvestor field and only show fields: name and id
If I use aggregate and $group like this:
db.getCollection('projects').aggregate([
{"$group" : {
_id:"$allocInvestor", count:{$sum:1}
}
}
])
I receive a count of project per allocInvestor but I need is to receive the list of allocInvestor with subarray of projects per allocInvestor.
I'm using meteor by the way in case that helps. But I first want to get the query right on mongodb then try for meteor.
You can use $push or $addToSet to create a list of name and _id per every group.
$push allows duplicates and $addToSet does not add an element to the list again, if it is already there.
db.getCollection('projects').aggregate([
{ "$group" : { _id : "$allocInvestor",
count : {$sum : 1},
"idList" : {"$addToSet" : "$_id"},
"nameList" : {"$addToSet":"$name"}
}
}
]);
To get the name and _id data in a single list:
db.getCollection('projects').aggregate([
{ "$group" : { _id : "$allocInvestor", "projects" : {"$addToSet" : {id : "$_id", name: "$name"}}}},
{"$project" : {"_id" : 0, allocInvestor : "$_id", "projects" : 1 }}
]);
Use the $$ROOT operator to reference the entire document and then use project to eliminate the fields that you do not require.
db.projects.aggregate([
{"$group" : {
"_id":"$allocInvestor",
"projects" : {"$addToSet" : "$$ROOT"}
}
},
{"$project" : {
"_id":0,
"allocInvestor":"$_id",
"projects._id":1
"projects.name":1
}
}
])

Mongo DB specify date query for all subobjects

I'm still new to MongoDB and I'm not able to achieve the following.
This a object inside one of the collections I have to deal with:
{
"_id" : ObjectId("5306ad28e4b04bd6667b03bf"),
"name" : "FOOBAR",
"Items" : [
{
"price" : 0,
"currency" : "EUR",
"expiryDate" : ISODate("2014-03-15T23:00:00Z"),
},
{
"price" : 0,
"currency" : "EUR",
"expiryDate" : ISODate("2015-03-15T23:00:00Z"),
},
{
"currency" : "EUR",
"expiryDate" : ISODate("2015-04-16T23:00:00Z"),
}, ...}
I now need to find objects, where the timestamp "expiryDate" for all sub-objects inside "Items" is less than a certain value (ISODate).
Here's what it tried:
1. first try
db.COLL.findOne({"Items.expiryDate": { $lt : ISODate("2015-02-10T00:00:00.000Z") }}));"
This will also return object where only one "expiryDate" is less thane.
second try
db.COLL.findOne({"Items": { $all : [ "$elemMatch" : { expiryDate: { $lt: ISODate(\"2015-02-10T00:00:00.000Z\") }} ] }}));"
Every query gives me only items where some but not all subobjects have a timestamp less than a certain time.
Please help me write this query!!
You can use the aggregation Framework to achieve this.
Using the $size operator we can find the size of Items which will be used in the later stages of aggregation.
The $unwind deconstructs the Items array so that we can apply the condition on individual items in the next $match stage.
In the $group stage we calculate the size of filtered Items and compare it with the original size using $cmp operator.This is done to identify documents where all sub-documents are less than the Date supplied in the $match condition.'
db.Coll.aggregate([
{'$project': {'name': 1, 'size': {'$size': '$Items'},'Items': 1}},
{'$unwind': '$Items'},
{'$match': {'Items.expiryDate': { $lt: ISODate("2015-03-15T23:40:00.000Z")}}},
{'$group': { '_id': '$_id','Items': { '$push': '$Items'},'size':{'$first': '$size'}, 'newsize': {'$sum':1}}},
{'$project': {'cmp_value': { $cmp : ['$size', '$newsize']},'name' :1 ,'Items': 1}},
{'$match': {'cmp_value': 0}}
])
Even though it's very old question, let me write my comment for others.
db.collection.findOne
will return only one record. Instead you should use
db.collection.find
Hope this helps!

how to sort before querying in the embedded document

I know how to sort the embedded document after the find results but how do I sort before the find so that the query itself is run on the sorted array ? I know this must be possible if I use aggregate but i really like to know if this is possible without that so that I understand it better how it works.
This is my embedded document
"shipping_charges" : [
{
"region" : "region1",
"weight" : 500,
"rate" : 10
},
{
"region" : "Bangalore HQ",
"weight" : 200,
"rate" : 40
},
{
"region" : "region2",
"weight" : 1500,
"rate" : 110
},
{
"region" : "region3",
"weight" : 100,
"rate" : 50
},
{
"region" : "Bangalore HQ",
"weight" : 100,
"rate" : 150
}
]
This is the query i use to match the 'region' and the 'weight' to get the pricing for that match ..
db.clients.find( { "shipping_charges.region" : "Bangalore HQ" , "shipping_charges.weight" : { $gte : 99 } }, { "shipping_charges.$" : 1 } ).pretty()
This query currently returns me the
{
"shipping_charges" : [
{
"region" : "Bangalore HQ",
"weight" : 200,
"rate" : 40
}
]
}
The reason it possibly returns this set is because of the order in which it appears(& matches) in the embedded document.
But, I want this to return me the last set that best matches to closest slab of the weight(100grams)
What changes required in my existing query so that I can sort the embedded document before the find runs on them to get the results as I want it ?
If for any reasons you are sure this cant be done without a MPR, let me know so that i can stay away from this method and focus only on MPR to get the desired results as I want it .
You can use an aggregation pipeline instead of map-reduce:
db.clients.aggregate([
// Filter the docs to what we're looking for.
{$match: {
'shipping_charges.region': 'Bangalore HQ',
'shipping_charges.weight': {$gte: 99}
}},
// Duplicate the docs, once per shipping_charges element
{$unwind: '$shipping_charges'},
// Filter again to get the candidate shipping_charges.
{$match: {
'shipping_charges.region': 'Bangalore HQ',
'shipping_charges.weight': {$gte: 99}
}},
// Sort those by weight, ascending.
{$sort: {'shipping_charges.weight': 1}},
// Regroup and take the first shipping_charge which will be the one closest to 99
// because of the sort.
{$group: {_id: '$_id', shipping_charges: {$first: '$shipping_charges'}}}
])
You could also use find, but you'd need to pre-sort the shipping_charges array by weight in the documents themselves. You can do that by using a $push update with the $sort modifier:
db.clients.update({}, {
$push: {shipping_charges: {$each: [], $sort: {weight: 1}}}
}, {multi: true})
After doing that, your existing query will return the right element:
db.clients.find({
"shipping_charges.region" : "Bangalore HQ",
"shipping_charges.weight" : { $gte : 99 }
}, { "shipping_charges.$" : 1 } )
You would, of course, need to consistently include the $sort modifier on any further updates to your docs' shipping_charges array to ensure it stays sorted.