How to match each array field to other field in Mongodb - mongodb

I have an array as below:
const test = [{
"_id": 1,
"name": "apple",
"car": "ford"
},{
"_id": 2,
"name": "melon",
"car": "ferrari"
},{
"_id": 3,
"name": "perl",
"car": "Renaut"
}]
And there is are documents of Mongodb as below:
[{
"name": "perl", "company": "A"
},{
"name": "melon", "company": "B"
},{
"name": "apple", "company": "C"
},{
"name": "apple", "company": "D"
},{
"name": "perl", "company": "E"
},{
"name": "apple", "company": "F"
}]
And I want to get this result using mongodb aggregate:
[{
"name": "perl", "company": "A", testInform: { "_id": 3, "name": "perl", "car": "Renaut"}
},{
"name": "melon", "company": "B", testInform: { "_id": 2, "name": "melon", "car": "ferrari"}
},{
"name": "apple", "company": "C", testInform: { "_id": 1, "name": "apple", "car": "ford"}
},{
"name": "apple", "company": "D", testInform: { "_id": 1, "name": "apple", "car": "ford"}
},{
"name": "perl", "company": "E", testInform: { "_id": 3, "name": "perl", "car": "Renaut"}
},{
"name": "apple", "company": "F", testInform: { "_id": 1, "name": "apple", "car": "ford"}
}]
I think to use aggregate with $match and $facet, etc., but I don't know exactly how to do this. Could you recommend a solution for this?
Thank you so much for reading this.

$lookup with pipeline keyword
db.demo2.aggregate(
{
$lookup:
{
from: "demo1",
let: { recordName: "$name"},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$$recordName", "$name" ] },
]
}
}
},
],
as: "testInform"
}
}
)

If the test array data is stored in a collection then acheiving O/P is pretty straightforward $lookup with $project aggregation
$arrayElemAt Why? because the lookup would fetch the joined documents in an array as testInform
db.maindocs.aggregate([
{
$lookup: {
from: "testdocs",
localField: "name",
foreignField: "name",
as: "testInform"
}
},
{
$project: {
_id: 0,
name: 1,
company: 1,
testInform: { $arrayElemAt: ["$testInform", 0] }
}
}
]);
Update based on comments:
The idea is to iterate the cursor from the documents stored in mongodb Array.prototype.find() the object from test which matches the name field, add it to result.
const test = [
{
_id: 1,
name: "apple",
car: "ford"
},
{
_id: 2,
name: "melon",
car: "ferrari"
},
{
_id: 3,
name: "perl",
car: "Renaut"
}
];
const cursor = db.collection("maindocs").find();
const result = [];
while (await cursor.hasNext()) {
const doc = await cursor.next();
const found = test.find(e => e.name === doc.name);
if (found) {
doc["testInform"] = found;
}
result.push(doc);
}
console.info("RESULT::", result);

The aggregation has one stage: Iterate over the test array and get the array element as an object which matches the name field in both the document and the array (using the $reduce operator).
const test = [ { ... }, ... ]
db.test_coll.aggregate( [
{
$addFields: {
testInform: {
$reduce: {
input: test,
initialValue: { },
in: {
$cond: [ { $eq: [ "$$this.name", "$name" ] },
{ $mergeObjects: [ "$$this", "$$value" ] },
"$$value"
]
}
}
}
}
}
] )

Related

How to replace array of object containing ids with the data using MongoDB aggregation

I am having a collection which contains the data like the following and want to have the desirable output which I have mentioned below.
db={
collectionA: [
{
"id": ObjectId("63b7c24c06ebe7a8fd11777b"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781")
]
},
{
"productIndex": 2,
"productCategory": ObjectId("63b7c24c06ebe7a8fd11777b"),
"productOwners": [
ObjectId("63b7c2fd06ebe7a8fd117781"),
ObjectId("63b7c12706ebe7a8fd117778")
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
],
collectionB: [
{
"_id": ObjectId("63b7c2fd06ebe7a8fd117781"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63b7c12706ebe7a8fd117778"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
},
]
}
Desirable Output = [
{
"id": ObjectId("507f1f77bcf86cd799439011"),
"uniqueRefId": "UUID-2023-0001",
"products": [
{
"productIndex": 1,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
}
]
},
{
"productIndex": 2,
"productCategory": ObjectId('614g2f77bff86cd755439021'),
"productOwners": [
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41acd"),
"fullname": "Jim Corbett",
"email": "jim.corbett#pp.com"
},
{
"_id": ObjectId("63ac1e59c0afb8b6f2d41ace"),
"fullname": "Carry Minatti",
"email": "carry.minatty#pp.com"
}
]
},
{
"productIndex": 3,
"productCategory": "",
"productOwners": ""
}
]
}
]
In the collectionA we are having other documents as well, its not just one document.
Similarly for collectionB we are having other documents too.
How we can get this desirable output?
I am expecting the mongodb query for getting this solution.
I have implemented the lookup like the following
db.collectionA.aggregate([
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "inventory_docs"
}
}
])
You can try this:
db.collectionA.aggregate([
{
"$unwind": "$products"
},
{
"$lookup": {
"from": "collectionB",
"localField": "products.productOwners",
"foreignField": "_id",
"as": "products.productOwners"
}
},
{
"$group": {
"_id": {
id: "$id",
uniqueRefId: "$uniqueRefId"
},
"products": {
"$push": "$products"
}
}
},
{
"$project": {
id: "$_id.id",
uniqueRefId: "$_id.uniqueRefId",
products: 1,
_id: 0
}
}
])
Playground link.
In this query, we do the following:
First we unwind the products array, using $unwind.
Then we calculate productOwners, using $lookup.
Then we group the unwinded elements, using $group.
Finally we, project the desired output using $project.

Mongodb: Push element in multiple nested array with condition

How can I push value into multiple nested array with specific conditions? I have a document like this
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"name": "John",
"grades": []
},
{
"_id": "1b",
"name": "Katie",
"grades": []
},
{
"_id": "1c",
"name": "Andy",
"grades": []
},
]
}
]
Query to insert into nested array. (Not sure what is missing here)
db.collection.update({
"_id": "class_a",
"students": {
$elemMatch: {
"_id": {
"$in": [
"1a",
"1b"
]
}
}
}
},
{
$push: {
"students.$.grades": "A+"
}
})
Got the following result. But I was expecting both John and Katie have A+ in grades
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"grades": ["A+"],
"name": "John"
},
{
"_id": "1b",
"grades": [],
"name": "Katie"
},
{
"_id": "1c",
"grades": [],
"name": "Andy"
}
]
}
]
Expected result
[
{
"_id": "class_a",
"students": [
{
"_id": "1a",
"grades": ["A+"],
"name": "John"
},
{
"_id": "1b",
"grades": ["A+"],
"name": "Katie"
},
{
"_id": "1c",
"grades": [],
"name": "Andy"
}
]
}
]
Mongo playground to test the code
You can use $[<identifier>] to update only the items that match a condition. Your first {} is to find the relevant documents, while the arrayFilters is to find the relevant items inside the document nested array:
db.collection.update(
{_id: "class_a", students: {$elemMatch: {_id: {$in: ["1a", "1b"]}}}},
{$push: {"students.$[item].grades": "A+"}},
{arrayFilters: [{"item._id": {$in: ["1a", "1b"]}}], upsert: true}
)
See how it works on the playground example
You should really use arrayFilters for these otherwse it'll only match the first entity. You don't need to use $elemMatch at all.
Playground - https://mongoplayground.net/p/_7y89KB83Ho
db.collection.update({
"_id": "class_a"
},
{
$push: {
"students.$[students].grades": "A+"
}
},
{
"arrayFilters": [
{
"students._id": {
"$in": [
"1a",
"1b"
]
}
}
]
})

How to project nested array elements with custom field name in MongoDB

I have a users collection in MongoDB with this sample row:
[
{
"_id": 1,
"name": "John Doe",
"comments": [
{
"_id": 100,
"content": "Comment 100",
"post": "1000"
},
{
"_id": 101,
"content": "Comment 101",
"post": "1000"
}
]
}
]
I want to project users and comments data after converting _id fields into id. So I used the following query:
db.users.aggregate([
{
$project: {
_id: 0,
id: '$_id',
name:1
comments: {
_id: 0,
id: '$_id',
content: 1,
},
}
}
])
Now the users _id field is successfully converted. But the comments id field is equal to _id of users collection instead of comments.
[
{
"id": 1,
"name": "john Doe",
"comments": [
{
"id": 1,
"content": "comment 100"
},
{
"id": 1,
"content": "comment 101"
},
]
}
]
How can I achieve the right result.
you should use $map in project like this
https://mongoplayground.net/p/H4pueuejYdf
db.collection.aggregate([
{
"$project": {
_id: 0,
id: "$_id",
name: 1,
comments: {
"$map": {
"input": "$comments",
"as": "c",
"in": {
id: "$$c._id",
content: "$$c.content",
post: "$$c.post"
}
}
}
}
}
])

MongoDB aggregate merging fields

I have a mongo Database I'll like to "join" two of them and then merge some other fields:
Let's see the schemas:
Students Schema (and data):
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
}
and Subject schema:
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter",
}
What I'll like to make a query with the subjects (all info) of a student, also if the student have additional fields in subject like passed add it to the subject subdocument.
Here is my query till now:
db.students.aggregate([
{
$match:
{
_id : ObjectId('5fbd564981b1313de790b580')
}
},
{
$lookup :
{
from : "subjects",
localField : "subjects._id",
foreignField : "_id",
as : "FoundSubject"
}
}
]);
which correctly make the "join" but the merge is still missing, I got as result:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
}
],
"__v": NumberInt("1"),
"FoundSubject": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math"
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
]
}
but I'll like to have:
{
"_id": ObjectId("5fbd564981b1313de790b580"),
"name": "John Doe",
"age": "21",
"image": "https://XXXX/481.png",
"subjects": [
{
"_id": ObjectId("5fbd4e6881b1313de790b56b"),
"course": 3,
"teacher": "John Smith",
"name": "Math",
"passed": true,
},
{
"_id": ObjectId("5fcb63fa8814d96876c687bf"),
"name": "IT",
"course": 8,
"teacher": "John Peter"
}
],
"__v": NumberInt("1"),
}
with merged data and field "passed" added. How can accomplish that?
I'm new to MongoDB coming from MySQL.
Thanks
You need to merge both objects, add below stage after $lookup,
MongoDB Version From 3.4
$map to iterate loop of students array
$reduce to iterate loop of FoundSubject array, check condition if condition match then return required fields otherwise return initial value
$project to remove FoundSubject from result
{
$addFields: {
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$reduce: {
input: "$FoundSubject",
initialValue: {},
in: {
$cond: [
{ $eq: ["$$s._id", "$$this._id"] },
{
_id: "$$this._id",
course: "$$this.course",
name: "$$this.name",
teacher: "$$this.teacher",
passed: "$$s.passed"
},
"$$value"
]
}
}
}
}
}
}
},
{ $project: { FoundSubject: 0 } }
Playground
MongoDB Version From 4.4
$map to iterate loop of students array,
$filter to get matching document from FoundSubject array and $first to get first object from array returned by filter
$mergeObjects to merge current objects with found result object from filter
remove FoundSubject using $$REMOVE
// skipping your stages
{
$addFields: {
FoundSubject: "$$REMOVE",
subjects: {
$map: {
input: "$subjects",
as: "s",
in: {
$mergeObjects: [
"$$s",
{
$first: {
$filter: {
input: "$FoundSubject",
cond: { $eq: ["$$s._id", "$$this._id"] }
}
}
}
]
}
}
}
}
}
Playground

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here