How to make query that find inside of find in mongodb - mongodb

I have a document as below in Mongodb.
{
"vatInfo":{
"company": "apple"
},
"type": "manager",
"parent": "123"
}
And I have another document in same collection as below.
{
"type": "member",
"parentId": "123",
"id": "3"
}
And when I make client.find({id: 3, type: 'member'}), I want to get this that finds vatInfo automatically inside of find.
{
"type": "member",
"parentId": "123",
"id": "3",
"vatInfo":{
"company": "apple"
},
}
How should I make aggregate for this find? I dont want to make double find.
Thank you so much for reading it.

You can use the aggregation pipelines for that.
$match - find the parent documen.
$lookup - join the document from the other collection.
$project - modify the structure of the result.
// collection `client`
{
"vatInfo":{ "company": "apple" },
"type": "manager",
"parent": "123"
},
{
"type": "member",
"parentId": "123",
"id": "3"
}
// query example
db.getCollection('client').aggregate([
{ $match: { 'id': '3', 'type': 'member' } },
{
$lookup: {
from: 'client',
localField: 'parentId',
foreignField: 'parent',
as: 't01'
}
},
{
$project: {
'_id': 0,
'type': 1,
'parentId': 1,
'id': 1,
'vatInfo': { $arrayElemAt: [ "$t01.vatInfo", 0 ] }
}
}
])
// result
{
"type" : "member",
"parentId" : "123",
"id" : "3",
"vatInfo" : {
"company" : "apple"
}
}

To add the field of another document, you can use populate in Mongoose :
For this to work you need to have a reference in your schema between the client document and the parent document :
const clientSchema = Schema({
[...]
parentId: { type: Schema.Types.ObjectId, ref: 'ParentCollection' }
});
Then, you can use populate anywhere in your code :
client.findOne({id: 3, type: 'member'}).populate('parentId');

Related

MongoDB aggregate merging fields

I have a mongo Database I'll like to "join" two of them and then merge some other fields:
Let's see the schemas:
Students Schema (and data):
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
}
and Subject schema:
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter",
}
What I'll like to make a query with the subjects (all info) of a student, also if the student have additional fields in subject like passed add it to the subject subdocument.
Here is my query till now:
db.students.aggregate([
{
$match:
{
_id : ObjectId('5fbd564981b1313de790b580')
}
},
{
$lookup :
{
from : "subjects",
localField : "subjects._id",
foreignField : "_id",
as : "FoundSubject"
}
}
]);
which correctly make the "join" but the merge is still missing, I got as result:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
"FoundSubject": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math"
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
]
}
but I'll like to have:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
],
"__v": NumberInt("1"),
}
with merged data and field "passed" added. How can accomplish that?
I'm new to MongoDB coming from MySQL.
Thanks
You need to merge both objects, add below stage after $lookup,
MongoDB Version From 3.4
$map to iterate loop of students array
$reduce to iterate loop of FoundSubject array, check condition if condition match then return required fields otherwise return initial value
$project to remove FoundSubject from result
{
$addFields: {
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$reduce: {
input: "$FoundSubject",
initialValue: {},
in: {
$cond: [
{ $eq: ["$$s._id", "$$this._id"] },
{
_id: "$$this._id",
course: "$$this.course",
name: "$$this.name",
teacher: "$$this.teacher",
passed: "$$s.passed"
},
"$$value"
]
}
}
}
}
}
}
},
{ $project: { FoundSubject: 0 } }
Playground
MongoDB Version From 4.4
$map to iterate loop of students array,
$filter to get matching document from FoundSubject array and $first to get first object from array returned by filter
$mergeObjects to merge current objects with found result object from filter
remove FoundSubject using $$REMOVE
// skipping your stages
{
$addFields: {
FoundSubject: "$$REMOVE",
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
$first: {
$filter: {
input: "$FoundSubject",
cond: { $eq: ["$$s._id", "$$this._id"] }
}
}
}
]
}
}
}
}
}
Playground

MongoDB - filter by several array elements

Suppose I have documents like this:
{
"_id", "1",
"myarray": [
{
"address": "abc",
"role": "input",
}
{
"address": "def",
"role": "output",
}
]
},
{
"_id", "2",
"myarray": [
{
"address": "xyz",
"role": "input",
}
{
"address": "abc",
"role": "output",
}
]
}
I want to return documents where myarray.address is abc and myarray.role is output, but not the documents where exists myarray.address='abc' and exists myarray.role='output', but the documents where exists element of myarray array:
address: "abc",
role: "output"
Using the example above, I want only the document with _id=2.
You can use $elemMatch query operator to match multiple fields inside an array
db.collection.find({ myArray: { $elemMatch: { address: 'abc', role: 'output' }}})

Mongo DB - query nested arrays of objects in referenced document

I have few referenced collections: Employees, Roles, Schools...
I need to query the database, to list all employees that are employed by a certain school. That means all Employees, that has certain School ID in their corresponding role document must be returned in the list or array of results.
So far I have tried to find it like this:
const employees = mongoose.schema("Employees");
employees.find({})
.populate({
"path": "roles"
})
.then(function(docs){
docs.filter(function(){
// write my filters here....
})
});
but this still is inefficient and I can't make it work.
It has to be a smarter way...
This is my Employee document, which references a document in Roles Collection:
{
"_id" : { "$oid" : "57027f1b9522d363243abr42" },
"assignedRoles": [
{
"type": "teacher",
"schools": [
{
"schoolId": "57027f1b9522d3632423rf32",
"classes" : ["math", "science"],
"active": true
},
{
"schoolId": "57027f1b9522d36324252fs2",
"classes" : ["science"],
"active": true
},
{
"schoolId": "57027f1b9522d36324242f35",
"classes" : ["math"],
"active": true
},
]
},
{
"type": "manager",
"schools": [
{
"schoolId": "57027f1b9522d3632423rf32",
"active": true
},
{
"schoolId": "57027f1b9522d36324252fs2",
"active": true
}
]
}
{
"type": "principal",
"schools": [
{
"schoolId": "57027f1b9522d3632423rf32",
"active": true
}
]
}
],
"rolesMeta": "Meta Info for the Roles"
}
The following is the list of Schools - irrelevant for the task, I am just adding that for the completion:
{
"_id": { "$oid" : "57027f1b9522d3632423rf32" },
"name": "G.Washington HS",
"district": "six",
"state": "New Mexico"
},
{
"_id": { "$oid" : "57027f1b9522d36324252fs2" },
"name": "A. Lincoln HS",
"district": "six",
"state": "New Mexico"
},
{
"_id": { "$oid" : "57027f1b9522d36324242f35" },
"name": "T. Roosvelt HS",
"district": "four",
"state": "New Mexico"
}
The Resolution to the problem is to use MongoDB Aggregate framework to be able to drill down and "flatten" the array structure.
Here is how I have resolved it:
db.employees.aggregate([
// I use $project to specify which field I want selected from the collection
{
$project: {_id: 1, "roles": 1}
},
// $lookup helps me get results and populate the 'role' field from the Roles collection. This returns an array
{
$lookup: {
"from": "roles",
"localField": "roles",
"foreignField": "_id",
"as": "roles"
}
},
// $unwind is self explanatory - it flattens the array and creates multiple objects, instances of the parent object for each array element.
{
$unwind: "$roles"
},
// now repeat for each array in this document
{
$unwind: "$roles.assignedRoles"
},
// repeat again, since I need to look in the schools array.
{
$unwind: "$roles.assignedRoles.schools"
},
// The following line will return all records that the school ID checks out.
{
$match: {"roles.assignedRoles.schools.schoolId": school}
},
// $group is a necessary cleanup task so we avoid repeating employee records... Nicely Done! :)
{
$group: {
"_id": "$_id",
"roles": {"$push": "$roles"},
}
}
])
.exec()
.then(function (docs) {
// here you process the resulted docs.
});

MongoDb $lookup query with multiple fields from objects array

This question has previously been marked as a duplicate of this question I can with certainty confirm that it is not.
This is not a duplicate of the linked question because the elements in question are not an array but embedded in individual objects of an array as fields. I am fully aware of how the query in the linked question should work, however that scenario is different from mine.
I have a question regarding the $lookup query of MongoDb. My data structure looks as follows:
My "Event" collection contains this single document:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendees": [
{
"type": 1,
"status": 2,
"contact": ObjectId("mongodbobjectidHEX1")
},
{
"type": 7,
"status": 4,
"contact": ObjectId("mongodbobjectidHEX2")
}
]
}
My "Contact" collection contains these documents:
{
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
},
{
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
What I want to do is perform an aggregate query with the $lookup operator on the "Event" collection and get the following result with full "contact" data:
{
"_id": ObjectId("mongodbobjectid..."),
"name": "Some Event",
"attendees": [
{
"type": 1,
"status": 2,
"contact": {
"_id": ObjectId("mongodbobjectidHEX1"),
"name": "John Doe",
"age": 35
}
},
{
"type": 7,
"status": 4,
"contact": {
"_id": ObjectId("mongodbobjectidHEX2"),
"name": "Peter Pan",
"age": 60
}
}
]
}
I have done the same with single elements of "Contact" referenced in another document but never when embedded in an array. I am unsure of which pipeline arguments to pass to get the above shown result?
I also want to add a $match query to the pipeline to filter the data, but that is not really part of my question.
Try this one
db.getCollection('Event').aggregate([{ "$unwind": "$attendees" },
{ "$lookup" : { "from" : "Contact", "localField" : "attendees.contact", "foreignField": "_id", "as" : "contactlist" } },
{ "$unwind": "$contactlist" },
{ "$project" :{
"attendees.type" : 1,
"attendees.status" : 1,
"attendees.contact" : "$contactlist",
"name": 1, "_id": 1
}
},
{
"$group" : {
_id : "$_id" ,
"name" : { $first : "$name" },
"attendees" : { $push : "$attendees" }
}
}
])

Combining multiple sub-documents into a new doc in mongo

I am trying to query multiple sub-documents in MongoDB and return as a single doc.
I think the aggregation framework is the way to go, but, can't see to get it exactly right.
Take the following docs:
{
"board_id": "1",
"hosts":
[{
"name": "bob",
"ip": "10.1.2.3"
},
{
"name": "tom",
"ip": "10.1.2.4"
}]
}
{
"board_id": "2",
"hosts":
[{
"name": "mickey",
"ip": "10.2.2.3"
},
{
"name": "mouse",
"ip": "10.2.2.4"
}]
}
{
"board_id": "3",
"hosts":
[{
"name": "pavel",
"ip": "10.3.2.3"
},
{
"name": "kenrick",
"ip": "10.3.2.4"
}]
}
Trying to get a query result like this:
{
"hosts":
[{
"name": "bob",
"ip": "10.1.2.3"
},
{
"name": "tom",
"ip": "10.1.2.4"
},
{
"name": "mickey",
"ip": "10.2.2.3"
},
{
"name": "mouse",
"ip": "10.2.2.4"
},
{
"name": "pavel",
"ip": "10.3.2.3"
},
{
"name": "kenrick",
"ip": "10.3.2.4"
}]
}
I've tried this:
db.collection.aggregate([ { $unwind: '$hosts' }, { $project : { name: 1, hosts: 1, _id: 0 }} ])
But it's not quite what I want.
You can definitely do this with aggregate. Let's assume your data is in collection named board, so please replace it with whatever your collection name is.
db.board.aggregate([
{$unwind:"$hosts"},
{$group:{_id:null, hosts:{$addToSet:"$hosts"}}},
{$project:{_id:0, hosts:1}}
]).pretty()
it will return
{
"hosts" : [
{
"name" : "kenrick",
"ip" : "10.3.2.4"
},
{
"name" : "pavel",
"ip" : "10.3.2.3"
},
{
"name" : "mouse",
"ip" : "10.2.2.4"
},
{
"name" : "mickey",
"ip" : "10.2.2.3"
},
{
"name" : "tom",
"ip" : "10.1.2.4"
},
{
"name" : "bob",
"ip" : "10.1.2.3"
}
]
}
So your basic problem here is that the arrays are contained in separate documents. So while you are correct to $unwind the array for processing, in order to bring the content into a single array you would need to $group the result across documents, and $push the content to the result array:
db.collection.aggregate([
{ "$unwind": "$hosts" },
{ "$group": {
"_id": null,
"hosts": { "$push": "$hosts" }
}}
])
So just as $unwind will "deconstruct" the array elements, the $push accumulator in $group brings "reconstructs" the array. And since there is no other key to "group" on, this brings all the elements into a single array.
Note that a null grouping key is only really practical when the resulting document would not exceed the BSON limit. Otherwise you are better off leaving the individual elements as documents in themselves.
Optionally remove the _id with an additional $project if required.