How to Populate data using Mongodb Aggregate framework? - mongodb

I have a current MongoDB Aggregate framework pipeline from a previous question and I am unable to add populate query to grab user profile by id.
My code is below
Product.aggregate([
{
$group: {
_id: {
hub: "$hub",
status: "$productStatus",
},
count: { $sum: 1 },
},
},
{
$group: {
_id: "$_id.hub",
counts: {
$push: {
k: "$_id.status",
v: "$count",
},
},
},
},
{
$group: {
_id: null,
counts: {
$push: {
k: { $toString: "$_id" },
v: "$counts",
},
},
},
},
{
$addFields: {
counts: {
$map: {
input: "$counts",
in: {
$mergeObjects: [
"$$this",
{ v: { $arrayToObject: "$$this.v" } },
],
},
},
},
},
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$counts" },
},
},
]);
and got the following result
[
{
"5fe75679e6f7a62ddaf5b2e9": {
"in progress": 5,
"Cancelled": 4,
"return": 1,
"on the way": 3,
"pending": 13,
"Delivered": 4
}
}
]
Expected Output
I need to grab user information from user collections using the hubId "5fe75679e6f7a62ddaf5b2e9" and expect a final result of the form below
[
{
"hub": {
"photo": "avatar.jpg",
"_id": "5fe75679e6f7a62ddaf5b2e9",
"name": "Dhaka Branch",
"phone": "34534543"
},
"statusCounts": {
"in progress": 5,
"Cancelled": 4,
"return": 1,
"on the way": 3,
"pending": 13,
"Delivered": 4
}
}
]
First id is user id and available in user collections.

You need to tweak your aggregate pipeline a little bit and include new pipeline stage like $lookup that populates the
hub
Product.aggregate([
{ "$group": {
"_id": {
"hubId": "$hubId",
"status": "$productStatus"
},
"count": { "$sum": 1 }
} },
{ "$group": {
"_id": "$_id.hubId",
"statusCounts": {
"$push": {
"k": "$_id.status",
"v": "$count"
}
}
} },
{ "$lookup": {
"fron": "users",
"localField": "_id",
"foreignField": "_id",
"as": "user"
} },
{ "$project": {
"user": { "$arrayElemAt": ["$user", 0] },
// "hub": { "$first": "$hub" },
"statusCounts": { "$arrayToObject": "$statusCounts" }
} }
])
To project only some fields in the user profile, you can update your $lookup pipeline to have the form
{ "$lookup": {
"from": "users",
"let": { "userId": "$_id" },
"pipeline": [
{ "$match": {
"$expr": { "$eq": ["$_id", "$$userId"] }
} },
{ "$project": {
"name": 1,
"phone": 1,
"photo": 1
} }
],
"as": "user"
} }

Related

Find one user then get their ranking based on their total points using MongoDB

So I got the following data:
Users collection
{
_id: ObjectId("62a2a0422ec90fea68390aaa"),
name: 'Robert Yamashita',
username: 'robyama',
email: 'robert.yamashita#rocketmail.com',
},
{
_id: ObjectId("62a2a0452ec90fea68390aad"),
name: 'Charles X',
username: 'cvx',
email: 'charles.xxx#rocketmail.com',
}
Points collection
{
userId: ObjectId("62a2a0422ec90fea68390aaa"),
action: 'Liked a post',
points: 10,
}
{
userId: ObjectId("62a2a0422ec90fea68390aaa"),
action: 'Liked a post',
points: 10,
}
{
userId: ObjectId("62a2a0452ec90fea68390aad"),
action: 'Liked a comment',
points: 5,
}
I created a pipeline to get the total points of username robyama using the following query:
db.users.aggregate([
{ $match: { username: 'robyama' } },
{
$lookup: {
from: 'points',
localField: '_id',
foreignField: 'user',
as: 'userPoints'
}
},
{
$unwind: '$userPoints'
},
{
$group: {
_id: {
name: '$name',
email: '$email',
username: '$username',
},
count: { $sum: '$userPoints.points' }
}
}
]);
I got the following result:
{
"_id": {
"name": "Robert Yamashita",
"email": "robert.yamashita#rocketmail.com",
"username": "robyama",
},
"count": 20
}
This is exactly what I needed but I wanted to add a ranking field to the returned query since Robert has 20 points and Charles only has 5. So ideally I want the result to be this:
{
"_id": {
"name": "Robert Yamashita",
"email": "robert.yamashita#rocketmail.com",
"username": "robyama",
},
"count": 20
"rank": 1
}
What should I add to my pipeline to get the above output? Any help would be greatly appreciated!
Here's another way to do it. There's only one "$lookup" with one embedded "$group" so it should be fairly efficient. The "$project" seems a bit contrived, but it gives the output in the format you want.
db.users.aggregate([
{
"$match": {
"username": "robyama"
}
},
{
"$lookup": {
"from": "points",
"as": "sortedPoints",
"pipeline": [
{
"$group": {
"_id": "$userId",
"count": {"$sum": "$points"}
}
},
{"$sort": {"count": -1}}
]
}
},
{
"$unwind": {
"path": "$sortedPoints",
"includeArrayIndex": "idx"
}
},
{
"$match": {
"$expr": {
"$eq": ["$_id", "$sortedPoints._id"]
}
}
},
{
"$project": {
"_id": {
"name": "$name",
"username": "$username",
"email": "$email"
},
"count": "$sortedPoints.count",
"rank": {
"$add": ["$idx", 1]
}
}
}
])
Try it on mongoplayground.net.
Well, this is one way of doing it.
Perform join using $lookup and calculate counts for each user.
Sort the elements by counts in desc order.
Group documents by _id as NULL and push them all in an array.
Unwind the array, along with getting row numbers.
Find your required document and calculate the rank using row number.
db.users.aggregate([
{
$lookup: {
from: "points",
localField: "_id",
foreignField: "userId",
as: "userPoints"
}
},
{
$unwind: "$userPoints"
},
{
$group: {
_id: {
name: "$name",
email: "$email",
username: "$username",
},
count: {
$sum: "$userPoints.points"
}
}
},
{
"$sort": {
count: -1
}
},
{
"$group": {
"_id": null,
"docs": {
"$push": "$$ROOT",
}
}
},
{
"$unwind": {
path: "$docs",
includeArrayIndex: "rownum"
}
},
{
"$match": {
"docs._id.username": "robyama"
}
},
{
"$addFields": {
"docs.rank": {
"$add": [
"$rownum",
1
]
}
}
},
{
"$replaceRoot": {
"newRoot": "$docs"
}
}
])
This is the playground link.

How can I get a mongo subset of a collection based on an another collection

I have two collections.
Collection 1 is like an account.
Collection 2 creates a unique association between a user and an account
I am trying to return the accounts for which the user has no association
Collection1 schema
const Collection1Schema = new Schema({
name: { type: String, required: true },
});
Collection1 data
[
{
"_id": "61cf8452fca008360872c9cd",
"name": "Aff 2"
},
{
"_id": "61cf845ffca008360872c9d0",
"name": "AFF 1"
},
{
"_id": "61cf8468fca008360872c9d3",
"name": "Aff 3"
}
]
Collection2 schema
const Collection2Schema = new Schema({
userID: { type: Schema.Types.ObjectId, required: true },
col_1_ID: { type: Schema.Types.ObjectId, required: true },
});
Collection2 data
[
{
"_id": "61e05bb5fe1d8327d4c73663",
"userID": "61cf82dac828bd519cfd38ca",
"col_1_ID": "61cf845ffca008360872c9d0"
},
{
"_id": "61e05c14fe1d8327d4c7367d",
"userID": "61cf82dac828bd519cfd38ca",
"col_1_ID": "61cf8468fca008360872c9d3"
},
{
"_id": "61e05ca0fe1d8327d4c73695",
"userID": "61e05906246ccc41d4ebd30f",
"col_1_ID": "61cf8452fca008360872c9cd"
}
]
This is what I have so far... but it does not return what the user is NOT apart of
I am using Collection2 as the basis in the pipeline
[
{
'$match': {
'userID': new ObjectId('61cf82dac828bd519cfd38ca')
}
}, {
'$lookup': {
'from': 'Collection1',
'localField': 'col_1_ID',
'foreignField': '_id',
'as': 'aa'
}
}, {
'$unwind': {
'path': '$aa',
'preserveNullAndEmptyArrays': true
}
}
]
What I would like to return is all the collection 1 documents ( where userIdD = '61cf82dac828bd519cfd38ca') is NOT associated in collection 2 ... like this :
[
{
"_id": "61cf8452fca008360872c9cd",
"name": "Aff 2"
}
]
UPDATE 1
Here is a playground where another user has joined another account, so the pipeline does not return "Aff 2" like expected
https://mongoplayground.net/p/W6W88_2MaI3
UPDATE 2
Here is a playground that almost does what I want... it's returning duplication "AFF 2" entries.
https://mongoplayground.net/p/nTI3MKNPEmD
try the inversing lookup
https://mongoplayground.net/p/hXAYyv8X461
db.Collection1.aggregate([
{
"$lookup": {
"from": "Collection2",
"localField": "_id",
"foreignField": "col_1_ID",
"as": "joined_docs"
}
},
{
$unwind: {
"path": "$joined_docs"
}
},
{
$match: {
"joined_docs.userID": {
$ne: "61cf82dac828bd519cfd38ca"
}
}
},
{
$project: {
"joined_docs": 0
}
}
])
ANSWER:
after messing around with several mongo playgrounds and digging into a few different pipeline attributes... here is what works:
https://mongoplayground.net/p/xbZeRfVcrZq
Data:
db={
"Collection1": [
{
"_id": "61cf8452fca008360872c9cd",
"name": "Aff 2"
},
{
"_id": "61cf845ffca008360872c9d0",
"name": "AFF 1"
},
{
"_id": "61cf8468fca008360872c9d3",
"name": "Aff 3"
}
],
"Collection2": [
{
"_id": "61e05bb5fe1d8327d4c73663",
"userID": "61cf82dac828bd519cfd38ca",
"col_1_ID": "61cf845ffca008360872c9d0"
},
{
"_id": "61e05c14fe1d8327d4c7367d",
"userID": "61cf82dac828bd519cfd38ca",
"col_1_ID": "61cf8468fca008360872c9d3"
},
{
"_id": "61e05ca0fe1d8327d4c73695",
"userID": "61e05906246ccc41d4ebd30f",
"col_1_ID": "61cf8452fca008360872c9cd"
},
{
"_id": "61e05c14fe1d8327d4c73600",
"userID": "61cf82dac828bd519cfd3111",
"col_1_ID": "61cf8468fca008360872c9d3"
},
{
"_id": "61e05c14fe1d8327d4c73601",
"userID": "61cf82dac828bd519cfd3112",
"col_1_ID": "61cf8452fca008360872c9cd"
},
]
}
Pipeline:
db.Collection1.aggregate([
{
"$lookup": {
"from": "Collection2",
"localField": "_id",
"foreignField": "col_1_ID",
"as": "joined_docs"
}
},
{
$match: {
"joined_docs.userID": {
$ne: "61cf82dac828bd519cfd38ca"
}
}
},
{
$unwind: {
"path": "$joined_docs",
}
},
{
$group: {
_id: "$_id",
"name": {
"$first": "$name"
},
}
}
])
result:
[
{
"_id": "61cf8452fca008360872c9cd",
"name": "Aff 2"
}
]
try this instead:
https://mongoplayground.net/p/HDm2sbdvH88
db.Collection1.aggregate([
{
"$lookup": {
"from": "Collection2",
"localField": "_id",
"foreignField": "col_1_ID",
"as": "joined_docs"
}
},
{
$unwind: {
"path": "$joined_docs"
}
},
{
$group: {
_id: {
account_id: "$_id",
account_name: "$name",
},
user_ids: {
$push: {
"userID": "$joined_docs.userID"
}
}
}
},
{
$match: {
"user_ids.userID": {
$nin: [
"61cf82dac828bd519cfd38ca"
]
}
}
},
{
$project: {
user_ids: 0
}
}
])

$lookup in nested array

I need a MongoDB query to return the aggregation result from a collection of events, users and confirmations.
db.events.aggregate([
{
"$match": {
"_id": "1"
}
},
{
"$lookup": {
"from": "confirmations",
"as": "confirmations",
"let": {
"eventId": "$_id"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$eventId",
"$$eventId"
]
}
}
},
{
"$lookup": {
"from": "users",
"as": "user",
"let": {
"userId": "$confirmations.userId"
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": [
"$_id",
"$$userId"
]
}
}
},
]
},
},
]
}
}
])
Desired
[
{
"_id": "1",
"confirmations": [
{
"_id": "1",
"eventId": "1",
"user": {
"_id": "1",
"name": "X"
},
"userId": "1"
},
{
"_id": "2",
"eventId": "1",
"user": {
"_id": "2",
"name": "Y"
},
"userId": "2"
}
],
"title": "foo"
}
]
Everything works except the embedded user in confirmations array. I need the output to show the confirmations.user, not an empty array.
Playgound: https://mongoplayground.net/p/jp49FW59WCv
You made mistake in variable declaration of inner $lookup. Try this Solution:
db.events.aggregate([
{
"$match": {
"_id": "1"
}
},
{
$lookup: {
from: "confirmations",
let: { "eventId": "$_id" },
pipeline: [
{
$match: {
"$expr": {
$eq: ["$eventId", "$$eventId"]
}
}
},
{
$lookup: {
from: "users",
let: { "userId": "$userId" },
pipeline: [
{
$match: {
$expr: {
$eq: ["$_id", "$$userId"]
}
}
}
],
as: "user"
}
},
{ $unwind: "$user" }
],
as: "confirmations"
}
}
])
Also instead of $unwind of user inside inner $lookup you can use:
{
$addFields: {
user: { $arrayElemAt: ["$user", 0] }
}
}
since $unwind will not preserve empty results from previous stage by default.

Referring to a different collection from an existing one and counting from the same collection

I have to collections A and B in which the documents of A contains the object ids which are present in the B in the fields centre and gcentre. What I'm currently is outputting the result which contains the name of the parent centre by referring from collection A's object id to B and then referring the gcentre's id to find the child and centre and count the documents assigned via javascript post-processing. Been new to the aggregation pipeline, I don't know how to refer via object id and that sort of counting of records. Is it possible with the aggregation pipeline? I have tried with $lookup but it doesn't seem to give the output as expected.
Documents in collection A:
{
"_id": {
"$oid": "5bbafa98d8c77251bea30f8c"
},
"parentCentre": {
"$oid": "1cbafa99d8c77251bea30f11"
},
"childCentre": {
"$oid": "5cbafa99d8c77251bea30f8d"
},
},
{
"_id": {
"$oid": "5bbafa98d8c77251bea30f8c"
},
"parentCentre": {
"$oid": "1cbafa99d8c77251bea30f11"
},
"childCentre": {
"$oid": "5cbafa99d8c77251bea30f8d"
},
},
{
"_id": {
"$oid": "5bbafa98d8c77251bea30f8c"
},
"parentCentre": {
"$oid": "1cbafa99d8c77251bea30f11"
},
"childCentre": {
"$oid": "5cbafa99d8c77251bea30f8d"
},
},
{
"_id": {
"$oid": "5bbafa98d8c77251bea30f8c"
},
"parentCentre": {
"$oid": "1cbafa99d8c77251bea30f21"
},
"childCentre": {
"$oid": "5cbafa99d8c77251bea30f6d"
},
}
Documents in collection B:
{
"_id": {
"$oid": "1cbafa99d8c77251bea30f11"
},
"Type": "Parent",
"Name": "Kris Labs"
},
{
"_id": {
"$oid": "1cbafa99d8c77251bea30f21"
},
"Type": "Parent",
"Name": "DEX Labs"
},
{
"_id": {
"$oid": "5cbafa99d8c77251bea30f8d"
},
"Type": "Child",
"Name": "Mili Labs"
},
{
"_id": {
"$oid": "5cbafa99d8c77251bea30f6d"
},
"Type": "Child",
"Name": "Max Labs"
}
Result:
{
"parentCentreName":"Kris Labs",
"Records":{
{
childCentreName: "Max Labs",
recordCount: 3
}
}
},
{
"parentCentreName":"DEX Labs",
"Records":{
{
childCentreName: "Mili Labs",
recordCount: 1
}
}
}
You can use the following aggregation query:
db.A.aggregate([
{
$group: {
_id: {
p: "$parentCentre",
c: "$childCentre"
},
count: {
$sum: 1
}
}
},
{
$group: {
_id: "$_id.p",
Records: {
$push: {
childCentreName: "$_id.c",
recordCount: "$count"
}
}
}
},
{
$unwind: "$Records"
},
{
"$lookup": {
"from": "B",
"localField": "_id",
"foreignField": "_id",
"as": "p"
}
},
{
"$lookup": {
"from": "B",
"localField": "Records.childCentreName",
"foreignField": "_id",
"as": "c"
}
},
{
$unwind: "$c"
},
{
$unwind: "$p"
},
{
$project: {
"parentCentreName": "$p.Name",
"Records.childCentreName": "$c.Name",
"Records.recordCount": 1,
_id: 0
}
},
{
$group: {
_id: "$parentCentreName",
"Records": {
$push: "$Records"
}
}
},
{
$project: {
"parentCentreName": "$_id",
"Records": 1,
_id: 0
}
}
])
MongoDB Playground

How to check if an element exists in a document and return true or false depending on it?

I have an aggregation query in which I use $lookup to get data from other collections. But I cannot understand how do I get a boolean value if a $match is found.
Schema
const likesSchema = new mongoose.Schema({
user: {
id: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
},
storyID: {
type: String,
required: true,
}
}, {
timestamps: true
});
Complete Query
const user_id = req.authorizedUser.sub;
const stories = await Story.aggregate([
{
$lookup: {
from: "comments",
localField: "storyID",
foreignField: "storyID",
as: "comments"
},
},
{
$lookup: {
from: "likes",
let: {storyID: "$storyID"},
pipeline: [
{
$match: {
$expr: { $eq: ["$$storyID", "$storyID"] }
}
},
{
$facet: {
"total": [{ $count: "count" }],
"byMe": [{
$match: {
$expr: { $eq: ["$user.id", user_id] } // Need boolean value if found/ not found
}
}]
}
}
],
as: "likes"
}
},
Snippet of Response
"likes": [
{
"total": [
{
"count": 2
}
],
"byMe": [
{
"_id": "5d04fe8e982bb50bbcbd2b48",
"user": {
"id": "63p6PpPyOh",
"name": "Ayan Dey"
},
"storyID": "b0g5GA6ZJFKkJcnJlp6w8qGR",
"createdAt": "2019-06-15T14:19:58.531Z",
"updatedAt": "2019-06-15T14:19:58.531Z",
"__v": 0
}
]
}
]
Required Response
"likes": {
"total": 2,
"byMe": true
}
You can use below aggregation
{ "$lookup": {
"from": "likes",
"let": { "storyID": "$storyID" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$$storyID", "$storyID"] }}}
],
"as": "likes1"
}},
{ "$addFields": {
"likes.total": { "$size": "$likes1" },
"likes.byMe": { "$ne": [{ "$indexOfArray": ["$likes1.user.id", user_id] }, -1] }
}},
{ "$project": { "likes1": 0 }}
Or
{ "$lookup": {
"from": "likes",
"let": { "storyID": "$storyID" },
"pipeline": [
{ "$match": { "$expr": { "$eq": ["$$storyID", "$storyID"] }}},
{ "$facet": {
"total": [{ "$count": "count" }],
"byMe": [{ "$match": { "$expr": { "$eq": ["$user.id", user_id] }}}]
}}
{ "$project": {
"total": {
"$ifNull": [{ "$arrayElemAt": ["$total.count", 0] }, 0 ]
},
"byMe": { "$ne": [{ "$size": "$byMe" }, 0] }
}}
],
"as": "likes"
}},
{ "$unwind": "$likes" }