nested document read in MongoDB array in node - node.js

I have a nested document of following format:
{
_id: "1234567890",
course: {content: [{ id: 1,
children:[{
id: 1111
},
{
id: 2222
}
{
id: 3333
}]
},
{ id: 2,
children:[{
id: 4444
},
{
id: 5555
}
{
id: 6666
}]
}
]}
}
I'm trying to query the children based on id (course.content.children.id) using the following code from Node.js:
var query = {
'course.content.children.id': quizId,
};
var option = {
'course.content.children.$': 1,
};
db.getEntityWithOption(tablename,query,option,function(data,err){
res.send(data.course.content[0].children);
});
db.getEntityWithOption is wrapper method on top of db.find() method. when I call the method with quiz id 1, the above code return me the following complete array: `
{ id: 1,
children:[{
id: 1111
},
{
id: 2222
}
{
id: 3333
}]
}
But I need only the specific element of the array with id 1111 not the compete array. Can any life savior suggest me what I'm doing wrong here. TIA :)

Well you can find the id what you want from the children object then send the result like:
res.send(data.course.content[0].children[0]);
Or you can find it with a for loop if its not in the first array element then sent the result.
Or you can try using $elemMatch in your query but then you will have to use aggregation.
Btw in my opinion you should not use so deeply nested documents cuz it will be hard to run complex queries on.

Please try it through aggregation, with $unwind and $match
> db.course.aggregate([
// unwind the `content` array
{$unwind: '$course.content'},
// unwind the `children` array of `content`
{$unwind: '$course.content.children'},
// match the id with `1111`
{$match: {'course.content.children.id': 1111}}]);
Result:
{ "_id" : ObjectId("56d058d22750efa1f9dc3772"), "course" : { "content" : { "id" : 1, "children" : { "id" : 1111 } } } }
To meet you requirement, only the the 1111 array is returned, we can do that through $project
> db.course.aggregate([{$unwind: '$course.content'},
{$unwind: '$course.content.children'},
{$match: {'course.content.children.id': 111}},
{$project: {_id: 0, arr: '$course.content.children'}}]);
Result:
{ "arr" : { "id" : 111 } }

You want to query by a property but you do not know which child of the content and children arrays possess the requested id. MongoDB provides the positional operator
The query should be defined this way:
var query = {
'course.content.$.children.$.id': quizId,
};

Related

mongoDB projection on array in an object

I have this document structure in the collection:
{"_id":"890138075223711744",
"guildID":"854557773990854707",
"name":"test-lab",
"game": {
"usedWords":["akşam","elma","akım"]
}
}
What is the most efficient way to get its fields except the array (it can be really large), and at the same time, see if an item exists in the array ?
I tried this:
let query = {_id: channelID}
const options = { sort: { name: 1 }, projection: { name: 1, "game.usedWords": { $elemMatch: { word}}}}
mongoClient.db(db).collection("channels").findOne(query, options);
but I got the error: "$elemMatch can not be used on nested fields"
If I've understood correctly you can use this query:
Using positional operator $ you can return only the matched word.
db.collection.find({
"game.usedWords": "akşam"
},
{
"name": 1,
"game.usedWords.$": 1
})
Example here
The output is only name and the matched word (also _id which is returned by default)
[
{
"_id": "890138075223711744",
"game": {
"usedWords": [
"akşam"
]
},
"name": "test-lab"
}
]

MongoDB assymetrical return of data, first item in array returned in full, the rest with certain properties omitted?

I'm new to MongoDB and getting to grips with its syntax and capabilities. To achieve the functionality described in the title I believe I can create a promise that will run 2 simultaneous queries on the document - one to get the full content of one item in the array (or at least the data that is omitted in the other query, to re-add after), searched for by most recent date, the other to return the array minus specific properties. I have the following document:
{
_id : ObjectId('5rtgwr6gsrtbsr6hsfbsr6bdrfyb'),
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
date: 05/06/2020,
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B',
date: 28/05/2020,
array: ['even','more','stuff']
},
{
id : 3,
title: 'C',
date: 27/05/2020,
array: ['mountains','of','knowledge']
}
]
}
and I would like to return
{
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
date: 05/06/2020,
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B'
},
{
id : 3,
title: 'C'
}
]
}
How valid and performant is the promise approach versus constructing one query that would achieve this? I have no idea how to perform such 'combined-rule'/conditions in MongoDB, if anyone could give an example?
If your subdocument array you want to omit is not very large. I would just remove it at the application side. Doing processing in MongoDB means you choose to use the compute resources of MongoDB instead of your application. Generally your application is easier and cheaper to scale, so implementation at the application layer is preferable.
But in this exact case it's not too complex to implement it in MongoDB:
db.collection.aggregate([
{
$addFields: { // keep the first element somewhere
first: { $arrayElemAt: [ "$mainArray", 0] }
}
},
{
$project: { // remove the subdocument field
"mainArray.array": false
}
},
{
$addFields: { // join the first element with the rest of the transformed array
mainArray: {
$concatArrays: [
[ // first element
"$first"
],
{ // select elements from the transformed array except the first
$slice: ["$mainArray", 1, { $size: "$mainArray" }]
}
]
}
}
},
{
$project: { // remove the temporary first elemnt
"first": false
}
}
])
MongoDB Playground

MongoDB return nested array of objects minus a property within the object (also .then() doesn't work with aggregation)

I'm very new to mongodb and am having difficulty getting to a solution for my use case. For example I have the following document:
{
_id : ObjectId('5rtgwr6gsrtbsr6hsfbsr6bdrfyb'),
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A',
array: ['lots','off','stuff']
},
{
id : 2,
title: 'B',
array: ['even','more','stuff']
}
]
}
I'd like to have the following returned:
{
uuid : 'something',
mainArray : [
{
id : 1,
title: 'A'
},
{
id : 2,
title: 'B'
}
]
}
I've tried various combinations of using findOne() and aggregate() with $slice and $project. With findOne(), if it worked at all, the who document would be returned. I am unable to test whether attempts at aggregating work because .then((ret)=>{}) promises don't seem to work in node.js for me with it (no issues with findOne). Calling a function like so
return db.myCollection.aggregate([
{
$match: {
_id:ObjectId(mongo_id)
}
},
{
$project : {
mainArray: {
id:1,
title:1
}
}
}
],function(err,res){
console.log(res)
return res
})
logs the entire function and not the droids I'm looking for.
You're missing toArray() method to obtain the actual result set. Instead you're returning the aggregation cursor object. Try this.
return db.myCollection.aggregate([matchCode,projectCode]).toArray().then(
data => {
console.log(data);
return data;
},
error => { console.log(error)});
The documnetation on aggregation cursor for MongoDB NodeJS driver can
be found here
http://mongodb.github.io/node-mongodb-native/3.5/api/AggregationCursor.html#toArray
This is an alternative solution (to the solution mentioned in the comment by #v1shva)
Instead of using aggregation you can use projection option of .findOne() operation.
db.myCollection.findOne(matchCode, {
projection: { _id: false, 'mainArray.array': false } // or { _id: -1, 'mainArray.array': -1 }
})

Mongoose delete operation [duplicate]

Here is array structure
contact: {
phone: [
{
number: "+1786543589455",
place: "New Jersey",
createdAt: ""
}
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
Here I only know the mongo id(_id) and phone number(+1786543589455) and I need to remove that whole corresponding array element from document. i.e zero indexed element in phone array is matched with phone number and need to remove the corresponding array element.
contact: {
phone: [
{
number: "+1986543589455",
place: "Houston",
createdAt: ""
}
]
}
I tried with following update method
collection.update(
{ _id: id, 'contact.phone': '+1786543589455' },
{ $unset: { 'contact.phone.$.number': '+1786543589455'} }
);
But it removes number: +1786543589455 from inner array object, not zero indexed element in phone array. Tried with pull also without a success.
How to remove the array element in mongodb?
Try the following query:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
It will find document with the given _id and remove the phone +1786543589455 from its contact.phone array.
You can use $unset to unset the value in the array (set it to null), but not to remove it completely.
You can simply use $pull to remove a sub-document.
The $pull operator removes from an existing array all instances of a value or values that match a specified condition.
Collection.update({
_id: parentDocumentId
}, {
$pull: {
subDocument: {
_id: SubDocumentId
}
}
});
This will find your parent document against given ID and then will remove the element from subDocument which matched the given criteria.
Read more about pull here.
In Mongoose:
from the document:
To remove a document from a subdocument array we may pass an object
with a matching _id.
contact.phone.pull({ _id: itemId }) // remove
contact.phone.pull(itemId); // this also works
See Leonid Beschastny's answer for the correct answer.
To remove all array elements irrespective of any given id, use this:
collection.update(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from a specific document:
collection.update(
{ _id: id },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
To remove all matching array elements from all documents:
collection.updateMany(
{ },
{ $pull: { 'contact.phone': { number: '+1786543589455' } } }
);
Given the following document in the profiles collection:
{ _id: 1, votes: [ 3, 5, 6, 7, 7, 8 ] }
The following operation will remove all items from the votes array that are greater than or equal to ($gte) 6:
db.profiles.update( { _id: 1 }, { $pull: { votes: { $gte: 6 } } } )
After the update operation, the document only has values less than 6:
{ _id: 1, votes: [ 3, 5 ] }
If you multiple items the same value, you should use $pullAll instead of $pull.
In the question having a multiple contact numbers the same use this:
collection.update(
{ _id: id },
{ $pullAll: { 'contact.phone': { number: '+1786543589455' } } }
);
it will delete every item that matches that number. in contact phone
Try reading the manual.

MongoDB apply iterator with additional query to all results

Is there anyway within mongo, via MapReduce or Aggregation to apply a second query based on the result set of the first?, such as an Aggregate within an aggregate, or new emit/query within MapReduce.
For example, I have a materialized path pattern of items (which also includes parentId), I can get all of the roots simply by:
db.collection.find({parentId: null}
.toArray(function(err, docs) {
});
What I want to do is determine if these docs have children, just a flag true/false. I can iterate through these docs using async each and check, but on large docs, this is not very performant at all and causes event loop delays, I can use eachSeries, but this is just slow.
Ideally, I'd like to be able to handle this all within Mongo. Any suggestions if that's possible?
Edit, Example collection:
{
_id: 1,
parentId: null,
name: 'A Root Node',
path: ''
}
{
_id: 2,
parentId: 1,
name: 'Child Node A',
path: ',1'
}
{
_id: 3,
parentId: 2,
name: 'Child Node B',
path: ',1,2'
}
{
_id: 4,
parentId: null,
name: 'Another Root Node',
path: ''
}
This basically represents two root nodes, where one root node ({_id: 1}) has two children (one being direct), example:
1
2
3
4
What I would like to do is do a query based on parentId so I can get the root nodes by using null or by passing a parentId I can get the children of that and determine if the result set from this, any of the items contain children, example response for where {parentId: null}:
[{
_id: 1,
parentId: null,
name: 'A Root Node',
path '',
hasChildren: true
},
{
_id: 4,
parentId: null,
name: 'Another Root Node',
path '',
hasChildren: false
}]
You could try creating an array of the parentIds from the materialized paths that you can then use in the aggregation pipeline to project the extra field/flag hasChildren.
This can be done by using the map() method on the cursor returned from the find() method. The following illustrates this:
var arr = db.collection.find({ "parentId": { "$ne": null } })
.map(function (e){ return e.path; })
.join('')
.split(',')
.filter(function (e){ return e; })
.map(function (e){ return parseInt(e); }),
parentsIds = _.uniq(arr); /* using lodash uniq method to return a unique array */
Armed with this array of parentIds, you can then use the aggregation framework in particular the $project pipeline which makes use of the the set operator $setIsSubset which takes two arrays and returns true when the first array is a subset of the second, including when the first array equals the second array, and false otherwise:
db.collection.aggregate([
{
"$match": {
"parentId": null
}
},
{
"$project": {
"parentId": 1,
"name": 1,
"path": 1,
"hasChildren": { "$setIsSubset": [ [ "$_id" ], parentIds ] }
}
}
], function (err, res) { console.log(res); });

Resources