mongodb aggregate on array of objects - node.js

I'm trying to group my participants array in to such a way that for a single participant I should get all the meeting under that participant in an array.
[
{
"_id": "5fc73e7131e6a20f6c492178",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
{
"_id": "5fc74e1254b8d337b4ae36d2",
"rsvp": "Not Answered",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 1",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
},
{
"_id": "5fc73f1cdfc45d3ca0c84654",
"participants": [
{
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
}
],
"title": "Meeting 2",
"startTime": "2020-11-01T18:30:00.000Z",
"endTime": "2020-11-03T18:30:00.000Z"
}
]
my expected result should be like
[{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 1",
"email": "participant1#gmail.com"
},
meetings: [{meeting1, meeting2, ...and so on}]
},
{
"participant": {
"_id": "5fc74bc5e7c54d0ea8f133ca",
"rsvp": "Yes",
"name": "Participant 2",
"email": "participant2#gmail.com"
},
meetings: [{meeting2, meeting3, ...and so on}]
}
]
I'm kinda stuck for hours to figure it out. I tried the approach by using $group and $unwind but I was getting participants as an array consisting of a single participant(object). and on that I was unable to run $match to match according to the participant's email because participants field was an array.
I tried this
const docs = await Meeting.aggregate([
{ $unwind: '$participants' },
{
$lookup: {
from: 'participants',
localField: 'participants',
foreignField: '_id',
as: 'participants'
}
},
{ $match },
{ $group: { _id: "$participants", meetings: { $push: "$$ROOT" } } },
]);
but this is not matching the expected result which I want it to be.

You can unwind to deconstruct the array and use group to get your desired output
db.collection.aggregate([
{
"$unwind": "$participants"
},
{
$group: {
_id: "$participants._id",
participants: {
$first: "$participants"
},
meetings: {
"$addToSet": "$title"
}
}
}
])
Working Mongo playground

Related

How to use $match after $lookup and $unwind so that it can check all the fields including the array for a keyword/string?

I have collections named
products
`
{
"_id": {
"$oid": "1"
},
"companyId": [
{
"$oid": "2"
}
],
"Title": "abcd",
"Caption": "abc",
},{
"_id": {
"$oid": "2"
},
"companyId": [
{
"$oid": "3"
}
],
"Title": "milk",
"Caption": "aa",
}
`
companies
`
{
"_id": {
"$oid": "2"
},
"name": "cathub",
"url": "cathub.com",
"__v": 0
},
"_id": {
"$oid": "3"
},
"name": "Amule",
"url": "amule.com",
"__v": 0
`
here the products collection have companyId as foreign key of _id from companies collection.What i need is that when i search for a perticular string in products,it needed to search all fields including companies which is joined.for example if my keyword is "Amule",then it needed to search in title and caption and companies.name also.if it found matching then we need to return the products document of _id:2
I tried with the following
{ $lookup:{ form:"companies", localField:"companyId", foriegnField:"_id", as :"result" } }
then
{ $unwind:{ path:"$result" } }
but i am not able to perform $match after that.Because it shows error and only allow to use $match
only in the begining.Please help to solve this issue(i need to solve this issue using TEXT index)
Complete query
model.aggregate([
{
$match: {
$text: {
$search: "Amul",
},
},
},
{
$lookup: {
from: "companies",
localField: "companyId",
foreignField: "_id",
as: "company",
},
},
{
$unwind: "$company",
},
{
$match: {
$text:{
$search: "Amul"}
},
},
},
$group: {
_id: {
_id: "$_id",
Title: "$Title",
},
comapany: {
$push: "$company",
},
},
]}
if the string 'Amul'present in any field of "products" then return the document or 'Amul' is present in the "name" field of joined "company" joined using $lookup then also return the parent document
note:-
'model' is the products collection

How to make a nested relationship on mongodb?

For example i have 4 collections. There are:
"company" collection
{
"id_company": "C01"
"company_name": "Sidomuncul"
"like": [
"123",
"121"
]
}
"user" collection
{
"id_user": "123",
"name": "Astra",
"major": "111",
"language": [{
"id_language": "101",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "112",
"language": [{
"id_language": "102",
"level": "Intermediate"
}]
}
"major" collection
{
"id_major": "111",
"name": "IT"
},
{
"id_major": "112",
"name": "Designer"
}
"language" collection
{
"id_language": "101",
"name": "English"
},
{
"id_language": "102",
"name": "Chinese"
}
And when i make a route for get who are like company by id_company "C01", i want show the result relation id_user in "like" field with user collection. Example result:
{
"id_company": C01",
"like": [
{
"id_user": "123",
"name": "Astra",
"major": "IT",
"language": [{
"id_language": "English",
"level": "Expert"
}]
},
{
"id_user": "121",
"name": "Bibi",
"id_major": "Designer",
"language": [{
"id_language": "Chinese",
"level": "Intermediate"
}]
}
] //Close Like Field
}
Thanks before.
SOLVED
Use lookup and group also push
Using Mongo aggregate pipeline you can collect required result in multiple stages. For mongo aggregate query you can use $lookup, $set, $unwind and $group stages. Try this query, you might get required result set:
db.company.aggregate([
{
"$unwind": "$like"
},
{
$lookup: {
from: "user",
localField: "like",
foreignField: "id_user",
as: "like"
}
},
{
"$unwind": "$like"
},
{
$lookup: {
from: "major",
localField: "like.major",
foreignField: "id_major",
as: "major"
}
},
{
"$unwind": "$major"
},
{
$set: {
"like.major": "$major.name"
}
},
{
$lookup: {
from: "language",
localField: "like.language.id_language",
foreignField: "id_language",
as: "lang"
}
},
{
"$unwind": "$lang"
},
{
$set: {
"like.language.id_language": "$lang.name"
}
},
{
$group: {
_id: "id_company",
company_name: {
"$first": "$company_name"
},
like: {
$push: {
id_user: "$like.id_user",
major: "$like.major",
name: "$like.name",
language: "$like.language"
}
}
}
}
])

Nodejs mongoose : how to retreive data from db collection related to other collection

I want to implement a function that would process students, courses and instructors data (loaded from MongoDB collections) in order to produce information about students enrolled on courses (and store it in another MongoDB collection). the function reports each student’s:
primary key
their name
the number of courses they are enrolled in
I want to Store the output of this routine in a MongoDB collection called coursereport like this :
[
{
"_id": "jeff",
"value": {
"name": "Jeff Holland",
"numbercourses": 2
}
},
{
"_id": "john.shore",
"value": {
"name": "John Shore",
"numbercourses": 2
}
},
{
"_id": "lee2331",
"value": {
"name": "Lee Aldwell",
"numbercourses": 1
}
},
{
"_id": "rcotter",
"value": {
"name": "Ray Cotter",
"numbercourses": 2
}
},
{
"_id": "scott",
"value": {
"name": "Scott Mills",
"numbercourses": 3
}
}
]
my data :
{
"students": [
{
"_id": "john.shore",
"name": {"first": "John", "last": "Shore"},
"email": "john.shore#gmail.com",
"major": "Electrical Engineering"
},
{
"_id": "jeff",
"name": {"first": "Jeff", "last": "Holland"},
"email": "jeff#yahoo.com",
"major": "Business"
},
{
"_id": "scott",
"name": {"first": "Scott", "last": "Mills"},
"email": "scott#hotmail.com",
"major": "Humanities/Art"
},
{
"_id": "rcotter",
"name": {"first": "Ray", "last": "Cotter"},
"email": "rcotter#msn.com",
"major": "Computer Science"
},
{
"_id": "lee2331",
"name": {"first": "Lee", "last": "Aldwell"},
"email": "lee2331#aol.com",
"major": "Graphic Design"
}
],
"courses": [
{
"_id": "HIST-1010",
"name": "History of the World 1010",
"description": "A bunch of really interesting things that actually happened",
"students": ["scott", "john.shore"],
"ratings": [3, 5, 4, 5, 4, 4, 2, 4]
},
{
"_id": "ENGCOMP-1010",
"name": "English Composition 1010",
"description": "If you can't write well, you've got nothing!",
"students": ["scott", "lee2331", "rcotter", "john.shore", "jeff"],
"ratings": [4, 4, 5, 4, 5, 1, 5]
},
{
"_id": "ART-1050",
"name": "Artistic Interpretation 1050",
"description": "Discover your inner beholder",
"students": ["rcotter", "scott", "jeff"],
"ratings": [3, 4, 3, 4, 4, 3, 4, 4]
}
],
"instructors": [
{
"_id": "wally.r.binns",
"name": {"first": "Wally", "middle": "r", "last": "Binns"},
"email": "wally.r.binns#ssu.edu",
"bio": "I was born in the middle of my mother's doctoral dissertation on Faraday Cage isolation. I've been an academic ever since...",
"publications": [{
"title": "Inverted Celestial Poetry",
"source": "http://www.pubcentral.com/poetry/inverted-celestial-poetry"
}],
"courses": ["ENGLIT-2500"]
},
{
"_id": "gerald.waterford.iii",
"name": {"first": "Gerald", "last": "Waterford", "suffix": "III"},
"email": "gerald.waterford.iii#ssu.edu",
"bio": "My father's father was a great man. My father, not so much. I am restoring the family honor.",
"publications": [{
"title": "Grow, grow, little Dandelion",
"source": "http://www.hopefulstories.com/my-dandelion"
}, {"title": "The teapot and the spoon", "source": "http://www.dishsoap.com/teapot-spoon"}],
"courses": ["ENGCOMP-1010", "HIST-1010"]
},
{
"_id": "kim.b",
"name": {"prefix": "Mrs.", "first": "Kim", "last": "Binnley"},
"email": "kim.b#ssu.edu",
"bio": "My mother told me 'Don't let those dopes push you around'. My life has been a constant struggle against dopeness ever since. Sigh...",
"publications": [],
"courses": ["ART-1050"]
}
]
}
I started with that :
async function produceReport(db, callback) {
const students = db.collection('students');
const courses = db.collection('courses');
const instructors = db.collection('instructors');
// implement missing code that will create 'coursereport' mongo collection with student courses data
callback();
}
module.exports = produceReport;
can any one give a help please
You have to use $lookup
Defined as:
To perform an equality match between a field from the input documents with a field from the documents of the “joined” collection,
So you have to join students and courses and later the output value with instructors..
Take a look at this query:
db.students.aggregate([
{
$lookup: {
from: "courses",
localField: "item",
foreignField: "email",
as: "students_courses"
}
},
{
"$lookup": {
"from": "instructors",
"localField": "students_courses._id",
"foreignField": "courses",
"as": "students_courses"
}
},
{
"$set": {
"_id": "$_id",
"value": {
"name": {
"$concat": [
"$name.first",
" ",
"$name.last"
]
},
"numbercourses": {
"$size": "$students_courses"
}
}
}
},
{
"$project": {
"_id": 1,
"value": 1
}
}
])
First stage is a joing with two collections with the fields indicated and soterd into a field called students_courses.
Then, another join with the _ids from the students_courses.
With the data then you can create a field named value with the name and count the number of courses in other field.
And $project to show only fields you want.
Check an example here
db.students.aggregate([
{
$lookup: {
from: "courses",
localField: "_id",
foreignField: "students",
as: "students_courses"
}
},
{
$project: {
_id: 1,
value: {
name: {
$concat: [
"$name.first",
" ",
"$name.last"
]
},
numbercourses: {
"$size": "$students_courses"
}
}
}
}
])

How to query from two collections in mongodb using aggregate?

I have the two following collections in my mongoDB:
PRODUCTS:
[
{
"_id": "5ebb0984e95e3e9e35aab3bf",
"name": "Product 1",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum"
},
{
"_id": "5ebdb9f3d943ae000a4a9714",
"name": "Product 2",
"brand": "DEF",
"code": "7891910000190",
"description": "Lorem Ipsum"
}
]
STOCK GOODS:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed058da75e4e01013f5779d",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04cede9e43cee79734b93",
"productId": "5ebb0984e95e3e9e35aab3bf"
},
{
"_id": "5ed059483da3bafccb34cec3",
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"branchId": "5ed049a8f9a892a5fd9b4d9e",
"inventoryId": "5ed04d01e9e43cee79734b95",
"productId": "5ebb0984e95e3e9e35aab3bf"
}
]
I want to get the follow result:
[
{
"_id": "5ed0586b7f2cfe02387d0706",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
},
{
"_id": "5ed059483da3bafccb34cec3",
"data": {
"_id": "5ebb0984e95e3e9e35aab3bf",
"brand": "ABC",
"code": "7891910000190",
"description": "Lorem Ipsum",
"name": "Product 1",
}
}
]
My NodeJS and MongoDB aggregate look like this:
const mongoose = require("mongoose");
const StockGoods = mongoose.model("StockGoods");
ObjectId = require("mongodb").ObjectID;
exports.getProducts = async (companyId, branchId, inventoryId) => {
const response = await StockGoods.aggregate([
{
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
},
{
$lookup: {
from: "products",
localField: "productId",
foreignField: "_id",
as: "inventory_docs",
},
},
{
$project: {
data: "$inventory_docs",
},
},
{ $unwind: "$data" },
]);
return response;
};
This is the same database and the aggregate function described above, you can check this: https://mongoplayground.net/p/FRgAIfO2bwh.
But, this function aggregate does not working when I use nodejs.
My API returns nothing (empty array).
Whats wrong?
Issue here is the way you have stored data in collection.... StockGoods collection has companyId, inventoryId and branchIdas a string but aggregation is looking for ObjectId aee below match criteria:
$match: {
companyId: new ObjectId("5ed0491bf9a892a5fd9b4d9b"), // new ObjectId(companyId)
inventoryId: new ObjectId("5ed04d01e9e43cee79734b95"), // new
ObjectId(inventoryId)
branchId: new ObjectId("5ed049a8f9a892a5fd9b4d9e"), // new ObjectId(branchId)
},
Based on the data you store in DB either you need to update match with string.
So below Aggregation will work for you..
[
{
"$match": {
"companyId": "5ed0491bf9a892a5fd9b4d9b",
"inventoryId": "5ed04d01e9e43cee79734b95",
"branchId": "5ed049a8f9a892a5fd9b4d9e"
}
},
{
"$lookup": {
"from": "products",
"localField": "productId",
"foreignField": "_id",
"as": "inventory_docs"
}
},
{
"$project": {
"data": "$inventory_docs"
}
},
{
"$unwind": "$data"
}
]
This works you can test in Compass Aggregator tab
[RESOLVED]
The problem was in the scheme:
companyId: {
type: String,
required: true,
},
The correct is:
companyId: {
type: Schema.Types.ObjectId,
required: true,
},

MongoDB $lookup on one document's array of object

I have searched online but could not find any match my case. Here is the situation.
I am using aggregate to combine one collection and one document which is from another collection together
restaurants.aggregate([
{
$match: {
_id: {
$in: idList
}
}
},
{
$lookup: {
from: "tags",
localField: "details.restaurantType",
foreignField: "details.restaurantType._id",
as: "types"
}
},
{
$project: {
restaurantName: "$details.restaurantName",
restaurantType: "$details.restaurantType",
type: {
$filter: {
input: "$types",
as: "type",
cond: {
$eq: ["$$type._id", "$details.restaurantType"]
}
}
},
currency: "$details.currency",
costPerPax: "$details.costPerPax"
}
}
]);
current result
The 'type' field in my current result is [], I need a matched value instead
[
{
"id": "5c20c7a0036dda80a8baabcc",
"restaurantName": "Villagio Restaurant Sutera Mall",
"type": [],
"currency": "RM",
"costPerPax": 22,
},
{
"id": "5c20ceb07715216d3c217b7a",
"restaurantName": "Thai Food Thai Now Sutera Mall",
"type": [],
"currency": "RM",
"costPerPax": 16,
}
]
expected result
I need the 'type' fields has match tag name from another collection like this
[
{
"id": "5c20c7a0036dda80a8baabcc",
"restaurantName": "Villagio Restaurant Sutera Mall",
"type": "Western",
"currency": "RM",
"costPerPax": 22,
},
{
"id": "5c20ceb07715216d3c217b7a",
"restaurantName": "Thai Food Thai Now Sutera Mall",
"type": "Thai",
"currency": "RM",
"costPerPax": 16,
}
]
Extra Information
two document from restaurants collection
{
"details": {
"restaurantName": "Villagio Restaurant Sutera Mall",
"restaurantType": "5c01fb57497a896d50f498a8"
},
"_id": "5c20c7a0036dda80a8baabcc",
"status": "OP",
"__v": 0
},
{
"details": {
"restaurantName": "Kingshahi Japanese Shop",
"restaurantType": "5c01fb57497a896d50f49879"
},
"_id": "5c20cb4fb7e75180480690c2",
"status": "OP",
"__v": 0
}
One document from tag collection
{
"_id": "5c01fb57497a896d50f49876",
"details": {
"restaurantTypeId": "5c01fb57497a896d50f49877",
"restaurantTypes": [
{
"_id": "5c01fb57497a896d50f49879",
"name": "Asian",
"counter": 1
},
{
"_id": "5c01fb57497a896d50f4987a",
"name": "Bakery",
"counter": 0
}
]
}
}
You can use below optimised aggregation pipeline
db.restaurants.aggregate([
{ "$lookup": {
"from": "tags",
"let": { "restaurantType": "$details.restaurantType" },
"pipeline": [
{ "$match": {
"$expr": { "$in": ["$$restaurantType", "$details.restaurantTypes._id"] }
}},
{ "$unwind": "$details.restaurantTypes" },
{ "$match": {
"$expr": { "$eq": ["$details.restaurantTypes._id", "$$restaurantType"] }
}}
],
"as": "types"
}},
{ "$project": {
"restaurantName": "$details.restaurantName",
"restaurantType": "$details.restaurantType",
"type": { "$arrayElemAt": ["$types.details.restaurantTypes.name", 0] },
"currency": "$details.currency",
"costPerPax": "$details.costPerPax"
}}
])

Resources