Filter from two collections - mongodb

I have two collections Employees, Clients
Employees schema has following record
{
"_id" : ObjectId("5a852dcd0290f7eca89e9a79"),
"FirstName" : "raj",
"LastName" : "patel",
"Forms" : [{
"ClientId" : ObjectId("5a8528ed0290f7eca89e9a5f"),
"ProjectId" : ObjectId("5a856fde0290f7eca89e9a88"),
"FormId" : ObjectId("5a6eeb9e3bf43426d4d31774")
}, {
"ClientId" : ObjectId("5a87f593d59a6fb5249c72ad"),
"ProjectId" : ObjectId("5a87f593d59a6fb5249c72b1"),
"FormId" : ObjectId("5a6eec263bf43426d4d31780")
}, {
"ClientId" : ObjectId("5a8528ed0290f7eca89e9a5f"),
"ProjectId" : ObjectId("5a856d850290f7eca89e9a85"),
"FormId" : ObjectId("5a6eec263bf43426d4d31780")
}]
}
Clients schema has following record
{
"_id" : ObjectId("5a8528ed0290f7eca89e9a5f"),
"CompanyName" : "test",
"Email" : "test#test.com",
"PhoneNumber" : "(987)654-3210",
"Projects" : [{
"_id" : ObjectId("5a856ca70290f7eca89e9a7f"),
"Name" : "Feedback",
"Description" : "Feedback form",
"Forms" : [
ObjectId("5a6eeace1a692b18b4a4ae3a"),
ObjectId("5a688a799109bd17b4e8c8b2")
]
}, {
"_id" : ObjectId("5a856d700290f7eca89e9a82"),
"Name" : "complaint",
"Description" : "sfsddsf !",
"Forms" : [
ObjectId("5a66d56ffe50af19647f66dd"),
ObjectId("5a66d4ccfe50af19647f66db"),
ObjectId("5a5f18169c50261c24ca5f0a")
]
}, {
"_id" : ObjectId("5a856d850290f7eca89e9a85"),
"Name" : "Test Project",
"Description" : "For testing purpose",
"Forms" : [
ObjectId("5a62e2a8f85cf41bd0a8f522"),
ObjectId("5a6eec263bf43426d4d31780")
]
}, {
"_id" : ObjectId("5a856fde0290f7eca89e9a88"),
"Name" : "simple project",
"Description" : "Project",
"Forms" : [
ObjectId("5a6eeb9e3bf43426d4d31774")
]
}
]
},{
"_id" : ObjectId("5a87f593d59a6fb5249c72ad"),
"CompanyName" : "PNB",
"Email" : "niravmodi#pnb.com",
"PhoneNumber" : "(987)654-3210",
"Projects" : [{
"_id" : ObjectId("5a87f593d59a6fb5249c72ae"),
"Name" : "Home Loan",
"Description" : "Get home loan!",
"Forms" : [
ObjectId("5a6eeb9e3bf43426d4d31774")
]
}, {
"_id" : ObjectId("5a87f593d59a6fb5249c72b1"),
"Name" : "Car Loan",
"Description" : "For testing purpose of android app",
"Forms" : [
ObjectId("5a6eec263bf43426d4d31780")
]
}
]
}
My output is like below
{
"_id" : "5a852dcd0290f7eca89e9a79",
"FirstName" : "raj",
"LastName" : "patel",
"Clients" : [{
"_id" : "5a87f593d59a6fb5249c72ad",
"CompanyName" : "PNB",
"Email" : "niravmodi#pnb.com",
"PhoneNumber" : "(987)654-3210",
"Projects" : [{
"_id" : "5a87f593d59a6fb5249c72b1",
"Name" : "Car Loan",
"Description" : "For testing purpose of android app",
"Forms" : [
"5a6eec263bf43426d4d31780"
]
}
]
}, {
"_id" : "5a8528ed0290f7eca89e9a5f",
"CompanyName" : "test",
"Email" : "test#test.com",
"PhoneNumber" : "(987)654-3210",
"Projects" : [{
"_id" : "5a856fde0290f7eca89e9a88",
"Name" : "simple project",
"Description" : "Project",
"Forms" : [
"5a6eeb9e3bf43426d4d31774"
]
}, {
"_id" : "5a856d850290f7eca89e9a85",
"Name" : "Test Project",
"Description" : "For testing purpose",
"Forms" : [
"5a62e2a8f85cf41bd0a8f522", // this first element of array should not come in result because it is not in Employee document
"5a6eec263bf43426d4d31780"
]
}
]
}
]
}
I want only that Forms which is in Employees document.In above 5a62e2a8f85cf41bd0a8f522 entry should not come because that entry is not in Employees document
For this I am doing aggregation as below,
db.Employees.aggregate([{
$match: {
_id: ObjectId("5a852dcd0290f7eca89e9a79")
}
}, {
$unwind: "$Forms"
}, {
$lookup: {
from: "Clients",
localField: "Forms.ClientId",
foreignField: "_id",
as: "Clients"
}
}, {
$unwind: "$Clients"
}, {
$unwind: "$Clients.Projects"
}, {
$unwind: "$Clients.Projects.Forms"
}, {
$redact: {
$cond: {
if : {
$and: [{
$eq: ["$Forms.ProjectId", "$Clients.Projects._id"]
}, {
$eq: ["$Forms.FormId", "$Clients.Projects.Forms"]
}
]
},
then: "$$KEEP",
else : "$$PRUNE"
}
}
}, {
$group: {
_id: {
_id: "$_id",
ClientId: "$Clients._id",
ProjectId: "$Forms.ProjectId"
},
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Client: {
$first: "$Clients"
},
Projects: {
$first: "$Clients.Projects"
},
Forms: {
$push: "$Clients.Projects.Forms"
}
}
}, {
$group: {
_id: {
_id: "$_id._id",
ClientId: "$Clients._id",
},
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Client: {
$first: "$Client"
},
Projects: {
$push: {
_id: "$Projects._id",
Name: "$Projects.Name",
Description: "$Projects.Description",
Forms : "$Forms"
}
}
}
}, {
$group: {
_id: "$_id._id",
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Clients: {
$push: {
_id: "$Client._id",
CompanyName: "$Client.CompanyName",
Email: "$Client.Email",
PhoneNumber: "$Client.PhoneNumber",
Projects: "$Projects"
}
}
}
}
]).pretty()
This is working fine for me, but i want only that form which is in Employees document

There is one level of $unwind /$group pair missing here. Currently your $redact stage get's as an input document like this:
{
"_id" : ObjectId("5a852dcd0290f7eca89e9a79"),
"FirstName" : "raj",
"LastName" : "patel",
"Forms" : {
"ClientId" : ObjectId("5a8528ed0290f7eca89e9a5f"),
"ProjectId" : ObjectId("5a856d850290f7eca89e9a85"),
"FormId" : ObjectId("5a6eec263bf43426d4d31780")
},
"Clients" : {
"_id" : ObjectId("5a8528ed0290f7eca89e9a5f"),
"CompanyName" : "test",
"Email" : "test#test.com",
"PhoneNumber" : "(987)654-3210",
"Projects" : {
"_id" : ObjectId("5a856d850290f7eca89e9a85"),
"Name" : "Test Project",
"Description" : "For testing purpose",
"Forms" : [
ObjectId("5a62e2a8f85cf41bd0a8f522"),
ObjectId("5a6eec263bf43426d4d31780")
]
}
}
}
So you're matching FormId with an array of forms which will return true if any element of that array matches. You have to add
{
$unwind: "$Clients.Projects.Forms"
}
to match values with each other.
EDIT:
Entire working aggregation:
db.Employees.aggregate([{
$match: {
_id: ObjectId("5a852dcd0290f7eca89e9a79")
}
}, {
$unwind: "$Forms"
}, {
$lookup: {
from: "Clients",
localField: "Forms.ClientId",
foreignField: "_id",
as: "Clients"
}
}, {
$unwind: "$Clients"
}, {
$unwind: "$Clients.Projects"
}, {
$unwind: "$Clients.Projects.Forms"
}, {
$redact: {
$cond: {
if : {
$and: [{
$eq: ["$Forms.ProjectId", "$Clients.Projects._id"]
}, {
$eq: ["$Forms.FormId", "$Clients.Projects.Forms"]
}
]
},
then: "$$KEEP",
else : "$$PRUNE"
}
}
}, {
$group: {
_id: {
_id: "$_id",
ClientId: "$Clients._id",
ProjectId: "$Clients.Projects._id"
},
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Client: {
$first: "$Clients"
},
Projects: {
$first: "$Clients.Projects"
},
Forms: {
$push: "$Clients.Projects.Forms"
}
}
}, {
$group: {
_id: {
_id: "$_id._id",
ClientId: "$_id.ClientId",
},
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Client: {
$first: "$Client"
},
Projects: {
$push: {
_id: "$Projects._id",
Name: "$Projects.Name",
Description: "$Projects.Description",
Forms : "$Forms"
}
}
}
}, {
$group: {
_id: "$_id._id",
FirstName: {
$first: "$FirstName"
},
LastName: {
$first: "$LastName"
},
Clients: {
$push: {
_id: "$Client._id",
CompanyName: "$Client.CompanyName",
Email: "$Client.Email",
PhoneNumber: "$Client.PhoneNumber",
Projects: "$Projects"
}
}
}
}
]).pretty()

Related

How to fetch all fields of parent document?

I have tried to fetch all fields of parent document using $group but it send only specific fields.
Another Question. How to fetch all fields of parent document using '$project'?
Collection "Opportunity"
Document #1
{
"_id" : ObjectId("5c42b737cd94891a57fbf33e"),
"accountId" : [
ObjectId("5c29f198b248d845931a1830"),
ObjectId("5c29f198b248d845931a1831"),
]
"name" : "OppNewTest01",
"value" : 10000,
"status" : "open",
}
Collection "Account"
Document #1
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
}
My MongoDB Query,
con.collection('opportunity').aggregate([
{
$unwind: "account"
},
{
$lookup: {
from: 'account',
localField: 'accountId',
foreignField: '_id',
as: 'accounts',
},
},
{
$unwind: '$accounts'
},
{
$group: {
"_id": "$_id",
"account": { "$push": "$accounts" }
}
},
]).toArray((err, res) => {
cbFun(res, err);
});
Output of the above query,
{
"_id" : "5c42b737cd94891a57fbf33e",
"account": [
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
},
{
"_id" : ObjectId("5c29f198b248d845931a1831"),
"name" : "MyTestAcc1",
"phone" : "7845124579",
},
]
}
I want to below output,
{
"_id" : "5c42b737cd94891a57fbf33e",
"accountId" : [
ObjectId("5c29f198b248d845931a1830"),
ObjectId("5c29f198b248d845931a1831"),
],
"name" : "OppNewTest01",
"value" : 10000,
"status" : "open",
"account": [
{
"_id" : ObjectId("5c29f198b248d845931a1830"),
"name" : "MyTestAcc",
"phone" : "7845124578",
},
{
"_id" : ObjectId("5c29f198b248d845931a1831"),
"name" : "MyTestAcc1",
"phone" : "7845124579",
},
]
}
I have tried to fetch above output but not getting.
You are just losing that data when you $group, save those fields in that stage to keep them.
{
$group: {
"_id": "$_id",
"account": { "$push": "$accounts" },
value: {$first: "$value"},
status: {$first: "$status},
name: {$first: "$name"},
accountId: {$push: "$accountId},
}
}
EDIT:
[
{
$group: {
"_id": "$_id",
"accounts": { "$push": "$accounts" },
"doc": {$first: "$$ROOT"},
}
},
{
$addFields: {
"doc.accounts" :"$accounts"
}
}
]

Match documents with their inner array element variables in MongoDB

I can't understand how to compare a document variable to another document variable. My goal is to match all Authors who have at least one book written in their mothertongue (native language).
However, after unwinding the books array, My $match: { mothertongue: "$bookLang"}} doesn't return return anything, eventhough they're the same in the $project stage.
Can you help me without javascript?
This is my current query:
db.author.aggregate([
{
$unwind: "$books"
},
{
$project: {
books: true,
mothertongue: true,
bookLang: "$books.lang"
}
},
{
$match: { mothertongue: "$bookLang"}
}
])
And here is a sample of the dataset
{
"_id" : ObjectId("5aa7b34a338571a7470be0eb"),
"fname" : "Minna",
"lname" : "Canth",
"mothertongue" : "Finnish",
"birthdate" : ISODate("1844-03-19T00:00:00Z"),
"deathdate" : ISODate("1897-05-12T00:00:00Z"),
"books" : [
{
"title" : "Anna Liisa",
"lang" : "Finnish",
"language" : "finnish",
"edition" : 1,
"cover" : "Hard",
"year" : 1895,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Tammi",
"pubId" : ObjectId("5aa7b34a338571a7470be0e4")
}
]
},
{
"title" : "The Burglary and The House of Roinila",
"lang" : "English (UK)",
"translator" : ObjectId("5aa7b34a338571a7470be0ee"),
"cover" : "Soft",
"year" : 2010,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Jonathan Cape",
"pubId" : ObjectId("5aa7b34a338571a7470be0e7")
}
]
},
{
"title" : "Anna Liisa 2 ed.",
"lang" : "Finnish",
"language" : "finnish",
"edition" : 2,
"cover" : "hard",
"year" : 1958,
"categorytags" : [
"Finland"
],
"publisher" : [
{
"name" : "Otava",
"pubId" : ObjectId("5aa7b34a338571a7470be0e9")
}
]
}
]
}
End goal. note I'm not interested in formatting just yet, just the filtering
{
"Author" : "Charles Bukowski",
"BooksInMothertongue" : [
"Love Is a Dog from Hell"
]
}
{
"Author" : "Minna Canth",
"BooksInMothertongue" : [
"Anna Liisa",
"Anna Liisa 2 ed."
]
}
...
Try this
db.author.aggregate([{
$match: {
books: {
$ne: []
}
}
},
{
$project: {
books: {
$filter: {
input: "$books",
as: "book",
cond: {
$eq: ["$$book.lang", "$mothertongue"]
}
}
},
fname: 1
}
}, {
$unwind: "$books"
},
{
$group: {
_id: "$_id",
Author: {
$first: '$fname'
},
BooksInMothertongue: {
$push: "$books.title"
}
}
}
])

Aggregate by array field with object ids

Is there a better way of retrieving values from a collection2 based on an array field with object ids in collection1? I've tried to use $project but failed to get all required fields
Collections to aggregate:
collection1:
{
"_id" : ObjectId("5a58910de202796cfef41c6a"),
"sortOrder" : 5,
"title" : "Question 1 ?",
"freeTextIncluded" : false,
"freeText" : false,
"resultChart" : "pieChart",
"answer" : [
ObjectId("5a579fefd5554706b446cc71"),
ObjectId("5a587f17e4b2de0d683f96a4"),
ObjectId("5a587f20e4b2de0d683f96a5"),
ObjectId("5a587f29e4b2de0d683f96a6")
],
"state" : "active",
"__v" : 1,
"description" : ""
}
collection2:
{
"_id" : ObjectId("5a579fefd5554706b446cc71"),
"slug" : "answer-1",
"title" : "Answer 1",
"state" : "active",
"__v" : 0,
"author" : ObjectId("5a2e6b56e593c8525ced34b8"),
"body" : "<p>Lipsum...</p>"
}
{
"_id" : ObjectId("5a587f17e4b2de0d683f96a4"),
"slug" : "answer-2",
"title" : "Answer 2",
"state" : "active",
"__v" : 0,
"body" : ""
}
{
"_id" : ObjectId("5a587f20e4b2de0d683f96a5"),
"slug" : "answer-3",
"title" : "Answer 3",
"state" : "active",
"__v" : 0,
"body" : "",
"isCorrect" : true,
"sortOrder" : 3
}
{
"_id" : ObjectId("5a587f29e4b2de0d683f96a6"),
"slug" : "answer-4",
"title" : "Answer 4",
"state" : "active",
"__v" : 0,
"body" : ""
}
This aggregation works ok but I'm just wondering if there's a better/shorter way of aggregating 2 collections...
db.getCollection('questions').aggregate([
{
$match: {'_id': ObjectId('5a58910de202796cfef41c6a') }
},
{
$unwind: "$answer"
},
{
$lookup:
{
from: "answers",
localField: "answer",
foreignField: "_id",
as: "answers"
}
},
{
$match: { "answers": { $ne: [] }}
},
{
$unwind: "$answers"
},
{
$group: {
_id : ObjectId('5a58910de202796cfef41c6a'),
answerList: {$push: "$answers"},
title: {$first: "$title"},
sortOrder: {$first: "$sortOrder"},
description: {$first: "$description"},
resultChart: {$first: "$resultChart"},
freeTextIncluded: {$first: "$freeTextIncluded"},
}
}
]);
You need to improve your query like this:
db.getCollection('test').aggregate([{
$match: {
'_id': ObjectId('5a58910de202796cfef41c6a')
}
},
{
$lookup: {
from: "answers",
localField: "answer",
foreignField: "_id",
as: "answers"
}
},
{
$unwind: {
path: "$answers",
preserveNullAndEmptyArrays: true
}
},
{
$group: {
_id: ObjectId('5a58910de202796cfef41c6a'),
answerList: {
$push: "$answers"
},
title: {
$first: "$title"
},
sortOrder: {
$first: "$sortOrder"
},
description: {
$first: "$description"
},
resultChart: {
$first: "$resultChart"
},
freeTextIncluded: {
$first: "$freeTextIncluded"
},
}
}])

Issue retrieving subdocuments from MongoDB

I have the following dataset:
{
"_id" : ObjectId("59668a22734d1d48cf34de08"),
"name" : "Nobody Cares",
"menus" : [
{
"_id" : "menu_123",
"name" : "Weekend Menu",
"description" : "A menu for the weekend",
"groups" : [
{
"name" : "Spirits",
"has_mixers" : true,
"sizes" : [
"Single",
"Double"
],
"categories" : [
{
"name" : "Vodka",
"description" : "Maybe not necessary?",
"drinks" : [
{
"_id" : "drink_123",
"name" : "Absolut",
"description" : "Fancy ass vodka",
"sizes" : [
{
"_id" : "size_123",
"size" : "Single",
"price" : 300
}
]
}
]
}
]
}
],
"mixers" : [
{
"_id" : "mixer_1",
"name" : "Coca Cola",
"price" : 150
},
{
"_id" : "mixer_2",
"name" : "Lemonade",
"price" : 120
}
]
}
]
}
And I'm attempting to retrieve a single drink from that dataset, I'm using the following aggregate query:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $unwind: "$menus" },
{ $project: { "_id": 1, "menus": { "groups": { "categories": { "drinks": { "name": 1 } } } } } }
])
However, it's returning the full structure of the dataset along with the correct data.
So instead of:
{
"_id": "drink_123",
"name": "Absolut"
}
I get:
{
"_id": ObjectId("59668a22734d1d48cf34de08"),
"menus": {
"groups": {
"categories": {
"drinks": { "name": "Absolut" }
}
}
}
}
For example. Any ideas how to just retrieve the subdocument?
If you need to retain the deeply nested model then this call will produce the desired output:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $project: {"_id": '$menus.groups.categories.drinks._id', name: '$menus.groups.categories.drinks.name'}},
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" }
])
The numerous unwinds are the result of the deep nesting of the drinks subdocuments.
Though, FWIW, this sort of query does perhaps suggest that the model isn't 'read friendly'.

Query that combines $project, $unwind & $group

I have the following data set:
{
"_id" : ObjectId("57684f2b61f2af6d49fa6dbd"),
"firstname" : "First1",
"surname" : "Sur1",
"email" : "first1#sur1.com",
"goals" : [
{
"gId" : "base1",
"recordDate" : ISODate("2016-06-21T20:05:48.972Z")
},
{
"gId" : "base2",
"recordDate" : ISODate("2016-06-21T20:05:48.972Z")
},
{
"gId" : "base1",
"recordDate" : ISODate("2016-06-21T20:05:48.972Z")
}
]
}
I need the following result:
{
"_id" : ObjectId("57684f2b61f2af6d49fa6dbd"),
"firstname" : "First1",
"surname" : "Sur1",
"email" : "first1#sur1.com",
"goals" : [
{
"gId" : "base1",
"count" : 2
},
{
"gId" : "base2",
"count" : 1
}
]
}
So far I played around with the aggregate query but I couldn't find the solution to my problem. My query looks like that but it doesn't work. The first bit $project runs fine on its own and so does $unwind and $group but I don't know how I can combine it together.
db.getCollection('users').aggregate(
{
$project : {
firstname: "$firstname",
surname: "$surname",
email: "$email",
goals: "$goals"
}
},
{
$unwind: '$goals'
},
{
$group: {
gid: '$goals.gId',
count: {'$sum': 1}
}
}
)
Thanks in advance,
Tom
Try it with the following pipeline
db.getCollection('users').aggregate(
{
$unwind: '$goals'
},
{
$group: {
_id: {
firstname: "$firstname",
surname: "$surname",
email: "$email",
gId: "$goals.gId"
},
count: {'$sum': 1}
}
},
{
$group: {
_id: {
firstname: "$_id.firstname",
surname: "$_id.surname",
email: "$_id.email"
},
goals: {
$push: {
gId: "$_id.gId",
count: "$count"
}
}
}
},
{
$project: {
_id: 0,
firstname: "$_id.firstname",
surname: "$_id.surname",
email: "$_id.email",
goals: 1
}
}
)
Result looks like this
{
"goals" : [
{
"gId" : "base2",
"count" : 1.0
},
{
"gId" : "base1",
"count" : 2.0
}
],
"firstname" : "First1",
"surname" : "Sur1",
"email" : "first1#sur1.com"
}