count of $lookup result mongodb - mongodb

Using mongodb with NodeJS driver
I have 2 collections. One for department and other for students.
Sample data for Deparmtent.
{
"_id" : ObjectId("5a24d20590d3d12155f3094e"), "name" : "CSE", "hod" :
"abc",
"students" : [
ObjectId("5a2129172c3e542acb78c1f5"),
ObjectId("5a2129172c3e542acb78c1f7"),
ObjectId("5a2129172c3e542acb78c1f9"),
ObjectId("5a2129172c3e542acb78c1fb")
]
}
{
"_id" : ObjectId("5a24d20590d3d12155f3094f"),
"name" : "IT",
"hod" : "xyz", ,
"students" : [
ObjectId("5a2129172c3e542acb78c1f6"),
ObjectId("5a2129172c3e542acb78c1f8"),
ObjectId("5a2129172c3e542acb78c1fa"),
ObjectId("5a2129172c3e542acb78c1fc")
]
}
and sample data for Students
{
"_id" : ObjectId("5a2129172c3e542acb78c1f4"),
"rollNumber" : 11,
"name" : "Thor",
"branch" : ObjectId("5a24d20590d3d12155f3094e"),
}
Here is the query:
aggregate([
{$lookup:
{ from: "students",
localField: "_id",
foreignField: "branch",
as: "studentData"
}
}
])
Output of the query
[
{ _id: 5a24d20590d3d12155f3094e,
name: 'CSE',
hod: 'abc',
studentData: [ [Object], [Object], [Object], [Object], [Object] ]
},
{ _id: 5a24d20590d3d12155f3094f,
name: 'IT',
hod: 'xyz',
studentData: [ [Object], [Object], [Object], [Object] ] }
]
I have two questions
How to print complete student data.
How to get the count of studentData for each Department

To get complete student data you can do something like
db.student.aggregate(
[
{
$lookup:
{
from:"department",
localField:"branch",
foreignField:"_id",
as:"branch"
}
}
]
)
This will give you something like this:
{
"_id" : 1.0,
"rollNumber" : 110.0,
"name" : "Thor",
"branch" : [
{
"_id" : 1.0,
"name" : "CSE",
"hod" : "abc"
}
]
}
To get the count of studentData for each Department
db.getCollection('student').aggregate(
[
{
$lookup:
{
from:"department",
localField:"branch",
foreignField:"_id",
as:"branch"
}
},
{
$group:
{
_id:"$branch",
"numOfStudent":{$sum:1},
"listOfStudents":{$push:"$name"}
}
}
]
)
This will give you something like this:
{
"_id" : [
{
"_id" : 2.0,
"name" : "IT",
"hod" : "xyz"
}
],
"numOfStudent" : 1.0,
"listOfStudents" : [
"Ironman2"
]
}
{
"_id" : [
{
"_id" : 1.0,
"name" : "CSE",
"hod" : "abc"
}
],
"numOfStudent" : 3.0,
"listOfStudents" : [
"Thor",
"Ironman",
"Ironman3"
]
}
You can change $push:$name to $push:$_id If you want to store the Ids of students and not their names.
EDIT
Get similar result using "Departments" collection:
db.department.aggregate([
{
$lookup:
{
from:"student",
localField:"students",
foreignField:"_id",
as:"studentsDetails"
}
},
{
$project:{
_id:0,
name:"$name",
hod:"$hod",
numOfStudents:{$size:"$studentsDetails"},
students:"$studentsDetails"
}
}
])
This will give you something like this:
{
"name" : "CSE",
"hod" : "abc",
"numOfStudents" : 2,
"students" : [
{
"_id" : 1.0,
"rollNumber" : 110.0,
"name" : "Thor",
"branch" : 1.0
},
{
"_id" : 3.0,
"rollNumber" : 111.0,
"name" : "Ironman2",
"branch" : 2.0
}
]
}

Your $lookup is completely fine. To add a count of students to your output data, you can use $addFields.
Add this to your aggregation:
{ $addFields: {studentCount: {$size: "$studentData"}}}
}
So, your query should be something like this:
aggregate([
{$lookup:
{ from: "students",
localField: "_id",
foreignField: "branch", as: "studentData"
}
}, { $addFields: {studentCount: {$size: "$studentData"}}
}
])

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

Unable to aggregate two collections using lookup in MongoDB Atlas

I have an orders collection that looks like this:
{
"_id" : "wJNEiSYwBd5ozGtLX",
"orderId" : 52713,
"createdAt" : ISODate("2020-01-31T04:34:13.790Z"),
"status" : "closed",
"orders" : [
{
"_id" : "ziPzwLuZrz9MNkaRT",
"productId" : 10290,
"quantity" : 2
}
]
}
I have an products collection that looks like this
{
"_id" : "238cwwLkZa6gKNN86",
"productId" : 10290,
"title" : "Product Title",
"price" : 9.9
}
I am trying to merge the price information into the orders information.
Something like:
{
"_id" : "wJNEiSYwBd5ozGtLX",
"orderId" : 52713,
"createdAt" : ISODate("2020-01-31T04:34:13.790Z"),
"status" : "closed",
"orders" : [
{
"_id" : "ziPzwLuZrz9MNkaRT",
"productId" : 10290,
"quantity" : 2,
"price": 9.9
}
]
}
If I try a $lookup command on MongoDB Atlas Dashboard like this:
{
from: 'products',
localField: 'orders.productId',
foreignField: 'productId',
as: 'priceInfo'
}
The aggregated output is (not what I wanted):
{
"_id" : "wJNEiSYwBd5ozGtLX",
"orderId" : 52713,
"createdAt" : ISODate("2020-01-31T04:34:13.790Z"),
"status" : "closed",
"orders" : [
{
"_id" : "ziPzwLuZrz9MNkaRT",
"productId" : 10290,
}
],
"priceInfo": [
{
"_id" : "238cwwLkZa6gKNN86",
"productId" : 10290,
"title" : "Product Title",
"price" : 9.9
}
]
}
I do not need a separate priceInfo array. It will be best if I have the product details information merged into the "orders" array. What should be the aggregation lookup syntax to achieve the desired output?
Demo - https://mongoplayground.net/p/bLqcN7tauWU
Read - $lookup $unwind $first $set $push $group
db.orders.aggregate([
{ $unwind: "$orders" }, // break array of orders into individual documents
{
$lookup: { // join
"from": "products",
"localField": "orders.productId",
"foreignField": "productId",
"as": "products"
}
},
{
$set: {
"orders.price": { "$arrayElemAt": [ "$products.price", 0 ] } // set the price
}
},
{
$group: { // group records back
_id: "$_id",
createdAt: { $first: "$createdAt" },
status: { $first: "$status" },
orderId: { $first: "$orderId" },
orders: { $push: "$orders" }
}
}
])

MongoDb: find deeply nested object with $lookup

I have collection like this:
collection name is - account.
and it has sub-documents like account > buildings > gateways > devices.
{
"_id" : ObjectId("5e1fe45cd05bfb0cc549297d"),
"apiCallCount" : 0,
"email" : "info#data.com",
"password" : "dummy",
"userName" : "AAAAA",
"companyName" : "The AAAAAA",
"apiKey" : "5e1fe45cd05bfb0cc549297c",
"solutionType" : "VVVVVV",
"parentCompany" : "",
"buildings" : [
{
"_id" : ObjectId("5e1fe5e3d05bfb0cc5494146"),
"buildingName" : "xxxxxx",
"address" : "xxx",
"suite" : "101",
"floor" : "22",
"timeZone" : "us/eastern",
"gateways" : [
{
"_id" : ObjectId("5e1fe624d05bfb0cc549453a"),
"gatewayName" : "CC-GW-THF-001",
"gatewayKey" : "gk_5e1fe624d05bfb0cc549453a",
"suite" : "area1",
"devices" : [
{
"_id" : ObjectId("5e1fe751d05bfb0cc549578d"),
"serialNumber" : "129300000013",
"area" : "area1",
"connectionStatus" : 1,
"gatewayKey" : "gk_5e1fe624d05bfb0cc549453a",
"applicationNumber" : 30,
"firmwareVersion" : "1.0",
"needsAttention" : false,
"verificationCode" : "GAAS",
"createdAt" : ISODate("2020-01-16T04:32:17.899Z"),
"updatedAt" : ISODate("2020-01-16T08:53:54.460Z")
}
],
"createdAt" : ISODate("2020-01-16T04:27:16.678Z"),
"updatedAt" : ISODate("2020-01-16T08:53:54.460Z")
},
{
"_id" : ObjectId("5e1fe651d05bfb0cc54947f0"),
"gatewayName" : "AA-GW-THF-002",
"gatewayKey" : "gk_5e1fe651d05bfb0cc54947f0",
"suite" : "area2",
"devices" : [
{
"_id" : ObjectId("5e1fe7a9d05bfb0cc5495cdf"),
"serialNumber" : "129300000012",
"area" : "area2",
"connectionStatus" : 0,
"gatewayKey" : "gk_5e1fe651d05bfb0cc54947f0",
"applicationNumber" : 30,
"firmwareVersion" : "1.0",
"needsAttention" : false,
"verificationCode" : "VG3K",
"createdAt" : ISODate("2020-01-16T04:33:45.698Z"),
"updatedAt" : ISODate("2020-01-16T08:54:17.604Z")
}
],
"createdAt" : ISODate("2020-01-16T04:28:01.532Z"),
"updatedAt" : ISODate("2020-01-16T08:54:17.604Z")
},
],
"createdAt" : ISODate("2020-01-16T04:26:11.941Z"),
"updatedAt" : ISODate("2020-01-16T08:56:32.657Z")
}
],
"createdAt" : ISODate("2020-01-16T04:19:40.310Z"),
"updatedAt" : ISODate("2020-04-06T18:18:39.628Z"),
"__v" : 1,
}
I have accountId, buildingId, gatewayId, deviceId.
I am trying to find matched device object using $lookup operator.
I think I have to fist find the building object by using buildingId and then filter gateway under that building using gatewayId and then find device object using deviceId that I have.
I basically need to have access to device object fields to project in final output.
Having difficulty in coming up with correct pipleline for usingg lookup operator.
I have this so far:
db.getCollection('test').aggregate([
{
$lookup: {
from: 'account',
let: {
accountId: "$accountId"
},
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$_id", "$$accountId"]
}
}
},
],
as: "accountDetails"
}
}, {
$unwind: "$accountDetails"
}, {
$lookup: {
from: 'account',
let: {
accountId: "$accountId",
buildingId: "$buildingId",
buildings: "$accountDetails"
},
pipeline: [
{
"$match": {
"$expr": {
"$eq": ["$buildings._id", "$$buildingId"] // how to dig through nested document to get to devices ?
}
}
},
],
as: "buildingDetails"
}
}
{
$project: { ... ...
}
])
if I do this:
{
$lookup: {
from: 'account',
localField: "accountId",
foreignField: "_id",
as: "accountDetails"
}
},
accountDetails gives me access the account document based on accountId. but I need to get to buildings > gateways > devices and find the matching device.
UPDATE:
I forgot to mention, I am working with 2 collections here.
sensingresults and accounts.
Main purpose is to aggregate data from sensingresults, but also find deviceId from account collection and return with the result.
That's why lookup is needed to join 2 collections?
UPDATE2:
Current output:
{
"accountId": ObjectId("5e1fe45cd05bfb0cc549297d"),
"avgZoneCountNumber": 0,
"avgZoneCountNumberInstant": 0,
"buildingId": ObjectId("5e1fe5e3d05bfb0cc5494146"),
"companyName": "The AAAAAA",
"createdAt": ISODate("1970-01-01T00:00:00Z"),
"dateHour": "2020-03-19T18",
"deviceId": ObjectId("5e1fe81ed05bfb0cc5496406"),
"gatewayId": ObjectId("5e1fe6a6d05bfb0cc5494d25"),
"minuteBucket": 1
}
Expected result:
{
"accountId": ObjectId("5e1fe45cd05bfb0cc549297d"),
"avgZoneCountNumber": 0,
"avgZoneCountNumberInstant": 0,
"buildingId": ObjectId("5e1fe5e3d05bfb0cc5494146"),
"createdAt": ISODate("1970-01-01T00:00:00Z"),
"dateHour": "2020-03-19T18",
"deviceId": ObjectId("5e1fe81ed05bfb0cc5496406"),
"gatewayId": ObjectId("5e1fe6a6d05bfb0cc5494d25"),
"minuteBucket": 1,
"serialNumber: 1, // this value should come from device object
"area": 1 // this value should come from device object
}
You can find nested device using $filter, $arrayElemAt and $let:
device: {
$let: {
vars: {
building: {
$arrayElemAt: [ { $filter: { input: "$company_name.buildings", cond: { $eq: [ "$$this._id", "$buildingId" ] }} }, 0 ]
}
},
in: {
$let: {
vars: {
gateway: {
$arrayElemAt: [ { $filter: { input: "$$building.gateways", cond: { $eq: [ "$$this._id", "$gatewayId" ] }} }, 0 ]
}
},
in: { $arrayElemAt: [ { $filter: { input: "$$gateway.devices", cond: { $eq: [ "$$this._id", "$deviceId" ] }} }, 0 ] }
}
}
}
}
Full Solution

MongoDB - $match based on another field value

I want to get results like simple inner join from SQL, for get all accounts of the same id_customer.
I almost do this, but I get something like left join, and I don't now how to filer the result of query.
query:
db.customer.aggregate([
{
$match: {
"LOAN.AMOUNT": { $gte: 41000 }
}
},
{
$lookup: {
from: "department",
localField: "ID",
foreignField: "ACCOUNT.ID_CUSTOMER",
as: "customer"
}
},
{ $match: { "myArray": { $ne: [] } } },
{
$unwind: {
path: "$customer",
preserveNullAndEmptyArrays: false
}
}
,
{
$unwind: "$customer.ACCOUNT"
},
{
$match: {
"customer.ACCOUNT.ID_CUSTOMER": "$ID"
}
},
])
actual result:
empty
expected result:
{
"_id" : ObjectId("5dfccc28d29876c2988c7c05"),
"ID" : 4.0,
"SECOND_NAME" : "abc",
"FIRST_NAME" : "abc",
"ID_DISCOUNT" : {
"ID" : 3.0,
"TITLE" : "abc",
"AMOUNT" : 5.0
},
"LOAN" : {
"ID" : 4.0,
"AMOUNT" : 42750.0
},
"customer" : {
"_id" : ObjectId("5dfb7f8f5861584bbaedf718"),
"ID" : 1.0,
"TITLE" : "abc",
"ADDRESS" : "abc",
"CITY" : "abc",
"ACCOUNT" : {
"ID" : 10.0,
"ID_CUSTOMER" : 4.0,
"DATE_OPEN" : "2019-09-24 21:03:44"
}
}
}
{
"_id" : ObjectId("5dfccc28d29876c2988c7c05"),
"ID" : 4.0,
"SECOND_NAME" : "abc",
"FIRST_NAME" : "abc ",
"ID_DISCOUNT" : {
"ID" : 3.0,
"TITLE" : "abc",
"AMOUNT" : 5.0
},
"LOAN" : {
"ID" : 4.0,
"AMOUNT" : 42750.0
},
"customer" : {
"_id" : ObjectId("5dfb7f8f5861584bbaedf718"),
"ID" : 1.0,
"TITLE" : "abc",
"ADDRESS" : "abc",
"CITY" : "abc",
"ACCOUNT" : {
"ID" : 11.0,
"ID_CUSTOMER" : 4.0,
"DATE_OPEN" : "2019-08-23 21:03:44"
}
}
}
List of all account with the same id_customer, but
$match: {
"customer.ACCOUNT.ID_CUSTOMER": "$ID"
}
not doing what I want to do.
I found the solution: use $expr operator
db.customer.aggregate([
{
$match: {
"LOAN.AMOUNT": { $gte: 41000 }
}
},
{
$lookup: {
from: "department",
localField: "ID",
foreignField: "ACCOUNT.ID_CUSTOMER",
as: "customer"
}
},
{ $match: { "myArray": { $ne: [] } } },
{
$unwind: {
path: "$customer",
preserveNullAndEmptyArrays: false
}
}
,
{
$unwind: "$customer.ACCOUNT"
},
{ $match: { $expr: { $eq: ["$customer.ACCOUNT.ID_CUSTOMER", "$ID"] } } },
])

Combining array of objects according to field in MongoDb

I am trying to build an aggregation that will return the documents with the biggest number of objects in one of the fields, after combining the field.
Let's say that have this following documents that two of them contains the same id inside the movie field
{
"_id" : ObjectId("5b79bb0c15d2d0697885467c"),
"movie" : [
ObjectId("5b79b8387d467d5ab860544f")
],
"takenSeats" : [{"id" : 1},{"id" : 2},{"id" : 4},
],
"creteDate" : ISODate("2018-08-14T18:46:36.090Z"),
}
{
"_id" : ObjectId("5b79bb0c15d2d069788ef48d"),
"movie" : [
ObjectId("5b79b8387d467d5ab860544f")
],
"takenSeats" : [{"id" : 2},{"id" : 7},{"id" : 4},
],
"creteDate" : ISODate("2018-08-14T18:46:36.090Z"),
}
{
"_id" : ObjectId("5b79bb0c15d2d069788fg54hq"),
"movie" : [
ObjectId("5b79b8387d467d5ab8df54h43")
],
"takenSeats" : [{"id" : 6},{"id" : 2},{"id" : 5},
],
"creteDate" : ISODate("2018-08-14T18:46:36.090Z"),
}
As you can see, two of the documents contains the same id in the field movie.
What I trying to do is: to take those document that contains the same id in movie field and combine the takenSeats field
The wanted result should looks like
{
"_id" : ObjectId("5b79b8387d467d5ab860544f"),
"takenSeats" : [{"id" : 2},{"id" : 7},{"id" : 4},{"id" : 1},{"id" : 2},
{"id" : 4}
],
"creteDate" : ISODate("2018-08-14T18:46:36.090Z"),
}
{
"_id" : ObjectId("5b79b8387d467d5ab8df54h43"),
"takenSeats" : [{"id" : 6},{"id" : 2},{"id" : 5},
],
"creteDate" : ISODate("2018-08-14T18:46:36.090Z"),
}
In the last hours I tried to achieve it with different operators like $push and $addToSet. This is the query that I did that was the closest to the result that I want, but the issue that the documents that I was receiving in the result are with duplicated ids
db.orders.aggregate([
{$match:{ "created":{$gt: new Date(ISODate().getTime() - 1000*60*60*24*15)}}},
{ $lookup: { from: "shows", localField: "showId", foreignField: "_id", as: "acociatedShow" } },
{ "$project": { "acociatedShow": 1 } },
{ $unwind : "$acociatedShow" },
{ "$group": {"_id": { "movie": "$acociatedShow.movie"},
"takenSeats": { "$addToSet": "$acociatedShow.takenSeats"}}},
{ $unwind : "$takenSeats" },
{ $group : { _id : "$takenSeats", movieId : { $first: '$_id.movie' },len : { $sum : 1 } } },
{ $limit : 3 },
{ $lookup: { from: "movies", localField: "movieId", foreignField: "_id", as: "topMovie" } },
{ $unwind: "$topMovie" }, { $replaceRoot: { newRoot: "$topMovie" } }
])