MongoDB aggregate conditional - hide object based on conditional - javascript

From the database:
_id: "123123123123123"
question: "Question1"
answer: "some answer"
by: "user1"
__v: 0
There is a conditional I am trying to implement here. It does not work. If by is empty, then do not display _id: "$question",
let answers = await Answer.aggregate([
{ $match: { $or: [{ by: user.email }, { by: user2[0].email }] } },
{
$cond: {
if: {
$eq: ["$by", ""],
},
then: 0,
else: 1,
},
},
{
$group: {
_id: "$question",
data: {
$push: "$$ROOT",
},
},
}
]);
Outcome (wrong):
{
"_id": "Question1",
"data": [
{
"answer": "some answer",
"by": "user1"
},
{
"answer": "some answer",
"by": "user2"
}
]
},
{
"_id": "Question2",
"data": [
{
"answer": "some answer",
"by": "user1",
}
]
},
Expected outcome:
I want to display only Question1 because user2 does not have an answer (there is no user2 by). So display Question only if there are 2 by.
{
"_id": "Question1",
"data": [
{
"answer": "some answer",
"by": "user1"
},
{
"answer": "some answer",
"by": "user2"
}
]
},

You have to remove $orcondition and use $all in
{ $match: { $or: [{ by: user.email }, { by: user2[0].email }] } },
changed to
{ $match: { $all: [{ by: user.email }, { by: user2.email }] } },

Related

Aggregate framework returns empty array instead of single object (MongoDB/Mongoose)

I am building an api for a kanban task management app. I have this data stored in the database.
{
"_id": "62fa5aa25778ec97bc6ee231",
"user": "62f0eb5ebebd0f236abcaf9d",
"name": "Marketing Plan",
"columns": [
{
"name": "todo",
"_id": "62fa5aa25778ec97bc6ee233",
"tasks": [
{
"title": "Task Four testing 2",
"description": "This is task four",
"subtasks": [
{
"name": "wash dshes test",
"completed": false,
"_id": "62ff74bfe80b11ade2d34456"
},
{
"name": "do homework",
"completed": false,
"_id": "62ff74bfe80b11ade2d34457"
}
],
"_id": "62ff74bfe80b11ade2d34455"
}
]
},
{
"name": "doing",
"_id": "62fa5aa25778ec97bc6ee234",
"tasks": []
},
{
"name": "done",
"_id": "62fa5aa25778ec97bc6ee235",
"tasks": []
}
],
"__v":0
}
I tried to return a single object with the id of req.params.id which in this case is 62ff74bfe80b11ade2d34455 however, it returns an empty array instead of returning a single object.
const getTask = asyncHandler(async (req, res) => {
const task = await Board.aggregate([
{
$match: {
"columns.tasks._id": req.params.id,
},
},
{
$project: {
columns: {
$first: {
$filter: {
input: "$columns.tasks",
cond: {
$eq: ["$$this._id", req.params.id],
},
},
},
},
},
},
{
$replaceRoot: {
newRoot: "$columns",
},
},
]);
});
As #yung-shun suggested, I needed to cast req.params.id as an Object ID.
const task = await Board.aggregate([
{
$match: {
"columns.tasks._id": new ObjectId(req.params.id),
},
},
{ $unwind: "$columns" },
{
$match: {
"columns.tasks._id": new ObjectId(req.params.id),
},
},
{
$project: {
task: {
$first: {
$filter: {
input: "$columns.tasks",
cond: { $eq: ["$$this._id", new ObjectId(req.params.id)] },
},
},
},
},
},
{ $replaceWith: "$task" },
]);
res.status(200).json(task);

How do I get comments count while fetching posts

I have two collections Posts an comments. I am storing comments with postID. I want to show comments count field when fetching all the posts data.
How do I achieve this?
// posts
{
postID: '123',
title: 'abc'
}
// comments
{
postID: '123',
comments: [
{
commentID: 'comment123',
comment: 'my Comment'
}
]
}
// Looking for this
{
postID: '123',
title: 'abc',
commentCount: 1
}
Here's one way you could do it.
db.posts.aggregate([
{
"$lookup": {
"from": "comments",
"localField": "postID",
"foreignField": "postID",
"pipeline": [
{
"$project": {
"_id": 0,
"commentCount": {"$size": "$comments"}
}
}
],
"as": "commentCount"
}
},
{
"$project": {
"_id": 0,
"postID": 1,
"title": 1,
"commentCount": {"$first": "$commentCount.commentCount"}
}
}
])
Try it on mongoplayground.net.
Try This.
pipeline = [{
"$lookup": {
"from": "comments",
"let": {
"postId": "$postId",
},
"pipeline": [
{
"$match": {
"$expr": {
"$eq": ["$postId", "$$postId"]
},
}
},
{
"$group": {
"_id": "$postId",
"comments_count": {"$sum": 1}
}
}
],
"as": "comments"
}
},
{
"$project": {
"_id": 0,
"postId": 1,
"title":1,
"comments_count": "$comments.comments_count"
}
}]
db.posts.aggregate(pipeline)

How to update nested object in array (Mongoose/MongoDB)

I've been struggling to get my around how to update a object in a nested array with a particular id. I've attempted to implement $set as shown below. I want to be able to update the task with an _id of 62ff74bfe80b11ade2d34455 with the data from the request body.
{
"_id": "62fa5aa25778ec97bc6ee231",
"user": "62f0eb5ebebd0f236abcaf9d",
"name": "Marketing Plan",
"columns": [
{
"name": "todo",
"_id": "62fa5aa25778ec97bc6ee233",
"tasks": [
{ ====> here
"title": "Task Four",
"description": "This is task four",
"subtasks": [
{
"name": "wash dshes",
"completed": false,
"_id": "62ff74bfe80b11ade2d34456"
},
{
"name": "do homework",
"completed": false,
"_id": "62ff74bfe80b11ade2d34457"
}
],
"_id": "62ff74bfe80b11ade2d34455"
}
]
},
{
"name": "doing",
"_id": "62fa5aa25778ec97bc6ee234",
"tasks": []
},
{
"name": "done",
"_id": "62fa5aa25778ec97bc6ee235",
"tasks": []
}
],
"__v": 0
}
const updatedTask = await Board.findOneAndUpdate(
{
"columns.tasks._id": req.params.id,
},
{ $set: { "columns.$.tasks": req.body } },
{ new: true }
);
You can use the positional operator in combination with an arrayfilter. Here's an example how you'd update a specific field of the relevant task:
db.collection.update({
"columns.tasks._id": req.params.id
},
{
"$set": {
"columns.$[].tasks.$[t].title": "it works"
}
},
{
"arrayFilters": [
{
"t._id": req.params.id
}
]
})
You can also try this on mongoplayground.
If you're looking for a way to replace the matching task object itself you can do:
db.collection.update({
"columns.tasks._id": req.params.id
},
{
"$set": {
"columns.$[].tasks.$[t]": req.body
}
},
{
"arrayFilters": [
{
"t._id": req.params.id
}
]
})

Converting string to Array (Aggregate mongodb)

Im getting random data from my database using the aggregate but apparently some of them have their genre values in string not in array, How can i convert those random data genre from string to array?
const book = await Book.aggregate([
/* This is just for testing which books has genres in specific type
{
$match: { genre: { $type: "string" } }
},
*/
{
$sample: { size: 6 }
}
]);
sample data result(genres that are in type of string)
[
{
"_id": "62710ac63ad1bfc6d17030fe",
"title": "Birth of a Theorem",
"author": "Villani, Cedric",
"genre": "mathematics",
"publisher": "Bodley Head",
"dateOfPublication": "2002-02-28T00:00:00.000Z",
"noOfCopies": 16,
"type": "Book",
"form": "Non-fiction",
"isbn": "979-81202-479229-867673-6",
"dateAdded": "2002-11-28T00:00:00.000Z"
},
{
"_id": "62710ac63ad1bfc6d1703108",
"title": "All the President's Men",
"author": "Woodward, Bob",
"genre": "history",
"publisher": "Random House",
"dateOfPublication": "2018-02-19T00:00:00.000Z",
"noOfCopies": 56,
"type": "Book",
"form": "Non-fiction",
"isbn": "978-41428-6606587-937631-",
"dateAdded": "2011-02-23T00:00:00.000Z"
},
]
sample data result(genres that are in type of array)
[
{
"_id": "62710ac63ad1bfc6d17030be",
"title": "Superfreakonomics",
"author": "Dubner, Stephen",
"genre": [
"economics",
"computer_science"
],
"publisher": "HarperCollins",
"dateOfPublication": "2003-10-06T00:00:00.000Z",
"noOfCopies": 31,
"type": "Thesis",
"form": "Non-fiction",
"isbn": "978-35029-7186192-465859-7",
"dateAdded": "2009-02-12T00:00:00.000Z"
}
]
$sample is for documents instead of embedded array.
Update
db.collection.update({
genre: {
$type: "string"
}
},
[
{
$set: { genre: [ "$genre" ] }
}
],
{
multi: true
})
mongoplayground
Aggregate
db.collection.aggregate([
{
$match: {
genre: {
$type: "string"
}
}
},
{
$set: {
genre: {
$cond: {
if: { $eq: [ { $type: "$genre" }, "string" ] },
then: [ "$genre" ],
else: "$genre"
}
}
}
}
])
mongoplayground

Aggregate array occourances

I have a set of documents (posts) which have an array of users mentioned in each post.
{
"title": "Some post title",
[ ... ]
"mentions": ["johnsmith", "johndoe", "paul"]
}
I want to aggregate a list of unique mentions, and the number of times they've been mentioned across all posts. For example:
[{ user: "johnsmith", count: 5 }, { user: "benlewis", count: 9 }, { user: "johndoe", count: 1 }]
With Mongo, I'd do something like:
"mentions": [{
"$unwind": "$mentions"
}, {
"$group": {
"_id": "$mentions",
"count": { "$sum": 1 }
}
}]
What's the equivalent in Elasticsearch?
You can use a Terms aggregation for that. A small (5.x) example:
PUT test
{
"mappings": {
"test" : {
"properties": {
"title": {
"type": "text"
},
"mentions": {
"type": "keyword"
}
}
}
}
}
POST test/test/1
{
"title": "Some post title",
"mentions": [
"johnsmith",
"johndoe",
"paul"
]
}
POST test/test/2
{
"title": "Some post title 2",
"mentions": [
"johnsmith"
]
}
GET test/_search
{
"size": 0,
"aggs": {
"test": {
"terms": {
"field": "mentions",
"size": 10
}
}
}
}
Gives the following response:
"aggregations": {
"test": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "johnsmith",
"doc_count": 2
},
{
"key": "johndoe",
"doc_count": 1
},
{
"key": "paul",
"doc_count": 1
}
]
}
}
}
Hope this helps :)

Categories