MongoDB aggregate Lookup remove array [duplicate] - node.js

This question already has answers here:
convert a $lookup result to an object instead of array
(4 answers)
Closed 12 months ago.
I am currently using an aggregate lookup method from mongoose and it works:
const data = await this.messageModel.aggregate([
{
$match: { channel_id: channel_id },
},
{
$limit: limit ? limit : 1000,
},
]).lookup({
from: 'users',
localField: 'author',
foreignField: 'id',
as: 'author',
pipeline: [
{ $project: { _id: 0, password: 0, email: 0, __v: 0 } }
],
});
The data returns an array of object like this
{
"_id": "622861fe264eaed05a32bb2d",
"channel_id": "5850473746686541647",
"author": [
{
"username": "UnusualAbsurd",
"status": "Working...",
"createdAt": "2022-03-08T14:02:53.728Z",
"id": "1"
}
],
"id": "5850638795510120271",
"createdAt": "2022-03-09T08:14:54.228Z",
"attachments": [],
"content": "zazaza",
"__v": 0
}
Now the problem is, the author object that I used the lookup() method on returns as an array. How do I make it not return as an array?

You can use $unwind after lookup like this:
{$unwind: '$author'}

Related

Mongodb get element value from another collection based on its _id

I'm trying to get an element value based on a result _id in an aggregation.
This is the aggregation:
$project: {
_id: 0,
brand: "$_id",
"options": {
$mergeObjects: "$ram"
},
sum: {
$add: [
"$sm",
1
]
}
}
And I want brand to get the name from other collection named "brands" which looks like this
[
{
"_id": ObjectId("617b0dbacda6cbd1a0403f68"),
"SerialNumber": "45454234324",
"name": "hp"
},
{
"_id": ObjectId("617b0dbacda6cbd1a0403f69"),
"SerialNumber": "azazz5245454az",
"name": "asus"
}]
What I want to get is the name of brand using using its _id based on the result _id.
Using SQL its something like this:
Get brands.name where _id=brands._id
i managed to do it using $lookup
{
$lookup:
{
from: "brands",
localField: "brand",
foreignField: "_id",
as: "brand"
}
},
{
$set: {
brand: "$brand.brand"
}
},

Mongoose: Updation of parent-child data attribute with sum of nested object array through findOneAndUpdate query not working

I'm trying to update one of the nested array elements in a mongo collection using the Node mongoose lib. Here is how my mongo schema looks like:
{
"_id": ObjectId("5f8af2fc5f23667adf3bbaf2"),
"score": 2.5,
"questions": [{
"_id": ObjectId("5f8af30d5f23667adf3bbaf5"),
"score": 1.5,
},
{
"_id": ObjectId("5f8af3115f23667adf3bbaf8"),
"score": 1,
"options": [{
"_id": ObjectId("5f8af3115f23667adf3bbaf9"),
"score": 1,
"desc": "description 1"
},
{
"_id": ObjectId("5f8af3115f23667adf3bbafa"),
"score": 0,
"desc": "description 2"
}
]
}
]
}
I'm updating the score inside the question array, the score attribute at the root array to be updated which is the sum of the array score i.e.
root score => question array1.score + array2.score
question score => option array1.score + array2.score
I used below mongoose function as mentioned in this answer by #turivishal which is working fine to update the root score based onn question array:
https://stackoverflow.com/a/64401747/3159714
Point to note here that, some other attributes like desc in the option array also needs to be updated in the same call.
Note: that, $setoninsert is not an option as in this case, upsert is always false.
Is this at all possible to perform both of this updates using a single query?
Add one more $map for options array check condition if option _id match then update option object and merge with current object using $mergeObjects
let id = mongoose.Types.ObjectId("5f8a94ccc8452643f1498419");
let oid = mongoose.Types.ObjectId("5f8af3115f23667adf3bbafa");
let option = {
desc: "updated desc"
};
let qid = mongoose.Types.ObjectId("5f8a94e8c8452643f149841c");
let question = {
score: 1,
order: 1,
category: "TEXT",
options: {
$map: {
input: "$$this.options",
in: {
$mergeObjects: [
"$$this",
{ $cond: [{ $eq: ["$$this._id", oid] }, option, {}] }
]
}
}
}
};
Model.findOneAndUpdate(
{ _id: id },
[{
$set: {
questions: {
$map: {
input: "$questions",
in: {
$mergeObjects: [
"$$this",
{ $cond: [{ $eq: ["$$this._id", qid] }, question, {}] }
]
}
}
}
}
},
{
$set: {
score: {
$reduce: {
input: "$questions",
initialValue: 0,
in: { $add: ["$$value", "$$this.score"] }
}
}
}
}]
])
Playground

Mongodb - populate with limit on items and get total count of those items

I have a query looking like this:
const articles = await Article.find(query)
.populate({
path: 'votedUsers', // array of users id
select: 'title name username',
options: {
limit: 3,
sort: { createdAt: -1 },
},
})
.exec()
Result:
[
{
title: 'Article title',
votedUsers: [,,], // array of populated users with limit of 3
totalCountVoted: 200 // need to add this field
}
]
I want to find articles and populate votedUsers property but with limit to 3 users, but at the same time
I need to know how many ids were in votedUsers property.
For example it can be 200 users that voted on that article, but I just need to know the number and populate only 3 of them.
You can try the following aggregation using the match, lookup, project stages, and slice and size operators:
(Please note that the "users" value in lookup from must be the physical collection name.)
app.get("/article", async (req, res) => {
const data = await Article.aggregate([
{
$match: {
category: "Category1"
}
},
{
$lookup: {
from: "users",
localField: "votedUsers",
foreignField: "_id",
as: "users"
}
},
{
$project: {
title: 1,
votedUsers: { $slice: ["$users", 3] },
totalCountVoted: { $size: "$users" }
}
}
]);
res.send(data);
});
This will give you a result like this:
[
{
"_id": "5dded78f8f30c402b0fac309",
"title": "Article1",
"votedUsers": [
{
"_id": "5dded60a84523642bc27f511",
"__v": 0,
"name": "User1"
},
{
"_id": "5dded61384523642bc27f512",
"__v": 0,
"name": "User2"
},
{
"_id": "5dded61b84523642bc27f513",
"__v": 0,
"name": "User3"
}
],
"totalCountVoted": 8
},
{
"_id": "5dded7c18f30c402b0fac30a",
"title": "Article2",
"votedUsers": [
{
"_id": "5dded61b84523642bc27f513",
"__v": 0,
"name": "User3"
},
{
"_id": "5dded63c84523642bc27f514",
"__v": 0,
"name": "User4"
},
{
"_id": "5dded64484523642bc27f515",
"__v": 0,
"name": "User5"
}
],
"totalCountVoted": 8
}
]
Playground

findOne() returns entire document, instead of a single object

I'm trying to query this set of data using findOne():
{
"_id": {
"$oid": "5c1a4ba1482bf501ed20ae4b"
},
"wardrobe": {
"items": [
{
"type": "T-shirt",
"colour": "Gray",
"material": "Wool",
"brand": "Filson",
"_id": "5c1a4b7d482bf501ed20ae4a"
},
{
"type": "T-shirt",
"colour": "White",
"material": "Acrylic",
"brand": "H&M",
"_id": "5c1a4b7d482bf501ed20ae4a"
}
]
},
"tokens": [],
"email": "another#new.email",
"password": "$2a$10$quEXGjbEMX.3ERdjPabIIuMIKu3zngHDl26tgRcCiIDBItSnC5jda",
"createdAt": {
"$date": "2018-12-19T13:46:09.365Z"
},
"updatedAt": {
"$date": "2018-12-19T13:47:30.123Z"
},
"__v": 2
}
I want to return a single object from the items array using _Id as a filter. This is how I'm doing that:
exports.deleteItem = (req, res, next) => {
User.findOne({ 'wardrobe.items': { $elemMatch: { "_id": "5c1a4b7d482bf501ed20ae4a",} } }, (err, item) => {
console.log(item);
if (err) {
return console.log("error: " + err);
}
res.redirect('/wardrobe');
});
};
However, console.log(item) returns the whole document—like so:
{ wardrobe: { items: [ [Object], [Object] ] },
tokens: [],
_id: 5c1a4ba1482bf501ed20ae4b,
email: 'another#new.email',
password:
'$2a$10$quEXGjbEMX.3ERdjPabIIuMIKu3zngHDl26tgRcCiIDBItSnC5jda',
createdAt: 2018-12-19T13:46:09.365Z,
updatedAt: 2018-12-19T13:47:30.123Z,
__v: 2 }
I want to eventually use this to delete single items, so I need to filter to the single object from the subdocument.
Concerning your question:
MongoDB always returns the full object matching your query, unless you add a projection specifying which fields should be returned.
If you really want to only return a nested object, you could use the aggregation pipeline with the $replaceRoot operator like this:
User.aggregate([
// you can directly query for array fields instead of $elemMatching them
{ $match: { 'wardrobe.items._id': "5c1a4b7d482bf501ed20ae4a"}}},
// this "lifts" the fields wardrobe up and makes it the new root
{ $replaceRoot: {newRoot: '$wardrobe'}
// this "splits" the array into separate objects
{ $unwind: '$items'},
// this'll remove all unwanted elements
{ $match: { 'items._id': "5c1a4b7d482bf501ed20ae4a" },
},
])
This should return only the wanted items.
A note though: If you plan to remove elements from arrays anyways, I'd rather suggest you have a look at the $pull operation, which can remove an element from an array if it matches a certain condition:
https://docs.mongodb.com/manual/reference/operator/update/pull/
User.update(
{ 'wardrobe.items._id': "5c1a4b7d482bf501ed20ae4a"},
{ $pull: { 'wardrobe.items': {_id: "5c1a4b7d482bf501ed20ae4a"}},
{ multi: true }
)

$lookup with integer type fields? [duplicate]

This question already has answers here:
How do I perform the SQL Join equivalent in MongoDB?
(19 answers)
Closed 4 years ago.
I have a mongodb document as
{
"_id": 10001,
"uid": 1413430,
"loginType": "student"
}
the _id is bookId. This book Id is primary key in "books" collection which contains isbn number. The isbn number in "books" is primary key in "bookDetails" collection. I want bookName and author from the above document using join (aggregate in mongodb). The "books" and "bookDetails" collection are as follows :
"books"
{
"_id": 10001,
"issued": true,
"isbn": 1177009,
"issuedIds": []
}
"bookDetails"
{
"_id": 1177009,
"quantity": 5,
"available": 5,
"tags": [
"cse",
"ece",
"me",
"ce",
"ee",
"sems 1"
],
"bookIds": [
10001,
10002,
10003,
10004,
10005
],
"bookName": "book 1",
"author": "author 1"
}
I am working with nodejs and mongodb.
Thanks all.
I got the answer.Please tell me if something is wrong because I got the required output.
database
.collection('issueCart')
.aggregate([
{
$match: {uid: parseInt(id)}
},
{
$lookup: {
from: "bookDetails",
localField: "_id",
foreignField: "bookIds",
as: "book"
},
},
{
$unwind: "$book"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: ["$book", "$$ROOT"]
}
}
},
{
$project: {"bookId": "$_id", author: "$author", name: "$bookName", _id: 0}
}
])

Resources