Need help filtering documents with criteria inside array - mongoDB - mongodb

Need help with query to filter the records in mongoDB. I am using compass to run the que
We have thousands of records/documents where each record/document contains the following array. For few documents, the events in lifeCycleinfo are out of order i.e. payment.completed event comes before 1payment.completed1 event.
I need to filter those records where completed event comes before created event
{
"lifeCycleInfo": [
{
"eventId": "9b8b6adfae",
"eventSubType": "SendTransfer_Receipt",
"eventType": "SendTransfer",
"odsTimestamp": {
"$date": "2023-02-06T14:33:42.308Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:42.271Z"
}
},
{
"eventId": "06e8d144-531b02",
"eventSubType": "payment.created",
"eventType": "Notification",
"odsTimestamp": {
"$date": "2023-02-06T14:33:45.488Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:45.479Z"
}
},
{
"eventId": "9da54454d6",
"eventSubType": "payment.completed",
"eventType": "Notification",
"odsTimestamp": {
"$date": "2023-02-06T14:33:46.698Z"
},
"payload": "{}",
"timestamp": {
"$date": "2023-02-06T14:33:46.689Z"
}
}
]
}
I tried to find it based on array index but not working.
{"lifeCycleInfo[1].eventtype":"payment.completed"}

You can use $reduce with $switch to iterate through the lifeCycleInfo array and keep track of the status with an object {inOrder: <bool>, seenCreated: <bool>}. We can conditionally set the 2 bools and use the final result to get the matched documents.
db.collection.aggregate([
{
$set: {
inOrderResult: {
"$reduce": {
"input": "$lifeCycleInfo",
"initialValue": {
inOrder: false,
seenCreated: false
},
"in": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$$this.eventSubType",
"payment.created"
]
},
"then": {
inOrder: "$$value.inOrder",
seenCreated: true
}
},
{
"case": {
$and: [
{
$eq: [
"$$this.eventSubType",
"payment.completed"
]
},
{
$eq: [
"$$value.seenCreated",
true
]
}
]
},
"then": {
inOrder: true,
seenCreated: true
}
}
],
default: "$$value"
}
}
}
}
}
},
{
$match: {
"inOrderResult.inOrder": true
}
}
])
Mongo Playground

Related

MongoDB: Cannot increment with non-numeric argument

I have a collection of User documents, containing several fields and an array of Bets. I am trying to set up an update that I will run on a schedule. In the User documents, the Balance field needs to be incremented by (Bets[index].multiplier * Bets[index].amount), and the Bets[index] needs to be marked as paid out and whether the bet was successful.
An example of a User document I'm trying to update. After running the update, User.bets[1].isPaidOut should be true, User.bets[1].didWin should be true, and User.balance should be incremented by 89 (that is, because ceiling(1.27 * 70) = 89).
{
"_id": {
"$oid": "63c9ca0217b00eaef7d6237f"
},
"guildId": "1061387401743831121",
"userId": "307884715677974530",
"userName": "Iron Man",
"balance": {
"$numberDouble": "100.0"
},
"lastClaimedAt": {
"$date": {
"$numberLong": "1674168834621"
}
},
"bets": [
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "New York Giants",
"opponentTeam": "Philadelphia Eagles",
"teamId": {
"$numberInt": "19"
},
"opponentTeamId": {
"$numberInt": "21"
},
"eventId": "401438004",
"amount": {
"$numberInt": "20"
},
"multiplier": {
"$numberDouble": "3.85"
},
"isPaidOut": true,
"didWin": false
},
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "Philadelphia Eagles",
"opponentTeam": "New York Giants",
"teamId": {
"$numberInt": "21"
},
"opponentTeamId": {
"$numberInt": "19"
},
"eventId": "401438004",
"amount": {
"$numberInt": "70"
},
"multiplier": {
"$numberDouble": "1.27"
},
"isPaidOut": false
},
{
"sport": "NFL",
"eventWeek": {
"$numberInt": "2"
},
"team": "San Francisco 49ers",
"opponentTeam": "Dallas Cowboys",
"teamId": {
"$numberInt": "25"
},
"opponentTeamId": {
"$numberInt": "6"
},
"eventId": "401438006",
"amount": {
"$numberInt": "200"
},
"multiplier": {
"$numberDouble": "1.49"
},
"isPaidOut": false
}
],
"createdAt": {
"$date": {
"$numberLong": "1674168834633"
}
},
"updatedAt": {
"$date": {
"$numberLong": "1674338378566"
}
},
"__v": {
"$numberInt": "3"
}
}
This is what I have for my Update. When this is run, I receive this error: uncaught promise rejection: write exception: write errors: [Cannot increment with non-numeric argument: {balance: { $ceil: { $mul: [ "bets.$.amount", "bets.$.multiplier" ] } }}]. I thought this could have been because of mismatched types, but all documents have the same as the one above.
const winnerId = 21;
const eventId = "401438004";
usersCollection.updateMany(
{
bets: {
"$elemMatch": {
eventId: eventId,
isPaidOut: false,
teamId: winnerId
}
}
},
{
$set: { "bets.$.isPaidOut" : true, "bets.$.didWin": true },
$inc: {
balance: {$ceil: {$mul: {"bets.$.amount": "bets.$.multiplier"} }}
}
}
);
$ceil is an aggregation operator, not an update operator, so you can only use it with an update if you use the update with aggregation pipeline
You will have to use pipelined update. But then, you won't be able to use $ field names. Here's a solution using $map and $reduce.
db.collection.updateMany(
bets: {
"$elemMatch": {
eventId: "401438004",isPaidOut: false,teamId: 21
}
},
[
{
"$set": {
"balance": { //set the updated balance
$reduce: {
input: "$bets", //iterate through the bets array
initialValue: "$balance", //start with the existing balance
in: {
$add: [
"$$value",
{
$cond: [ //check the condition
{$and: [
{$eq: ["$$this.eventId","401438004"]},
{$eq: ["$$this.teamId",21]},
{$eq: ["$$this.isPaidOut",false]}
]},
{$ceil: {$multiply: ["$$this.amount","$$this.multiplier"]}}, //if true, perform the arithmetic operation andd add it to existing $$value
0 //if false, add 0
]
}
]
}
}
},
"bets": { //set the updated bets array
$map: { //iterate through the bets array
input: "$bets",
in: {
$cond: [ //check the condition
{$and: [
{$eq: ["$$this.eventId","401438004"]},
{$eq: ["$$this.teamId",21]},
{$eq: ["$$this.isPaidOut",false]}
]},
{
"$mergeObjects": [ //if true, merge the array element with updated fields
"$$this",
{"didWin": true,"isPaidOut": true}
]
},
{
"$mergeObjects": [ //if false, keep the array element as it is
"$$this"
]
}
]
}
}
}
}
}
])
Demo

Update more than one inner document in MongoDb

In my example project, I have employees under manager. Db schema is like this;
{
"employees": [
{
"name": "Adam",
"_id": "5ea36b27d7ae560845afb88e",
"bananas": "allowed"
},
{
"name": "Smith",
"_id": "5ea36b27d7ae560845afb88f",
"bananas": "not-allowed"
},
{
"name": "John",
"_id": "5ea36b27d7ae560845afb88g",
"bananas": "not-allowed"
},
{
"name": "Patrick",
"_id": "5ea36b27d7ae560845afb88h",
"bananas": "allowed"
}
]
}
In this case Adam is allowed to eat bananas and Smith is not. If I have to give the permission of eating bananas from Adam to Smith I need to perform update operation twice like this:
db.managers.update(
{ 'employees.name': 'Adam' },
{ $set: { 'employees.$.bananas': 'not-allowed' } }
);
and
db.managers.update(
{ 'employees.name': 'Smith' },
{ $set: { 'employees.$.bananas': 'allowed' } }
);
Is it possible to handle this in a single query?
You can use $map and $cond to perform conditional update to the array entries depending on the name of the employee. A $switch is used for potential extension of cases.
db.collection.update({},
[
{
"$set": {
"employees": {
"$map": {
"input": "$employees",
"as": "e",
"in": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$$e.name",
"Adam"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "not-allowed"
}
]
}
},
{
"case": {
$eq: [
"$$e.name",
"Smith"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "allowed"
}
]
}
}
],
default: "$$e"
}
}
}
}
}
}
])
Mongo Playground
db.managers.update(
{
$or: [
{"employees.name": "Adam"},
{"employees.name": "Smith"}
]
},
{
$set: {
"employees.$[e].bananas": {
$cond: [{ $eq: ["$e.name", "Adam"] }, "not-allowed", "allowed"]
}
}
},
{
arrayFilters: [{ "e.name": { $in: ["Adam", "Smith"] } }]
}
)

mongo query for a one-to-many collection where all records has to match the condition and get an unique record

Employee has multiple employeeActions, the employeeActions data looks like this:
[
{
"email": "one#gmail.com",
"companyRegNo": 105,
"event": {
"created": ISODate("2022-09-16T06:42:04.387Z"),
"desc": "COMPLETED_APPLICATIONS",
"note": "Direct apply"
}
},
{
"email": "one#gmail.com",
"companyRegNo": 105,
"event": {
"created": ISODate("2022-09-20T06:42:42.761Z"),
"desc": "ASKED_TO_REVIEW",
}
},
{
"email": "two#gmail.com",
"companyRegNo": 227,
"event": {
"created": ISODate("2022-09-16T06:42:04.387Z"),
"desc": "COMPLETED_APPLICATIONS",
"note": "Direct apply",
}
},
{
"email": "two#gmail.com",
"companyRegNo": 227,
"event": {
"created": ISODate("2022-09-28T06:42:42.761Z"),
"desc": "ASKED_TO_REVIEW",
}
},
{
"email": "three#gmail.com",
"companyRegNo": 157,
"event": {
"created": ISODate("2022-09-16T06:42:04.387Z"),
"desc": "COMPLETED_APPLICATIONS",
"note": "Direct apply",
}
},
{
"email": "four#gmail.com",
"companyRegNo": 201,
"deleted": true,
"event": {
"created": ISODate("2022-09-15T06:42:42.761Z"),
"desc": "COMPLETED_APPLICATIONS",
}
},
]
I need to write an aggregation query to get all email ids where the employee action of the user
- Does not have an ASKED_TO_REVIEW event created before '2022-09-25'
- deleted is either false or does not exist
The out put should have only
{"email": "one#gmail.com"}
{"email": "three#gmail.com"}
The below match and project query did not work
db.collection.aggregate([
{
"$match": {
"$and": [
{
"deleted": {
"$ne": true
}
},
{
"$or": [
{
"$and": [
{
"event.name": {
"$eq": "ASKED_TO_REVIEW"
}
},
{
"event.created": {
"$lt": ISODate("2022-09-25")
}
}
]
},
{
"event.name": {
"$ne": "ASKED_TO_REVIEW"
}
}
]
}
]
}
},
{
"$project": {
"email": 1,
"_id": 0
}
}
])
How do i go about this?
You need to group the events by email and then apply your filtering logic to those groups, something like this:
db.collection.aggregate([
{
"$group": {
"_id": "$email",
"field": {
"$push": "$$ROOT"
}
}
},
{
"$match": {
$expr: {
"$eq": [
0,
{
"$size": {
"$filter": {
"input": "$field",
"as": "item",
"cond": {
"$or": [
{
"$and": [
{
"$eq": [
{
"$getField": {
"field": "desc",
"input": "$$item.event"
}
},
"ASKED_TO_REVIEW"
]
},
{
"$lt": [
{
"$getField": {
"field": "created",
"input": "$$item.event"
}
},
ISODate("2022-09-25")
]
}
]
},
{
"$eq": [
{
"$getField": {
"field": "deleted",
"input": "$$item"
}
},
true
]
}
]
}
}
}
}
]
}
}
},
{
"$project": {
email: "$_id",
"_id": 0
}
}
])
Playground link.
Figured out the working query. After grouping by email, $elemMatch needs to be used for the and condition between "event.desc" and "event.created"
db.collection.aggregate([
{
"$group": {
"_id": "$email",
"field": {
"$push": "$$ROOT"
}
}
},
{
"$match": {
"$and": [
{
"field.deleted": {
"$ne": true
}
},
{
"$or": [
{
"field": {
"$elemMatch": {
"event.desc": "ASKED_TO_REVIEW",
"event.created": {
"$lt": ISODate("2022-09-25")
}
}
}
},
{
"field.event.desc": {
"$ne": "ASKED_TO_REVIEW"
}
}
]
}
]
}
},
{
"$project": {
email: "$_id",
"_id": 0
}
}
])
Playground Link

Filter documents that have id in another collection in MongoDB with aggregation framework

So I have two collection. collectionA and collectionB
collection A has following documents
db={
"collectiona": [
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
],
"collectionb": [
{
"_id": "6173ddf33ed0wef368a094zq",
"collectionaID": "6173ddf33ed09368a094e68a",
"data": [
{
"userID": "123",
"visibility": false,
"response": false
},
{
"userID": "2345",
"visibility": true,
"response": true
}
]
},
{
"_id": "6173ddf33ed09368awef4e68g",
"collectionaID": "61wefdf33ed09368a094e6dc",
"data": [
{
"userID": "5678",
"visibility": false,
"response": false
},
{
"userID": "674",
"visibility": true,
"response": false
}
]
}
]
}
So What I need is documents from collection A which has response false in collection B
and document should be sorted by first the ones that have visibility false and then the ones that have visibility true
for eg. userID : 123 should get 3 documents
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
whereas userID 2345 should get two
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
User 674 will receive 3 objects from collection A but second would be in the last as it has visibility true for that document
{
"_id": "6173ddf33ed09368a094e68a",
"title": "a"
},
{
"_id": "61wefdfewf09368a094ezzz",
"title": "c"
},
{
"_id": "61wefdf33ed09368a094e6dc",
"title": "b"
},
MongoDB Playground link : https://mongoplayground.net/p/3rLry0FPlw-
Really appreciate the help. Thanks
You can start from collectionA:
$lookup the collectionB for the record related to the user specified
filter out collectionB documents according to response
assign a helper sortrank field based on the visibility and whether collectionaID is a match
$sort according to sortrank
wrangle back to the raw collection A
db.collectiona.aggregate([
{
"$lookup": {
"from": "collectionb",
let: {
aid: "$_id"
},
"pipeline": [
{
$unwind: "$data"
},
{
$match: {
$expr: {
$and: [
{
$eq: [
"$data.userID",
"2345"
]
},
{
$eq: [
"$collectionaID",
"$$aid"
]
}
]
}
}
}
],
"as": "collB"
}
},
{
$match: {
"collB.data.response": {
$ne: true
}
}
},
{
"$unwind": {
path: "$collB",
preserveNullAndEmptyArrays: true
}
},
{
"$addFields": {
"sortrank": {
"$cond": {
"if": {
$eq: [
"$collB.data.visibility",
false
]
},
"then": 1,
"else": {
"$cond": {
"if": {
$eq: [
"$collB.collectionaID",
"$_id"
]
},
"then": 3,
"else": 2
}
}
}
}
}
},
{
$sort: {
sortrank: 1
}
},
{
$project: {
collB: false,
sortrank: false
}
}
])
Here is the Mongo playground for your reference.

Change value of a field in embed document with condition in mongo DB

I try to get a data from the database. But I've had a problem in a section where I can't edit the value of the embedded fields. I want to put the boolean value instead of the mobile number. if it has a value, equal to the true and if it does not have a value, it will be false.
I have document like this in my collection:
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"title": "newwww",
"message": [{
"_id": ObjectId("606d6e1037c2544324925c5f"),
"text": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"user_id": null,
"user_full_name": null,
"user_mobile_number": null,
"submit_date": {
"$date": "2021-04-07T08:32:16.093Z"
}
}, {
"_id": ObjectId("606d6edc546feebf508d75f9"),
"text": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"user_id": null,
"user_full_name": null,
"user_mobile_number": "9653256482",
"submit_date": {
"$date": "2021-04-07T08:35:40.881Z"
}
}],
"user_mobile_number": "9652351489",
}
Do query:
db.ticket.aggregate([{"$match": {"_id": ObjectId("606d6ea237c2544324925c61")}}, {
"$project": {"message.is_admin":{
"$let": {
vars: {
mobile_number: "$message.user_mobile_numebr"
},
in: {
"$cond": [{$eq: ['$$mobile_number', null]},false,true ]
}
}
}
}
}])
and result is:
[
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"message": [
{
"is_admin": true
},
{
"is_admin": true
}
]
}
]
but i want result like this:
[
{
"_id": ObjectId("606d6ea237c2544324925c61"),
"message": [
{
"is_admin": false
},
{
"is_admin": true
}
]
}
]
means I want when message.user_mobile_number has value, get true and when value is null get false.
How can I do this?
Demo - https://mongoplayground.net/p/h_zkRfDbZrS
Use $map
db.collection.aggregate([
{
"$project": {
"message": {
"$map": {
input: "$message",
"as": "message",
"in": {
"$cond": [{ "$eq": [ "$$message.user_mobile_number", null ] }, { is_admin: false }, { is_admin: true } ]
}
}
}
}
}
])