How to retrieve a specific field from a subdocument array with mongoose - arrays

I'm trying to get a specific field from a subdocument array
I'm not gonna include any of the fields in the parent doc
Here is the sample document
{
"_id" : ObjectId("5409dd36b71997726532012d"),
"hierarchies" : [
{
"rank" : 1,
"_id" : ObjectId("5409df85b719977265320137"),
"name" : "CTO",
"userId" : [
ObjectId("53a47a639c52c9d83a2d71db")
]
}
]
}
I would like to return the rank of the hierarchy if the a userId is in the userId array
here's what I have so far in my query
collectionName.find({{hierarchies:
{$elemMatch : {userId: ObjectId("53a47a639c52c9d83a2d71db")}}}
, "hierarchies.$.rank", function(err,data){}
so far it returns the entire object in the hierarchies array I want, but I would like to limit it to just the rank property of the object.

The projection available to .find() queries generally in MongoDB does not do this sort of projection for internal elements of an array. All you can generally do is return the "matched" element of the array entirely.
For what you want, you use the aggregation framework instead, which gives you more control over matching and projection:
Model.aggregate([
{ "$match": {
"hierarchies.userId": ObjectId("53a47a639c52c9d83a2d71db")
}},
{ "$unwind": "$hierarchies" },
{ "$match": {
"hierarchies.userId": ObjectId("53a47a639c52c9d83a2d71db")
}},
{ "$project": {
"rank": "$hierarchies.rank"
}}
],function(err,result) {
})
That basically matches the documents, filters the array content of the document to just the match and then projects only the required field.

Related

How to access the elements in a mongodb array

I have a mongodb Document like the one mentioned below
{
"_id" : ObjectId("213122423423423"),
"eventDateTimes" :
[
ISODate("2015-05-26T12:18:15.000Z"),
ISODate("2015-05-26T12:18:14.000Z"),
ISODate("2015-05-26T12:00:37.000Z"),
ISODate("2015-05-26T12:00:36.000Z")
],
"parseFlags" :
[
false,
"True",
false,
false
],
"eventMessages" : [
"Erro1 ",
"Error2",
"Error3",
"Error4"
]
}
}
I have to fetch the eventMessages based on the parseFlags array.
I have to get the index of elements in the parseFlags array where the value is "false" and then get the event messages mapping to those indexes.
The result should be the document with following parameters where parseFlag is false:
{
id,
EventDateTimes:[date1,date3,date4],
ParseFlags :[false,false,false]
eventMessages :[Error1,Error3,Error4]
}
Can you please let me know how to get this output? I am using mongodb 3.2.
Mongodb 3.4 has new array operator zip that groups the array elements based on the index
db.collectionName.aggregate({
$project: {
transposed: {
$zip: {inputs: ["$eventDateTimes", "$parseFlags", "$eventMessages"]
}
}
},
{$unwind: '$transposed'},
{$project: {eventDateTime: {$arrayElemAt: ['$tranposed',0]}, parseFlag:
{$arrayElemAt: ['$transposed', 1]}}}
{$match: {parseFlag: false}}
)
For mongo 3.2 there isn't any direct way to handle the expected query. You can instead use mapReduce with custom code
db.collection.mapReduce(function(){
var transposed = [];
for(var i=0;i<this.eventDateTimes.length;i++){
if(this.parseFlags[i]===false){
transposed.push({eventDateTime: this.eventDateTimes[i], parseFlag: this.parseFlags[i]});
}
}
emit(this._id, transposed);
}, function(key, values){
return [].concat.apply([], values);
}, {out: {inline:1}})
PS: I haven't actually executed the query, but above one should give you an idea how it needs to be done.

Mongodb query array

I need to get all documents that match an array of objects or an object with many fields.
Example 1 (array of objects)
If the document match the country_code than he must have one of postal_codes too
var locations = [
{
country_code : 'IT',
postal_codes : [21052, 21053, 21054, 21055]
},
{
country_code : 'GER',
postal_codes : [41052, 41053, 41054, 41055]
}
]
Example 2 (object with fields)
If the document match the key than it must have one of the values of that key
var location = {
'IT' : [21052, 21053, 21054, 21055],
'GER' : [41052, 41053, 41054, 41055]
}
I like the first type of document to match(array of objects) but how can i use to get all documents that match?
The documents to find have this structure:
{
"_id" : ObjectId("587f6f57ed6b9df409db7370"),
"description" : "Test description",
"address" : {
"postal_code" : "21052",
"country_code" : "IT"
}
}
You can use $in to find such collections.
db.collection_name.find(
{ address.postal_code: { $in: [your values] } },
)
Check this link for querying child objects.
Check this link for mongoDB $in
One way is to use the $or operator. This will help you limit the combinations of country_code and postal_code.
Your query should look something like this.
db.locations.find({
$or: [{
"country_code": "IT",
"postal_code": {
$in: [21052, 21053, 21054, 21055]
}
}, {
"country_code": "GER",
"postal_code": {
$in: [41052, 41053, 41054, 41055]
}
}]
})

mongo add to nested array if entry does not contain two fields that match

I have a mongo document that contains an array called history:
{
"_id" : ObjectId("575fe85bfe98c1fba0a6e535"),
"email" : "email#address",
"__v" : 0,
"history" : [
{
"name" : "Test123",
"organisation" : "Rat",
"field" : 4,
"another": 3
}
]
}
I want to add fields to each history object or update fields IF the name AND organisation match, however if they don't, I want to add a new object to the array with the queried name and organisation and add/update the other fields to the object when necessary.
So:
This query, finds one that matches:
db.users.find({
email:"email#address",
$and: [
{ "history.name": "Test123", "history.organisation": "Rat"}
]
})
However, I'm struggling to get the update/upsert to work IF that combination of history.name and history.organisation dont exist in the array.
What I think I need to do is a :
"If this history name does not equal 'Test123' AND the history organisation does not equal 'Rat' then add an object to the array with those fields and any other field provided in the update query."
I tried this:
db.users.update({
email:"email#address",
$and: [
{ "history.name": "Test123", "history.organisation": "Rat"}
]
}, {
history: { name: "Test123"},
history: { organisation: "Rat"}
}, {upsert:true})
But that gave me E11000 duplicate key error index: db.users.$email_1 dup key: { : null }
Any help greatly appreciated.
Thanks community!
Not possible with a single atomic update I'm afraid, you would have to do a couple of update operations that satisfy both conditions.
Break down the update logic into two distinct update operations, the first one would require using the positional $ operator to identify the element in the history array you want and the $set to update the existing fields. This operation follows the logic update fields IF the name AND organisation match
Now, you'd want to use the findAndModify() method for this operation since it can return the updated document. By default, the returned document does not include the modifications made on the update.
So, armed with this arsenal, you can then probe your second logic in the next operation i.e. update IF that combination of "history.name" and "history.organisation" don't exist in the array. With this second
update operation, you'd need to then use the $push operator to add the elements.
The following example demonstrates the above concept. It initially assumes you have the query part and the document to be updated as separate objects.
Take for instance when we have documents that match the existing history array, it will just do a single update operation, but if the documents do not match, then the findAndModify() method will return null, use this logic in your second update operation to push the document to the array:
var doc = {
"name": "Test123",
"organisation": "Rat"
}, // document to update. Note: the doc here matches the existing array
query = { "email": "email#address" }; // query document
query["history.name"] = doc.name; // create the update query
query["history.organisation"] = doc.organisation;
var update = db.users.findAndModify({
"query": query,
"update": {
"$set": {
"history.$.name": doc.name,
"history.$.organisation": doc.organisation
}
}
}); // return the document modified, if there's no matched document update = null
if (!update) {
db.users.update(
{ "email": query.email },
{ "$push": { "history": doc } }
);
}
After this operation for documents that match, querying the collection will yield the same
db.users.find({ "email": "email#address" });
Output:
{
"_id" : ObjectId("575fe85bfe98c1fba0a6e535"),
"email" : "email#address",
"__v" : 0,
"history" : [
{
"name" : "Test123",
"organisation" : "Rat",
"field" : 4,
"another" : 3
}
]
}
Now consider documents that won't match:
var doc = {
"name": "foo",
"organisation": "bar"
}, // document to update. Note: the doc here does not matches the current array
query = { "email": "email#address" }; // query document
query["history.name"] = doc.name; // create the update query
query["history.organisation"] = doc.organisation;
var update = db.users.findAndModify({
"query": query,
"update": {
"$set": {
"history.$.name": doc.name,
"history.$.organisation": doc.organisation
}
}
}); // return the document modified, if there's no matched document update = null
if (!update) {
db.users.update(
{ "email": query.email },
{ "$push": { "history": doc } }
);
}
Querying this collection for this document
db.users.find({ "email": "email#address" });
would yield
Output:
{
"_id" : ObjectId("575fe85bfe98c1fba0a6e535"),
"email" : "email#address",
"__v" : 0,
"history" : [
{
"name" : "Test123",
"organisation" : "Rat",
"field" : 4,
"another" : 3
},
{
"name" : "foo",
"organisation" : "bar"
}
]
}

MongoDB remove an item from an array inside an array of objects

I have a document that looks like this:
{
"_id" : ObjectId("56fea43a571332cc97e06d9c"),
"sections" : [
{
"_id" : ObjectId("56fea43a571332cc97e06d9e"),
"registered" : [
"123",
"e3d65a4e-2552-4995-ac5a-3c5180258d87"
]
}
]
}
I'd like to remove the 'e3d65a4e-2552-4995-ac5a-3c5180258d87' in the registered array of only the specific section with the _id of '56fea43a571332cc97e06d9e'.
My current attempt is something like this, but it just returns the original document unmodified.
db.test.findOneAndUpdate(
{
$and: [
{'sections._id': ObjectId('56fea43a571332cc97e06d9e')},
{'sections.registered': 'e3d65a4e-2552-4995-ac5a-3c5180258d87'}
]
},
{
$pull: {
$and: [
{'sections._id': ObjectId('56fea43a571332cc97e06d9e')},
{'sections.registered': 'e3d65a4e-2552-4995-ac5a-3c5180258d87'}
]
}
})
I've looked in to $pull, but I can't seem to figure out how to make it work on an array of nested objects containing another array. The $pull examples all seem to deal with only one level of nesting. How do I remove the matching entry from the registered array of the item in the sections array with the _id that I supply?
You need to use the positional $ update operator to remove the element from your array. You need this is because "sections" is an array of sub-documents.
db.test.findOneAndUpdate(
{ "sections._id" : ObjectId("56fea43a571332cc97e06d9e") },
{ "$pull": { "sections.$.registered": "e3d65a4e-2552-4995-ac5a-3c5180258d87" } }
)

Getting subdocument element's count per index inside an array and updating the subdocument key - subdocument in array(IN MONGODB)

How to get subdocument element's count inside an array and how to update the subdocument's key in MongoDB
For eg, following is the whole doc stored in mongodb:
{
"CompanyCode" : "SNBN",
"EventCode" : "ET00008352",
"EventName" : "Sunburn Presents Avicii India Tour",
"TktDetail" : [
{
"Type" : "Category I",
"Qty" : {
"10-Dec" : {
"value" : 58
},
"11-Dec" : {
"value" : 83
},
"12-Dec" : {
"value" : 100
}
}
},
{
"Type" : "Category II",
"Qty" : {
"10-Dec" : {
"value" : 4
},
"11-Dec" : {
"value" : 7
},
"12-Dec" : {
"value" : 8
}
}
},
{
"Type" : "PRICE LEVEL 1",
"Qty" : {
"11-Dec" : {
"value" : 2
}
}
},
{
"Type" : "CatIV",
"Qty" : {
"18-Dec" : {
"value" : 20
}
}
}
],
"TransDate" : [
"10-Dec-2013",
"11-Dec-2013",
"12-Dec-2013",
],
"VenueCode" : "SNBN",
"VenueName" : "Sunburn",
"_id" : ObjectId("52452db273b92012c41ad612")
}
Here TktDetail is an array, inside which there is a Qty subdoc which contains multiple elements, I want to know how to get the elements count inside Qty per index?
For example, the 0th index of TktDetail array contains 1 Qty subdoc, which further has a element count of 3, whereas 3rd index has element count of 1 in Qty subdoc.
If I want to update the subdoc key, like, I want to update the date in Qty from "10-Dec" to "10-Dec-2013", how is it possible?
Thanks in advance, looking for a reply ASAP..
So the first thing here is that you actually asked two questions, being "how do I get a count of the items under Qty?" and "how can I change the names?". Now while normally unrelated I'm going to treat them as the same thing.
What you need to do is change your schema and in doing so I'm going to allow you to get the count of items and I'm going to encourage you to change those field names as well. Specifically you need a schema like this:
"TktDetail" : [
{
"Type" : "Category I",
"Qty" : [
{ "date": ISODate("2013-12-10T00:00:00.000Z") , "value" : 58 },
{ "date": ISODate("2013-12-11T00:00:00.000Z"), "value" : 83 },
{ "date": ISODate("2013-12-01T00:00:00.000Z"), "value" : 100 },
]
},
All the gory details are in my answer here to a similar question. But the problem basically is that when you use sub-documents in the way you have you are ruining your chances of doing any meaningful query operations on this, as to get at each element you must specify the full path to get there.
That answer has more detail, but the case is you really want an array. The trade-off, it's a little harder to update, especially considering you have nested arrays, but it's a lot easier to add and much easier to query.
Also, and related, change your dates to be dates and not strings. The strings, are no good for comparisons inside MongoDB. With them set as proper BSON dates (noting I clipped them to the start of day) you can compare, and query ranges and do useful things. Your application code will be happy to as the driver will return a real date object, rather than something you have to manipulate "both ways".
So once you have read through, understood and implemented this, on to counting:
db.collection.aggregate([
// Unwind the TktDetail array to de-normalize
{"$unwind": "$TktDetail"},
// Also Unwind the Qty array
{"$unwind": "$Qty" },
// Get some group information and count the entries
{"$group": {
"_id": {
"_id": "$_id,
"EventCode": "$EventCode",
"Type": "$TktDetail.Type"
},
"Qty": {"$sum": 1 }
}},
// Project nicely
{"$project": {
"_id": 0,
"EventCode": "$_id.EventCode",
"Type: "$_id.Type",
"Qty": 1,
}},
// Let's even sort it
{"$sort": { "EventCode": 1, "Qty" -1 }}
])
So that allowed us to get a count of the items in Qty for each EventCode by Type with the Qty ordered higest to lowest.
And that is not possible on your current schema without loading and traversing each document in code.
So there is the case. Now if you want to ignore this and just go about changing the sub-document key names, then you'll need to do remove the key and underlying document and replace with the new key name, using update:
db.collection.update(
{ EventCode: "ET00008352"},
{ $unset:{ "TktDetail.0.Qty.10-Dec": "" }}
)
db.collection.update(
{ EventCode: "ET00008352"},
{ $set:{ "TktDetail.0.Qty.10-Dec-2013": { value: 58 } }}
)
And you'll need to do that for every item that you have.
So you either work out that schema conversion or otherwise have a lot of work anyway in order to change the keys. For myself, I'd do it properly, and only do it once so I didn't run into the next problem later.

Resources