Mongodb unwind and match VS match and unwind - mongodb

I'm looking to optimize the MongoDB performance by minimizing the number of records to unwind.
I do like:
unwind(injectionRecords),
match("machineID" : "machine1"),
count(counter)
But because of huge data unwind operation takes a lot of time and then it matches from unwind.
It unwinds all the 4 records then matches machineID from result and give me count of it.
Instead I would like to do something like :
match("machineID": "machine1"),
unwind(injectionRecords)
count(counter)
So, it would match records having machineID and unwind only 2 instead of 4 and give me the count of it.
Is this possible? How can I do this?
Here are sample docs,
{
"_id" : ObjectId("5981c24b90a7c215e4f166dd"),
"machineID" : "machine1",
"injectionRecords" : [
{
"startTime" : ISODate("2017-08-02T17:45:04.779+05:30"),
"endTime" : ISODate("2017-08-02T17:45:07.763+05:30"),
"counter" : 1
},
{
"startTime" : ISODate("2017-08-02T17:45:24.417+05:30"),
"endTime" : ISODate("2017-08-02T17:45:27.402+05:30"),
"counter" : 2
}
]
},
{
"_id" : ObjectId("5981c24b90a7c215e4f166de"),
"machineID" : "machine2",
"injectionRecords" : [
{
"startTime" : ISODate("2017-08-02T17:46:04.779+05:30"),
"endTime" : ISODate("2017-08-02T17:46:07.763+05:30"),
"counter" : 1
},
{
"startTime" : ISODate("2017-08-02T17:46:24.417+05:30"),
"endTime" : ISODate("2017-08-02T17:46:27.402+05:30"),
"counter" : 2
}
]
}

The following query will return a count of injectionRecords for a given machineId. I think this is what you are asking for.
db.collection.aggregate([
{$match: {machineID: 'machine1'}},
{$unwind: '$injectionRecords'},
{$group:{_id: "$_id",count:{$sum:1}}}
])
Of course, this query (where the unwind takes place before the match) is functionally equivalent:
db.collection.aggregate([
{$unwind: '$injectionRecords'},
{$match: {machineID: 'machine1'}},
{$group:{_id: "$_id",count:{$sum:1}}}
])
However, running that query with explain ...
db.collection.aggregate([
{$unwind: '$injectionRecords'},
{$match: {machineID: 'machine1'}},
{$group:{_id: "$_id",count:{$sum:1}}}
], {explain: true})
... shows that the unwind stage applies to the entire collection whereas if you match before unwinding then only the matched documents are unwound.

Related

I need to count how many children orgs are assigned to a parent org in MongoDB

I'm new to the MongoDB world. I'm trying to figure out how to count the number of children organizations assigned to a parent organization. I have documents that have this general structure:
{
"_id" : "001",
"parentOrganization" : {
"organizationId" : "pOrg1"
},
"childOrganization" : {
"organizationId" : "cOrg1"
}
},
{
"_id" : "002",
"parentOrganization" : {
"organizationId" : "pOrg1"
},
"childOrganization" : {
"organizationId" : "cOrg2"
}
},
{
"_id" : "003",
"parentOrganization" : {
"organizationId" : "pOrg2"
},
"childOrganization" : {
"organizationId" : "cOrg3"
}
}
Each document has a parentOrganization with an associated childOrganization. There may be multiple documents with the same parentOrganization, but different childOrganizations. There may also be multiple documents with the same parent/child relationship. Additionally, there may even be a case where a child org may associate with multiple parent orgs.
I'm trying to group by parentOrganization and then count the number of unique childOrganization's associated with each parentOrganization, as well as display the unique id's.
I have tried using an aggregation framework with $match and $group, but I'm still not getting into the child organization parts to count them. Here is what I'm currently attempting:
var s1 = {$match: {"parentOrganization.organizationId": {$exists: true}}};
var s2 = {$group: {_id: "$parentOrganization.organizationId", count: {$sum: "$childOrganization.organizationId"}}};
db.collection.aggregate(s1, s2);
My results are returning the parentOrganization, but my $sum is not returning the number of associated childOrganizations:
/* 1 */
{
"_id" : "pOrg1",
"count" : 0
}
/* 2 */
{
"_id" : "pOrg2",
"count" : 0
}
I get the feeling it is a bit more complicated than my limited knowledge has access to at this time. What details am I missing in this query?
Your $sum is referencing the childOrganization.organizationId value, which is a string. When $sum references a string, it will return the value 0.
I was a unsure of exactly what you were asking for, but I believe that these aggregations can help you on your way.
This will return a count of documents groups by the parentOrganization.organizationId
db.collection.aggregate({$group: {"_id":"$parentOrganization.organizationId", "count": {"$sum": 1}}})
Output:
{ "_id" : "pOrg2", "count" : 1 }
{ "_id" : "pOrg1", "count" : 2 }
This will return a count of unique parent/child organizations:
db.collection.aggregate(
{$group: {"_id": {"parentOrganization": "$parentOrganization.organizationId", "childOrganization": "$childOrganization.organizationId"}, "count":{$sum:1}}})
Output:
{ "_id" : { "parentOrganization" : "pOrg2", "childOrganization" : "cOrg3" }, "count" : 1 }
{ "_id" : { "parentOrganization" : "pOrg1", "childOrganization" : "cOrg2" }, "count" : 1 }
{ "_id" : { "parentOrganization" : "pOrg1", "childOrganization" : "cOrg1" }, "count" : 1 }
This will return a count of unique child organizations and get the set of unique child organizations as well using $addToSet. One caveat of using $addToSet is that the MongoDB 16MB limit on document size still holds. This means that if your collection is large enough such that the size of the set will make one document greater than 16MB, the command will fail. The first $group will create a set of child organizations grouped by parent organization. The $project is used simply to add the total size of the set to the result.
db.collection.aggregate([
{$group: {"_id" : "$parentOrganization.organizationId", "childOrgs" : { "$addToSet" : "$childOrganization.organizationId"}}},
{$project: {"_id" : "$_id", "uniqueChildOrgsCount": {"$size" : "$childOrgs"}, "uniqueChildOrgs": "$childOrgs"}}])
Output:
{ "_id" : "pOrg2", "uniqueChildOrgsCount" : 1, "uniqueChildOrgs" : [ "cOrg3" ]}
{ "_id" : "pOrg1", "uniqueChildOrgsCount" : 2, "uniqueChildOrgs" : [ "cOrg2", "cOrg1" ]}
During these aggregations, I left out the $match statement you included for simplicity, but you could add that back as well.

mongodb unwind aggregate result?

So I'm trying to produce some statistics in my database using aggregation
My current script looks like this:
db.posts.aggregate( [
{ $group: {_id : "$domain", "counter" : {$sum : 1}}},
{ $sort : { counter : -1}},
{ $match : { counter : {$gt : 10} } }
])
and produces result like this:
{
"result" : [
{
"_id" : "i.imgur.com",
"counter" : 1220
},
{
"_id" : "imgur.com",
"counter" : 459
}
],
"ok" : 1
}
Quite satisfactory, but I want to move further. I'm using robomongo (more comfortable to me) and this appears like single document to me:
I want to have each result to be a separate document like this:
| _id | counter
---+-------------+--------
1 | i.imgur.com | 1220
---+-------------+--------
2 | imgur.com | 459
I assumed I needed to use $unwind, but failed miserably: adding { $unwind : "$result"} to aggregate chain produces this output:
/* 0 */
{
"result" : [],
"ok" : 1
}
What have I done wrong and how to do it right?
I don't use robomongo, but it seems like you could tack on .result to your script to get the results output you want.
db.posts.aggregate( [
{ $group: {_id : "$domain", "counter" : {$sum : 1}}},
{ $sort : { counter : -1}},
{ $match : { counter : {$gt : 10} } }
]).result
I've done some looking into this and it appears that the return output you are getting is a consequence of robomongo using db.posts.runCommand("aggregate", {pipeline: [<array of pipeline operators>]}) and then not "instantiating a cursor" rather than the aggregate() helper which creates a cursor.
I don't think there's much you can do on this outside of filing a ticket.

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.

How to find a document with maximum field value in mongodb?

I have a number of Mongodb documents of the following form:
{
"auditedId" : "53d0f648e4b064e8d746b31c",
"modifications" : [
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31d"),
"modified" : "2014-07-22 18:33:05"
},
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
},
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31f"),
"modified" : "2014-07-24 12:04:24"
}
]
}
For each of these documents I want to find "auditRecordId" value which corresponds to the latest modification. In the given example I want to retrieve
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e")
Or, even better:
{
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
}
Is there any way how I can do this without writing map-reduce functions?
Whenever you have an array in your document, the aggregate method is your friend :)
db.foo.aggregate([
// De-normalize the 'modifications' array
{"$unwind":"$modifications"},
// Sort by 'modifications.modified' descending
{"$sort":{"modifications.modified":-1}},
// Pick the first one i.e., the max
{"$limit":1}
])
Output:
{
"result" : [
{
"_id" : ObjectId("53d12be57a462c7459b6f1c7"),
"auditedId" : "53d0f648e4b064e8d746b31c",
"modifications" : {
"auditRecordId" : ObjectId("53d0f648e4b064e8d746b31e"),
"modified" : "2014-07-24 14:15:27"
}
}
],
"ok" : 1
}
Just to illustrate the $unwind operator, I used the above query with $limit. If you have multiple documents of the above format, and you want to retrieve the latest modification in each, you'll have to add another $group phase in your aggregation pipeline and use the $first operator:
db.foo.aggregate([
{"$unwind":"$modifications"},
{"$sort":{"modifications.modified":-1}},
{"$group":{
"_id" : "$auditedId",
"modifications" : {$first:"$modifications"}}}
])

MongoDb - How to search BSON composite key exactly?

I have a collection that stored information about devices like the following:
/* 1 */
{
"_id" : {
"startDate" : "2012-12-20",
"endDate" : "2012-12-30",
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount"]
},
"data" : {
"results" : "1"
}
}
/* 2 */
{
"_id" : {
"startDate" : "2012-12-20",
"endDate" : "2012-12-30",
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
},
"data" : {
"results" : "2"
}
}
/* 3 */
{
"_id" : {
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
},
"data" : {
"results" : "3"
}
}
And I am trying to query the documents using the _id field which will be unique. The problem I am having is that when I query for all the different attributes as in:
db.collection.find({$and: [{"_id.dimensions":{ $all: ["manufacturer","model"], $size: 2}}, {"_id.metrics": { $all:["noOfUsers","deviceCount"], $size: 2}}]});
This matches 2 and 3 documents (I don't care about the order of the attributes values), but I would like to only get 3 back. How can I say that there should not be any other attributes to _id than those that I specify in the search query?
Please advise. Thanks.
Unfortunately, I think the closest you can get to narrowing your query results to just unordered _id.dimensions and unordered _id.metrics requires you to know the other possible fields in the _id subdocument field, eg. startDate and endDate.
db.collection.find({$and: [
{"_id.dimensions":{ $all: ["manufacturer","model"], $size: 2}},
{"_id.metrics": { $all:["noOfUsers","deviceCount"], $size: 2}},
{"_id.startDate":{$exists:false}},
{"_id.endDate":{$exists:false}}
]});
If you don't know the set of possible fields in _id, then the other possible solution would be to specify the exact _id that you want, eg.
db.collection.find({"_id" : {
"dimensions" : ["manufacturer", "model"],
"metrics" : ["deviceCount", "noOfUsers"]
}})
but this means that the order of _id.dimensions and _id.metrics is significant. This last query does a document match on exact BSON representation of _id.