Pardeep
Pardeep

Reputation: 2283

get count of conditionally matched elements from an array in MongoDB

I want comments with today's date and it should be non-empty and how much comments it has via using mongoose. I have tried a lot. Currently, I am trying to achieve with two methods. both have some problems let me explain. please consider I have only two posts in DB one has no comments like: [], and the other has 2 comments two inside it with today date and the 3 is old.

Method 1 :

in this method, it returns me today comment but it only returns single comment added on today. and also returning me another object which has no comments

 Post.find({ })
    .select({
      comments: { $elemMatch: { date: { $gt: startOfToday } }  },
      title: 1,
    })
    .exec((err, doc) => {
      if (err) return res.status(400).send(err);

      res.send(doc);
    });

the output of above code is :

[{"_id":"5e9c67f0dd8479634ca255b1","title":"sdasd","comments":[]},{"_id":"5e9d90b4a7008d7bf0c4c96a","title":"sdsd","comments":[{"date":"2020-04-21T04:04:11.058Z","votes":
[{"user":"hhhh","vote":1}],"_id":"5e9e70bbece9c31b33f55041","author":"hhhh","body":"xvxgdggd"}]}]

Method 2 : In this method I am using the same thing above inside the found object like this:

 Post.find({ comments: { $elemMatch: { date: { $gt: startOfToday } } } })
  .exec((err, doc) => {
    if (err) return res.status(400).send(err);

    res.send(doc);
  });

And it returns me first post with all comments (3 comments) but not second post(that is good) that have empty comment array.

here is the output :

[{"author":{"id":"5e85b42f5e4cb472beedbebb","nickname":"hhhh"},"hidden":false,"_id":"5e9d90b4a7008d7bf0c4c96a","title":"sdsd","body":"dsfdsfdsf","votes":[{"user":"5e85b42f5e4cb472beedbebb","vote":1}],"comments":[{"date":"2020-04-20T12:08:32.585Z","votes":[],"_id":"5e9d90c0a7008d7bf0c4c96b","author":"hhhh","body":"zcxzczxc z zxc"},
{"date":"2020-04-21T04:04:11.058Z","votes":[{"user":"hhhh","vote":1}],"_id":"5e9e70bbece9c31b33f55041","author":"hhhh","body":"xvxgdggd"},
{"date":"2020-04-21T04:56:25.992Z","votes":[],"_id":"5e9e7cf96095882e11dc510c","author":"hhhh","body":"new should appear in feeds"}],"date":"2020-04-20T12:08:20.687Z","createdAt":"2020-04-20T12:08:20.692Z","updatedAt":"2020-04-21T04:56:26.003Z","__v":3}]

This is my post schema :

  const postSchema = new Schema(
  {
    title: {
      type: String,
      required: true,
      unique: 1,
      index: true,
    },
    author: {
      id: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "User",
      },
      nickname: String,
    },
    body: {
      type: String,
      required: true,
    },
    comments: [
      {
        author: {
          type: String,
          required: true,
        },
        body: {
          type: String,
          required: true,
        },
        date: { type: Date, default: Date.now },
        votes: [{ user: String, vote: Number, _id: false }],
      },
    ],
    date: { type: Date, default: Date.now },
    hidden: {
      type: Boolean,
      default: false,
    },
    votes: [{ user: Schema.Types.ObjectId, vote: Number, _id: false }],
  },
  { timestamps: true }
);

So, if I have SUM up the things I need today comments and today is 21st April (Two comments) and another comment date is 20. I only need today's comments with its count.

If I forgot something to add please let me know. Thanks

Upvotes: 2

Views: 211

Answers (1)

whoami - fakeFaceTrueSoul
whoami - fakeFaceTrueSoul

Reputation: 17915

There are couple of changes as $elemMatch would return only the first matching element from array but not all the matching elements in comments array. So it's not useful here, additionally if you want comments for today you need to use $gte instead of $gt for input startOfToday. Finally, You need to use aggregation-pipeline to do this :

db.collection.aggregate([
   /** Lessen the no.of docs for further stages by filter with condition */
  {
    $match: { "comments.date": { $gte: ISODate("2020-04-21T00:00:00.000Z") } }
  },
  /** Re-create `comments` array by using filter operator with condition to retain only matched elements */
  {
    $addFields: {
      comments: {
        $filter: {
          input: "$comments",
          cond: { $gte: ["$$this.date", ISODate("2020-04-21T00:00:00.000Z")] }
        }
      }
    }
  },
  {
    $addFields: { count: { $size: "$comments" } } // Add count field which is size of newly created `comments` array(Which has only matched elements)
  }
]);

Test : mongoplayground

Upvotes: 1

Related Questions