How to count specific sub documents on other sub documents in mongoose? - node.js

I have a schema like this struct:
{
"user" : "admin",
"exercises" : [
{
"name" : "exercise 1",
"documents" : [
{
"idDoc" : "1",
"name" : "doc1"
},
{
"idDoc" : "2",
"name" : "doc1"
},
{
"idDoc" : "3",
"name" : "doc2"
}
]
}
]
}
How can I count all documents that have name = doc1?
The output is:
{
"_id" : "objectId", --userId
"count" : 2
}
I know this below code can count all document but I don't have any idea to count them with conditions.
Schema.aggregate()
.match({ user: username })
.project({ size: {
$reduce : {
"input" : "$exercises",
"initialValue" : 0,
"in" : {"$add":["$$value",{"$size":"$$this.documents"}]}
}
}});
Please help! Thank you for reading.

You're going the right way by using $reduce, just need some changes in data processing. You can use $reduce with $filter to create an array that has all documents with name doc1 then use $size to count items in that array. Example:
db.collection.aggregate([
{ $match: { user: "admin" } },
{
$addFields: {
count: {
$size: {
$reduce: {
input: "$exercises",
initialValue: [],
in: {
$concatArrays: [
"$$value",
{
$filter: {
input: "$$this.documents",
as: "item",
cond: { $eq: ["$$item.name", "doc1"] }
}
}
]
}
}
}
}
}
},
])
MongoPlayground

Related

How can i do elemMatch inside array using mongodb?

I have below user details in my bookings collection
{
"_id" : ObjectId("609a382b589346973c84c6fe"),
"Name" : "abc",
"UserId":1
"Status" : "Pending",
"BookingData" : {
"Date" : ISODate("2021-04-30T04:00:00.000Z"),
"info" : [],
"BookingDataMethod" : "avf",
"Message" : null,
"products" : [
{
"_id" : ObjectId("60a4e92775e5de3570578820"),
"ProductName" : "Test1",
"ProductID" : ObjectId("60a4e92475e5de357057880a"),
"IsDeliveryFailed" : "Yes"
},
{
"_id" : ObjectId("60a4e92775e5de357057881f"),
"ProductName" : "Test2",
"ProductID" : ObjectId("60a4e92475e5de357057880d")
}
],
}
}
I have prepared a query for the below conditions and when I run the below query I should get the "UserId":1 documents but I got 0 records
condition 1: products should not be null
condition 2: ProductID should exist in the products array and should not be null
condition 3: IsDeliveryFailed should not be "Yes"
Based on the above user only one product got delivery failed(IsDeliveryFailed": "Yes") so when I run this query it should return "UserId":1 document. if both products "IsDeliveryFailed": "Yes" then
we should not get this user
Query
db.getCollection('bookings').find({
"$and": [
{ "BookingData.products": { $ne: [] } },
{ "BookingData.products": {"$elemMatch":{ "ProductID": { "$exists": true ,$ne: null } }} },
{ "BookingData.products": {"$elemMatch":{ "IsDeliveryFailed": { $ne: 'Yes' } }} }
]
})
Could someone please tell me the issue on the above query or please help me to prepare a query for the above condition?
I think you can do it with aggregations
db.collection.aggregate([
{
$match: {
"BookingData.products": { "$exists": true }
}
},
{
$set: {
"BookingData.products": {
"$filter": {
"input": "$BookingData.products",
"cond": {
$and: [
{ $ne: [ "$$this.ProductID", undefined ] },
{ $ne: [ "$$this._id", null ] },
{ $ne: [ "$$this.IsDeliveryFailed", "Yes" ] }
]
}
}
}
}
},
{
$match: {
$expr: {
$ne: [ "$BookingData.products", [] ]
}
}
}
])
Working Mongo playground

how to use $filter after $map immediately in mongoose

When use js code,i can use functional expression one by one;For example:
array.map(***).filter(...)
can i use filter after map like above in mongoose?
My question is like this.I have an dataset like below:
{
"_id" : ObjectId("5e3bd328f3dec754e1b8e17d"),
"userId" : "5e33ee0b4a3895a6d246f3ee",
"userName" : "jackiewillen",
"hasReviewedTimes" : 4,
"notes" : [
{
"time" : ISODate("2020-02-23T10:12:19.190Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b1"),
"memoryTime" : ISODate("2020-02-23T10:42:19.190Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51df83966daeae41e7f5b0"),
"memoryTime" : ISODate("2020-02-23T22:12:19.190Z")
}
]
},
{
"time" : ISODate("2020-02-23T10:45:26.615Z"),
"memoryLine" : [
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bd"),
"memoryTime" : ISODate("2020-02-23T11:15:26.615Z")
},
{
"hasReviewed" : false,
"_id" : ObjectId("5e51e746966daeae41e7f5bc"),
"memoryTime" : ISODate("2020-02-23T22:45:26.615Z")
}
]
},
}
i use $map to get item which contain memoryTime less than now in memoryLine like below:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
]
},
},
}
}
}
])
my result is like below:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
},
{ // =====> this item is not needed because of containing empty memoryLine
"time": "2020-02-23T10:45:26.615Z",
"memoryLine": [] // =======> i dont want empty item
},
]
but i want result like this:
"notes": [
{
"time": "2020-02-23T10:12:19.190Z",
"memoryLine": [
{
"hasReviewed": false,
"_id": "5e51df83966daeae41e7f5b1",
"memoryTime": "2020-02-23T10:42:19.190Z"
}
]
}
]
so i use $filter after $map to filter item which contain empty memoryLine:
db.notes.aggregate([{
$match: {
"$and": [
{ userId: '5e33ee0b4a3895a6d246f3ee'}
]
}
}, {
$project: {
notes: {
$map: {
input: "$notes",
in: {
$mergeObjects: [
"$$this",
{
memoryLine: {
$filter: {
input: "$$this.memoryLine",
as: "mLine",
cond: { $lt: ["$$mLine.memoryTime", new Date()] }
}
}
}
],
$filter: {
input: "$$this",
as: "note",
cond: { $ne: ["$$note.memoryLine", []] }
}
},
},
}
}
}
Then this goes wrong.
You need to run another $filter as a separate pipeline stage (for readability) or as the most outer one for your current $project. I would prefer the first one:
{
$addFields: {
notes: {
$filter: {
input: "$notes",
cond: {
$ne: [ "$$this.memoryLine", [] ]
}
}
}
}
}

MongoDB : add New Field to existing sub document after $look stage or merge lookup response to main document

I want new field "isActive" inside modifierStatus sub document which is coming from modifieritems collection.
modifieritems collection :
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0
}
favoritedrinks collection :
{
"alcoholName" : "whiskey",
"modifierList" : [{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
}
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
}]
}
my query is :
db.getCollection('favoritedrinks').aggregate([
{ "$sort": { "alcoholName": 1 } },
{"$lookup": {
"from": "modifieritems",
localField: 'modifierList.modifierId',
foreignField: '_id',
as: 'modifier'
}},
{
$project:{
"alcoholName" : "$alcoholName",
"modifierStatus":"$modifier",
}
},
]);
But my expected result :
{
"alcoholName" : "Whiskey",
"modifierStatus" : [
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a61"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a67"),
"modifierName" : "xxx",
"isActive" : 1,
},
{
"_id" : ObjectId("5e6a5a0e6d40624b12453a66"),
"modifierId" : ObjectId("5e6a5a0e6d40624b12453a6a"),
"modifierName" : "yyy",
"isActive" : 0,
}
]
}
anyone please help me?
Try this query :
Update with new requirement :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
"$lookup": {
"from": "modifieritems",
localField: "modifierList.modifierId",
foreignField: "_id",
as: "modifierStatus"
}
},
{
$addFields: {
modifierStatus: {
$map: {
input: "$modifierList",
as: "m",
in: {
$mergeObjects: [
{
$arrayElemAt: [ /** As filter would only get one object (cause you'll have only one matching doc in modifieritems coll for each "modifierList.modifierId", So getting first element out of array, else you need to take this array into an object & merge that field to particular object of 'modifierList') */
{
$filter: {
input: "$modifierStatus",
cond: {
$eq: [
"$$this._id",
"$$m.modifierId"
]
}
}
},
0
]
},
"$$m"
]
}
}
}
}
},
{
$project: {
modifierStatus: 1,
alcoholName: 1,
_id: 0
}
}
])
Test : MongoDB-Playground
Old :
db.favoritedrinks.aggregate([
{
"$sort": {
"alcoholName": 1
}
},
{
$lookup: {
from: "modifieritems",
let: {
id: "$modifierList.modifierId"
},
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$id"] } }
},
/** Adding a new field modifierId(taken from _id field of modifieritems doc)
* to each matched document from modifieritems coll */
{
$addFields: {
modifierId: "$_id"
}
}
],
as: "modifierStatus"
}
},
/** By mentioning 0 to particular fields to remove them & retain rest all other fields */
{
$project: {
modifierList: 0,
_id: 0
}
}
])
Test : MongoDB-Playground
When you want $project to include a field's current value while keeping the same field name, you need only specify :1. When you use "$field" you are explicitly setting the value, which will overwrite any existing value.
Try making your projection:
{
$project:{
"alcoholName" : 1,
"modifier.isActive": 1,
"modifier.modifierName": 1
}
}

How to get inbox's coversation's users and its last messages in NodeJS with MongoDb

MongoDb User Collection
Think that you are User 1. In the inbox page, I want to get the conversation's last message. I may sent the last message or receive the last message from a user. The last message will be shown in inbox like this:
Query Result Shold Be Like This
[
{
"_id": "user2",
"username": "user2",
"lastMessage": "3"
},
{
"_id": "user3",
"username": "user3",
"lastMessage": "2"
}
]
User 1 Document on MongoDb
{
"_id" : ObjectId("user1"),
"username" : "user1",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user2")
}
},
"message" : "1",
"received_at" : ISODate("2019-04-27")
},
{
"from" : {
"user" : {
"id" : ObjectId("user3")
}
},
"message" : "2",
"received_at" : ISODate("2019-05-1")
}
]
}
User 2 Document on MongoDb
{
"_id" : ObjectId("user2"),
"username" : "user2",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user1")
}
},
"message" : "3",
"received_at" : ISODate("2019-04-29")
}
]
}
User 3 Document on MongoDb
{
"_id" : ObjectId("user3"),
"username" : "user3",
"inbox" : [
{
"from" : {
"user" : {
"id" : ObjectId("user1")
}
},
"message" : "4",
"received_at" : ISODate("2019-04-30")
}
]
}
What query I have to use for this problem ?
You can use below aggregation:
db.col.aggregate([
{
$unwind: "$inbox"
},
{
$addFields: {
participants: [ "$_id", "$inbox.from.user.id" ]
}
},
{
$match: { participants: "user1" }
},
{
$addFields: {
participants: {
$filter: {
input: "$participants",
cond: {
$ne: [ "$$this", "user1" ]
}
}
}
}
},
{
$unwind: "$participants"
},
{
$sort: { "inbox.received_at": -1 }
},
{
$group: {
_id: "$participants",
lastMessage: { $first: "$inbox.message" }
}
}
])
The challenge here is that you need to analyse an array which might contain for instance [user1, user2] or [user2, user1] and both should be considered as the same grouping key.
To do that you can introduce participants array in order to filter out all the messages that do not belong to user1 and then remove user1 from that array (using $filter) so that you can group by second user.
The point is that you run $unwind to get single document per message and then $sort them so that you can run $group with $first to get the most recent one
Mongo Playground

Get array elements across all documents in the collection that match a specific array element content

I have the following document structure in my MongoDB and I am trying to return an array of objects containing all prices for itemID "5a59c587fa9b4a212b0a1312" across all documents using the following query but unfortunately it is always returning an empty array. Can someone please advice what I might be doing wrong here? and how I can get such a result?
Note: I am using promised-mongo in a Node.js app to access my MongoDB
Query I tried:
{ transDetails: { $elemMatch: { itemID: "5a59c587fa9b4a212b0a1312" } } }
DB sample:
{
"_id" : ObjectId("5a688e7ea52deb6d4a6b6663"),
"transactionID" : "1",
"transDetails" : [
{
"itemID" : "5a59c587fa9b4a212b0a1312",
"price" : "22"
},
{
"itemID" : "5a59c95b081c6c612bd17058",
"price" : "24"
}
] }
{
"_id" : ObjectId("5a6aa99a52deb6d4a67714"),
"transactionID" : "2",
"transDetails" : [
{
"itemID" : "5a59c587fa9b4a212b0a1312",
"price" : "35"
},
{
"itemID" : "5a59c95b081c6c612bd17058",
"price" : "24"
}
] }
Find with projection to have only matched items in the transDetails:
.find({"transDetails.itemID": "5a59c587fa9b4a212b0a1312"}, {_id:0, "transDetails.$": 1})
Will return
{
"transDetails" : [
{
"itemID" : "5a59c587fa9b4a212b0a1312",
"price" : "22"
}
]
},
{
"transDetails" : [
{
"itemID" : "5a59c587fa9b4a212b0a1312",
"price" : "35"
}
]
},
....
Wish you re-shape the documents, you can use aggregation:
.aggregate([
{ $match: { "transDetails.itemID": "5a59c587fa9b4a212b0a1312" } },
{ $project: {
_id: 0,
transDetails: {
$filter: {
input: "$transDetails",
as: "item",
cond: { $eq: [ "$$item.itemID", "5a59c587fa9b4a212b0a1312" ] }
}
}
} },
{ $unwind: "$transDetails"},
{ $project: {price: "$transDetails.price"}}
])
Which will give you
{
"price" : "22"
},
{
"price" : "35"
},
...

Resources