MongoDB aggregate with foreign model inside array - arrays

I am trying to aggregate data with a foreign model.
The structure I am trying to supercharge is the following:
{
"_id" : ObjectId("62b489664cbb9bc8c947f19f"),
"user_id" : ObjectId("61a775da4cbb9bc8c947edd9"),
"product_types" : [
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
},
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ed"),
}
]
}
I am trying to add product data from product_id, and I think I am pretty close to it, but I am adding 2 identical products in an array instead of the correct one:
Query:
db.getCollection('interests').aggregate([
{
$lookup:{
from: "products",
localField: "product_types.product_id",
foreignField: "_id",
as: "productInterestData"
}
},
{
$set: {
"product_types.product": {
$map: {
input: "$product_types",
in: {
$mergeObjects: [
"$this",
{
$arrayElemAt: [
"$productInterestData",
{$indexOfArray: ["$productInterestData.id", "$this.id"]}
]
}
]
}
}
}
}
},
{$unset: "productInterestData"}
])
Result (with an array of 2 identical products, instead of the correct one):
{
"_id" : ObjectId("62b489664cbb9bc8c947f19f"),
"user_id" : ObjectId("61a775da4cbb9bc8c947edd9"),
"product_types" : [
{
"type" : NumberLong(0),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"product" : [
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
},
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
}
]
},
{
"type" : NumberLong(1),
"product_id" : ObjectId("62b4890f4cbb9bc8c947e5ed"),
"product" : [
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
},
{
"_id" : ObjectId("62b4890f4cbb9bc8c947e5ef"),
"name" : "olive",
}
]
}
]
}
Any idea on how to fix the query to have only one product instead of an array of identical ones?

Few small adjustments on the $set phase:
product_types, not product_types.product, in order to avoid duplication of the array. In order to nest it anther product add the key product in the $mergeObjects operation.
$productInterestData._id instead of $productInterestData.id
$$this instead of $this (we need two $ here)
$$this.product_id instead of $this.id
db.interests.aggregate([
{
$lookup: {
from: "products",
localField: "product_types.product_id",
foreignField: "_id",
as: "productInterestData"
}
},
{
$set: {
product_types: {
$map: {
input: "$product_types",
in: {
$mergeObjects: [
"$$this",
{product:{
$arrayElemAt: [
"$productInterestData",
{$indexOfArray: ["$productInterestData._id", "$$this.product_id"]}
]
}}
]
}
}
}
}
},
{$unset: "productInterestData"}
])
See how it works on the playground example

Related

Update nested array in a mongo document

I have below collection
{
"_id" : ObjectId("5d7e6c54c23c210001108556"),
“parentId" : "5d0162ba69cf130001a16115",
"tasks" : [
{
"_id" : "ae60a8f1",
"taskMetaDataIds” : [
ObjectId("5d55a648e2f7320001e578ac")
],
"name" : “meta 3"
},
{
"_id" : "07544d96",
"taskMetaDataIds" : [
ObjectId("5d55a648e2f732676676676”),
ObjectId("5d55a648e2612333556888”)
],
"name" : “meta 2"
},
],
"name" : “New Topic",
"createdBy" : "01526151-8303-450f-b08b-b36a1760b774",
"createdDate" : ISODate("2019-09-15T16:52:36.150+0000"),
"updatedDate" : ISODate("2019-09-15T16:52:36.150+0000")
}
I am looking for below output . Is there an operator which can directly convert the array of objects into array of strings as shown below. I can do it with a script by looping over the taskMetaDataIds array but I am looking to use a direct mongo operator which suits my purpose.
{
"_id" : ObjectId("5d7e6c54c23c210001108556"),
“parentId" : "5d0162ba69cf130001a16115",
"tasks" : [
{
"_id" : "ae60a8f1",
"taskMetaDataIds” : [
"5d55a648e2f7320001e578ac"
],
"name" : “meta 3"
},
{
"_id" : "07544d96",
"taskMetaDataIds" : [
"5d55a648e2f732676676676”,
“5d55a648e2612333556888”
],
"name" : “meta 2"
},
],
"name" : “New Topic",
"createdBy" : "01526151-8303-450f-b08b-b36a1760b774",
"createdDate" : ISODate("2019-09-15T16:52:36.150+0000"),
"updatedDate" : ISODate("2019-09-15T16:52:36.150+0000")
}
I tried below but it does not seem to be working-
db.getCollection("data").updateOne({"_id":ObjectId("5d7e6c54c23c210001108556")}, [{ $set: { "tasks.taskMetaDataIds": "$tasks.taskMetaDataIds.str" } }])
$set - Set tasks array field.
1.1. $map - Iterate each element in the tasks array and return a new array.
1.1.1. $mergeObjects - Merge the current iterated task object and taskMetaDataIds array (from the result 1.1.1.1).
1.1.1.1. $map - Iterate the current iterated task object's taskMetaDataIds array and returns a new array.
1.1.1.1.1. $toString - Convert each iterate id from ObjectId type to string type.
db.getCollection("data").updateOne({
"_id": ObjectId("5d7e6c54c23c210001108556")
},
[
{
$set: {
"tasks": {
$map: {
input: "$tasks",
as: "task",
in: {
$mergeObjects: [
"$$task",
{
"taskMetaDataIds": {
$map: {
input: "$$task.taskMetaDataIds",
in: {
$toString: "$$this"
}
}
}
}
]
}
}
}
}
}
])
Demo # Mongo Playground

How do I create a MongoDB aggregate to lookup and add fields using ObjectIds in array objects

Using Mongo 4.4
I'm looking to to lookups across collections and add a human readable value from the target collection to the source collection using a aggregate.
This works fine for individual values, but for some lookups the ObjectIds are in objects in arrays, and I can't get that work. I can pull all the values back, but not place the individual values in the array objects.
In this test case, I have a library database with a books collection and a subscribers collection. The subscribers have a checkouts entry with is an array of objects, containing a reference to a book, and the checkout date. I want to add the book title to each object in the array.
Test Database:
books collection:
[
{
"_id" : ObjectId("63208c9f0d97eff0cfbefde6"),
"title" : "There and back again",
"author" : "Bilbo Baggins",
"publisher" : "Middle Earth Books"
},
{
"_id" : ObjectId("63208cd10d97eff0cfbeff02"),
"title" : "Two Towers",
"author" : "JRR Tolkin",
"publisher" : "Dude Books"
},
{
"_id" : ObjectId("63208cf10d97eff0cfbeffa3"),
"title" : "Dune",
"author" : "Frank Herbert",
"publisher" : "Classic Books"
},
{
"_id" : ObjectId("63208d1d0d97eff0cfbf0087"),
"title" : "Old Man's War",
"author" : "John Scalzi",
"publisher" : "Old Man Books"
}
]
subscribers collection:
[
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z")
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z")
}
],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c4e0d97eff0cfbefc1f"),
"name" : "Bob",
"checkouts" : [],
"address" : "123 Somewhere"
},
{
"_id" : ObjectId("63208c640d97eff0cfbefc9a"),
"name" : "Mary",
"checkouts" : [],
"address" : "123 Somewhere Else"
}
Desired Output for user Tom:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : "Two Towers"
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : "Old Man's War"
}
],
"address" : "123 Somewhere",
}
Using this aggregate:
db.getCollection('subscribers').aggregate([
{$match: {_id: ObjectId("63208c2e0d97eff0cfbefb46") } },
{$lookup: {from: "books", localField: "checkouts.bookId", foreignField: "_id", as: "book_tmp_field" }},
{$addFields: { "checkouts.title": "$book_tmp_field.title"}},
{$project: { book_tmp_field: 0}}
])
This is the closest I can get:
{
"_id" : ObjectId("63208c2e0d97eff0cfbefb46"),
"name" : "Tom",
"checkouts" : [
{
"bookId" : ObjectId("63208cd10d97eff0cfbeff02"),
"checkoutDate" : ISODate("2022-01-01T21:21:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
},
{
"bookId" : ObjectId("63208d1d0d97eff0cfbf0087"),
"checkoutDate" : ISODate("2022-01-02T21:22:20.202Z"),
"title" : [
"Two Towers",
"Old Man's War"
]
}
],
"address" : "123 Somewhere"
}
Before performing the lookup, you should UNWIND the checkouts array. After all the processing is done, group the documents, to obtain the checkouts in the array. Finally, project your desired output document. Like this:
db.subscribers.aggregate([
{
$match: {
_id: ObjectId("63208c2e0d97eff0cfbefb46")
}
},
{
"$unwind": "$checkouts"
},
{
$lookup: {
from: "books",
localField: "checkouts.bookId",
foreignField: "_id",
as: "book_tmp_field"
}
},
{
$addFields: {
"checkouts.title": "$book_tmp_field.title"
}
},
{
$project: {
book_tmp_field: 0
}
},
{
"$group": {
"_id": {
_id: "$_id",
address: "$address",
name: "$name"
},
"checkouts": {
"$push": "$checkouts"
}
}
},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [
"$_id",
{
checkouts: "$checkouts"
}
]
}
}
}
])
Here's the playground link.

Update value of key in Object in nested array of objects in MongoDB

I am trying to update data of "array1.array2._id": ObjectId("627a6fab60dc3c523b396af1") and Set Name to John But it's updating in all array2's first element's name to John.
db.getCollection('tests')
.updateOne({ "array1.array2._id": ObjectId("627a6fab60dc3c523b396af1") },{ $set: { "array1.$[].array2.$.name" : "John" } })
{
"_id" : ObjectId("627a6fab60dc3c523b396aec"),
"array1" : [
{
"array2" : [
{
"_id" : ObjectId("627a6fab60dc3c523b396af1"),
"name" : "test"
},
{
"_id" : ObjectId("627a6fab60dc3c523b396af2"),
"name" : "ABC"
}
],
"_id" : ObjectId("627a6fab60dc3c523b396aed")
},
{
"array2" : [
{
"_id" : ObjectId("627a6fab60dc3c523b396af3"),
"name" : "XYZ"
},
{
"_id" : ObjectId("627a6fab60dc3c523b396af4"),
"name" : "Testing"
}
],
"_id" : ObjectId("627a6fab60dc3c523b396aee")
}
]
}
Based on this great answer by #R2D2, you can do:
db.collection.update({
"array1.array2._id": ObjectId("627a6fab60dc3c523b396af1")
},
{
$set: {
"array1.$[].array2.$[y].name": "John"
}
},
{
arrayFilters: [
{
"y._id": ObjectId("627a6fab60dc3c523b396af1")
}
]
})
As you can see on this playground example

Get the Patients Info from two Collections in MongoDB 3.6

Here are the two collections named Patients and Hospitals, I need to get the PatientId's data which is in Hospital Collections stored under patientsId's in string data format, and _id is stored as ObjectId below are the details:
**Hospital Collection :**
{
"_id" : ObjectId("5c04b943ff491824b806686a"),
"email" : "ayoub.khial#gmail.com",
"password" : "$2a$10$4Wt5Rn6udxREdXCIt3hGb.sKhKUKOlyiYKmLTjYG3SqEPKFSw9phq",
"PatientDetails" : {
"WeekJoined" : "Monday",
"description" : "I",
"PatientIds" : [
"5a0c6797fd3eb67969316ce2",
"5c07ada8ff49183284e509d1",
"5c07acc1ff49183284e509d0"
]
} }
**Patient Collection :**
{
"_id" : ObjectId("5a0c6797fd3eb67969316ce2"),
"picture" : "http://placehold.it/150x150",
"name" : "Genmom",
"email" : "leilaware#genmom.com",
"city" : "Rabat",
"location" : {
"type" : "Point",
"coordinates" : [
-6.79387,
33.83957
]
}
}
View Code :
{
"$lookup" : {
"from" : "Patient",
"localField" : "Hospital.PatientsDetails.PatientIds",
"foreignField" : "_id",
"as" : "PatientDetails"
}
}
How can i handle data type conversion in Lookup in MongoDB 3.6
local field should be PatientDetails.PatientIds
If your PatientIds are as ObjectIds i.e
"PatientIds" : [
ObjectId("5a0c6797fd3eb67969316ce2"),
ObjectId("5c07ada8ff49183284e509d1"),
ObjectId("5c07acc1ff49183284e509d0")
];
try this
>>>db.hospitals.aggregate([
{
$lookup: {
from: "patients",
localField: "PatientDetails.PatientIds",
foreignField: "_id",
as: "PatientDetails"
}
}
]);
If your PatientIds are Strings try this
>>>db.hospitals.aggregate([
{
$lookup: {
from: "patients",
let: { patientIds: "$PatientDetails.PatientIds" },
pipeline: [
{
$match: { $expr: { $in: [{ $toString: "$_id" }, "$$patientIds"] } }
}
],
as: "PatientDetails"
}
}
]);

Aggregating MongoDB, displaying top results from two separate collections

I have am trying to perform an aggregate function on my collection but I can't seem to fit the right query for the job.
My goal is to display the top 2 fastest laps on all maps and show the associated user first name and last name.
Here is my stats collections:
{
"_id" : ObjectId("5c86674d87e8cd468c850c86"),
"lapTime" : "1:32:29",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:49:01.472Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c8667ec87e8cd468c850c87"),
"lapTime" : "2:32:34",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:51:40.895Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c86674x87e8Sd567c120c86"),
"lapTime" : "1:12:29",
"map" : "France",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T10:49:01.472Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c8667f887e8cd468c850c88"),
"lapTime" : "1:88:29",
"map" : "Italy",
"driver" : [
ObjectId("5c7c499b555fa13f50c9c248")
],
"date" : ISODate("2019-03-11T13:51:52.727Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c866970c65910291c6f2000"),
"lapTime" : "1:34:29",
"map" : "Italy",
"driver" : [
ObjectId("5c80f78ca0ecdf26c83dfc8a")
],
"date" : ISODate("2019-03-11T13:58:08.135Z"),
"__v" : 0
}
{
"_id" : ObjectId("5c868532b5c50c17b0917f9e"),
"lapTime" : "1:43:33",
"map" : "Italy",
"driver" : [
ObjectId("5c80f78ca0ecdf26c83dfc8a")
],
"date" : ISODate("2019-03-11T15:56:34.869Z"),
"__v" : 0
}
Since I am passing the driver ID by reference here:
"driver":[ObjectId("5c7c499b555fa13f50c9c248")] , I want to display the driver's attributes from my users collection.
Here is one of my user objects:
{
"_id" : ObjectId("5c7c499b555fa13f50c9c248"),
"password" : "$2a$10$L..Pf44/R7yJfNPdikIObe04aiJaY/e94VSKlFscjgYOe49Y7iwJK",
"email" : "john.smith#yahoo.com",
"firstName" : "John",
"lastName" : "Smith",
"laps" : [],
"__v" : 0,
}
Here is what I tried so far:
db.getCollection('stats').aggregate([
{ $group: {
_id: { map: "$map" }, // replace `name` here twice
laps: { $addToSet: "$lapTime" },
driver:{$addToSet: "$driver"},
count: { $sum: 1 }
} },
{$lookup:
{
from: "users",
localField: "firstName",
foreignField: "lastName",
as: "driver"
}},
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
As a result, I am getting drivers as a empty array.
What I am actually trying to achieve is something like this:
{
"_id" : {
"map" : "France"
},
"laps" : [
"Jonathan Smith":"2:32:34",
"Someone Else":"1:32:29"
],
"count" : 2.0
}
I think this should work:-
db.getCollection('stats').aggregate([
{ $unwind: "$driver" },
{$lookup:
{
from: "users",
localField: "driver",
foreignField: "_id",
as: "driver"
}},
{ $group: {
_id: { map: "$map" }, // replace `name` here twice
laps: { $addToSet:
{
lapTime: "$lapTime",
driverName: "$driver.firstName" + "$driver.lastName"
}
},
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);

Resources