How to fetch all fields of parent document? - mongodb

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"
}
}
]

Related

Mongodb Aggregation get Data per user

Report table sample data
{
"_id" : ObjectId("614415f4a6566a001623b622"),
"record" : [
{
"dateTime" : ISODate("2021-09-17T04:13:39.465Z"),
"status" : "time-in",
"month" : 9,
"day" : 17,
"year" : 2021,
"time" : 1631852019465.0,
"date" : ISODate("2021-09-17T00:00:00.000Z"),
},
{
"dateTime" : ISODate("2021-09-17T04:14:01.182Z"),
"status" : "time-out",
"month" : 9,
"day" : 17,
"year" : 2021,
"time" : 1631852041182.0,
"date" : ISODate("2021-09-17T00:00:00.000Z"),
}
],
"uid" : ObjectId("614415b0a6566a001623b80b"),
"date" : ISODate("2021-09-17T00:00:00.000Z"),
"status" : "time-out",
"createdAt" : ISODate("2021-09-17T04:13:40.102Z"),
"updatedAt" : ISODate("2021-09-17T04:14:01.831Z"),
"__v" : 0
}
Users table sample data
{
"_id" : ObjectId("615c0f6db30aff375cd05ac1"),
"displayName" : "test test",
"firstName" : "test",
"lastName" : "test",
"email" : "test#gmail.com",
"brand" : "Jollibee",
"phone" : "+632312312312312",
"role" : 1,
"isVerified" : true,
"isArchived" : false,
"createdAt" : ISODate("2021-10-05T08:40:13.208Z"),
"updatedAt" : ISODate("2021-10-05T08:40:13.208Z"),
"__v" : 0
}
I have a data like this
db.getCollection('users').aggregate([
{
"$match": { brand: "Jollibee" }
},
{
$lookup: {
from: "orders",
let: { id: 'id' },
pipeline: [
{
$match: {
date: { $gte: ISODate("2020-11-01"), $lt: ISODate("2021-11-31") },
}
}
],
as: "orders",
},
},
{
$project: {
"_id": 1,
"name": 1,
"orders": 1
}
}
])
when I'm using this aggregation I'm getting all the data inserted per user.
What I want to happen is that. I will only get the data that belong to the user and not all the data of all users.
Added the sample documents for each collection
You are not comparing the userIds of both collections. You should add that on your $match. Playground
db.users.aggregate([
{
"$match": {
brand: "Jollibee"
}
},
{
$lookup: {
from: "orders",
let: {
id: "$_id"
},
pipeline: [
{
$match: {
date: {
$gte: ISODate("2020-11-01"),
$lt: ISODate("2021-11-30")
},
$expr: {
$eq: [
"$uid",
"$$id"
]
}
}
}
],
as: "orders",
},
},
{
$project: {
"_id": 1,
"name": 1,
"orders": 1
}
}
])

Filter from two collections

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()

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'.

MongoDB - Find first and last in the embedded array for a single matching document

I have a collection of websites, which each contain a list of websites and their keywords that are being tracked. I also have another collection called "rankings" which for each of the keywords in the website contains a ranking. The collection so far looks like this:
{
"_id" : ObjectId("58503934034b512b419a6eab"),
"website" : "https://www.google.com",
"name" : "Google",
"keywords" : [
"Search",
"Websites",
],
"tracking" : [
{
"_id" : ObjectId("5874aa1df63258286528598d"),
"position" : 0,
"created_at" : ISODate("2017-01-1T09:32:13.831Z"),
"real_url" : "https://www.google.com",
"keyword" : "Search"
},
{
"_id" : ObjectId("5874aa1ff63258286528598e"),
"keyword" : "Search",
"real_url" : "https://www.google.com",
"created_at" : ISODate("2017-01-2T09:32:15.832Z"),
"found_url" : "https://google.com/",
"position" : 3
},
{
"_id" : ObjectId("5874aa21f63258286528598f"),
"keyword" : "Search",
"real_url" : "https://www.foamymedia.com",
"created_at" : ISODate("2017-01-3T09:32:17.017Z"),
"found_url" : "https://google.com/",
"position" : 2
},
{
"_id" : ObjectId("5874aa21f63258286528532f"),
"keyword" : "Websites",
"real_url" : "https://www.google.com",
"created_at" : ISODate("2017-01-1T09:32:17.017Z"),
"found_url" : "https://google.com/",
"position" : 1
},
{
"_id" : ObjectId("5874aa21f63258286528542f"),
"keyword" : "Websites",
"real_url" : "https://www.google.com",
"created_at" : ISODate("2017-01-1T09:32:17.017Z"),
"found_url" : "https://google.com/",
"position" : 2
},
]
}
What I want to do is:
1) Group all of the keywords together by their keyword
2) Find the starting position (at the very start of the month)
3) Find the current position (as of today)
So in theory I want to be given an object like:
{
"_id" : ObjectId("58503934034b512b419a6eab"),
"website" : "https://www.google.com",
"tracking" : [
{
"_id" : ObjectId("5874aa1df63258286528598d"),
"keyword": "Search",
"start_position": 0,
"todays_position": 3,
},
{
"_id" : ObjectId("5874aa1df63258286528598d"),
"keyword": "Website",
"start_position": 0,
"todays_position": 2,
},
]
I am confused about how to do the grouping on another field, though. I have tried the following so far:
db.getCollection('websites').aggregate([
{
$lookup: {
from: "seo_tracking",
localField: "website",
foreignField: "real_url",
as: "tracking"
}
},
{
$match: {
"_id" : ObjectId("58503934034b512b419a6eab")
}
},
{
$group: {
"_id" : "$_id",
"keyword" : {
$first: "$tracking.keyword",
},
}
}
]);
But this is not grouping by the keyword, nor can I figure out how I would get the expected value.
You can try something like this. $unwind the tracking array followed by $sort on tracking.keyword and tracking.created_at. $group by tracking.keyword and $first to get starting position, $avg to get average position and $last to get the today's position. Final $group to roll up everything back to tracking array.
db.website.aggregate([{
$match: {
"_id": ObjectId("58503934034b512b419a6eab")
}
}, {
$lookup: {
from: "seo_tracking",
localField: "website",
foreignField: "real_url",
as: "tracking"
}
}, {
$unwind: "$tracking"
}, {
$sort: {
"tracking.keyword": 1,
"tracking.created_at": -1
}
}, {
$group: {
"_id": "$tracking.keyword",
"website": {
$first: "$website"
},
"website_id": {
$first: "$_id"
},
"avg_position": {
$avg: "$tracking.position"
},
"start_position": {
$first: "$tracking.position"
},
"todays_position": {
$last: "$tracking.position"
}
}
}, {
$group: {
"_id": "$website_id",
"website": {
$first: "$website"
},
"tracking": {
$push: {
"keyword": "$_id",
"avg_position":"$avg_position",
"start_position": "$start_position",
"todays_position": "$todays_position"
}
}
}
}]);