How can i do elemMatch inside array using mongodb? - node.js

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

Related

How to count specific sub documents on other sub documents in mongoose?

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

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

how to get data in mongoose where last element in array?

how to get data in mongoose where last element in array?
I have data looks like this:
[
{
"_id" : ObjectId("5b56eb3deb869312d85a8e69"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
}
]
},
{
"_id" : ObjectId("5b56eb3deb869312d8589765"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:03:30.347Z")
},
{
"status" : "done",
"createdAt" : ISODate("2018-07-24T09:04:22.347Z")
}
]
}
]
And, I want to get data above where last object transactionStatus.status = process, so the result should be:
{
"_id" : ObjectId("5b56eb3deb869312d85a8e69"),
"transactionStatus" : [
{
"status" : "pending",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
},
{
"status" : "process",
"createdAt" : ISODate("2018-07-24T09:02:53.347Z")
}
]
}
how to do that with mongoose?
You can use $expr (MongoDB 3.6+) inside of match. Using $let and $arrayElemAt passing -1 as second argument you can get the last element as a temporary variable and then you can compare the values:
db.col.aggregate([
{
$match: {
$expr: {
$let: {
vars: { last: { $arrayElemAt: [ "$transactionStatus", -1 ] } },
in: { $eq: [ "$$last.status", "process" ] }
}
}
}
}
])
The same result can be achieved for lower versions of MongoDB using $addFields and $match. You can add $project then to remove that temporary field:
db.col.aggregate([
{
$addFields: {
last: { $arrayElemAt: [ "$transactionStatus", -1 ] }
}
},
{
$match: { "last.status": "process" }
},
{
$project: { last: 0 }
}
])
//Always update new status at Position 0 using $position operator
db.update({
"_id": ObjectId("5b56eb3deb869312d85a8e69")
},
{
"$push": {
"transactionStatus": {
"$each": [
{
"status": "process",
"createdAt": ISODate("2018-07-24T09:02:53.347Z")
}
],
"$position": 0
}
}
}
)
//Your Query for checking first element status is process
db.find(
{
"transactionStatus.0.status": "process"
}
)
refer $position, $each

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"
},
...

How to include empty array when filtering an array

Here is my item model.
const itemSchema = new Schema({
name: String,
category: String,
occupied: [Number],
active: { type: Boolean, default: true },
});
I want to filter 'occupied' array. So I use aggregate and unwind 'occupied' field.
So I apply match query. And group by _id.
But if filtered 'occupied' array is empty, the item disappear.
Here is my code
Item.aggregate([
{ $match: {
active: true
}},
{ $unwind:
"$occupied",
},
{ $match: { $and: [
{ occupied: { $gte: 100 }},
{ occupied: { $lt: 200 }}
]}},
{ $group : {
_id: "$_id",
name: { $first: "$name"},
category: { $first: "$category"},
occupied: { $addToSet : "$occupied" }
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Here is example data set
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 95, 100, 145, 200 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
The result above query
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
}
]
and the result I want
[
{
"_id" : ObjectId("59c1bced987fa30b7421a3eb"),
"name" : "printer1",
"category" : "printer",
"occupied" : [ 100, 145 ],
"active" : true
},
{
"_id" : ObjectId("59c2dbed992fb91b7421b1ad"),
"name" : "printer2",
"category" : "printer",
"occupied" : [ ],
"active" : true
}
]
how could I do this??
Thanks in advance.
In the simplest form, you keep it simply by not using $unwind in the first place. Your conditions applied imply that you are looking for the "unique set" of matches to specific values.
For this you instead use $filter, and a "set operator" like $setUnion to reduce the input values to a "set" in the first place:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$filter": {
"input": { "$setUnion": [ "$occupied", []] },
"as": "o",
"cond": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
}
}
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
Both have been around since MongoDB v3, so it's pretty common practice to do things this way.
If for some reason you were still using MongoDB 2.6, then you could apply $map and $setDifference instead:
Item.aggregate([
{ "$match": { "active": true } },
{ "$project": {
"name": 1,
"category": 1,
"occupied": {
"$setDifference": [
{ "$map": {
"input": "$occupied",
"as": "o",
"in": {
"$cond": {
"if": {
"$and": [
{ "$gte": ["$$o", 100 ] },
{ "$lt": ["$$o", 200] }
]
},
"then": "$$o",
"else": false
}
}
}},
[false]
]
}
}}
], (err, items) => {
if (err) throw err;
return res.json({ data: items });
});
It's the same "unique set" result as pulling the array apart, filtering the items and putting it back together with $addToSet. The difference being that its far more efficient, and retains ( or produces ) an empty array without any issues.

Resources