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

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

Related

Filter out items from mongoDb nested array and add new field

There is a mongoDb collection, looks like this:
[
{
"_id": {
"$oid": "63110728d74738cdc48a7de0"
},
"listName": "list_name",
"alloweUidList": [
{
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"role": "creator",
"boolId": 1,
"crDate": "2022-09-01 21:25",
"modDate": null
}
],
"offerModelList": [
{
"offerListenerEntity": {
"_id": "6311072ed74738cdc48a7de1",
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"itemName": "sometehing",
"crDate": "2022-09-01 21:25",
"boolId": 1,
"modDate": null,
"imageColorIndex": 3,
"shoppingListId": "63110728d74738cdc48a7de0",
"checkFlag": 0,
"itemCount": 1
},
"offers": [
{
"id": "62fa7983b7f32cc089864a3b",
"itemId": 127382,
"itemName": "item_1",
"itemCleanName": "item_clean_name",
"imageUrl": "item.png",
"price": 10,
"measure": "measure",
"salesStart": "N.a",
"source": "source",
"runDate": "2022.08.15-14:11:15",
"shopName": "shop_name",
"isSales": 1,
"insertType": "automate",
"timeKey": "2022_08_15_18_51",
"imageColorIndex": 0,
"isSelectedFlag": 1,
"selectedBy": "not_selected",
"itemCount": 1
},
{
"id": "62fa7983b7f32cc089864a3b",
"itemId": 127382,
"itemName": "item_2",
"itemCleanName": "item_clean_name",
"imageUrl": "image.png",
"price": 20,
"measure": "measure",
"salesStart": "N.a",
"source": "source",
"runDate": "2022.08.15-14:11:15",
"shopName": "shop_name",
"isSales": 1,
"insertType": "automate",
"timeKey": "2022_08_15_18_51",
"imageColorIndex": 0,
"isSelectedFlag": 0,
"selectedBy": "not_selected",
"itemCount": 1
}
]
},
{
"offerListenerEntity": {
"_id": "6311a5c0d74738cdc48a7de2",
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"itemName": "anything",
"crDate": "2022-09-02 08:42",
"boolId": 1,
"modDate": null,
"imageColorIndex": 1,
"shoppingListId": "63110728d74738cdc48a7de0",
"checkFlag": 0,
"itemCount": 2
},
"offers": []
}
],
"crDate": "2022-09-01 21:25",
"modDate": "2022-09-01 21:25",
"boolId": 1,
"imageColorIndex": 1
}
]
So it has an array, with a nested array.
I would like to filter out the entire item from the offerModelList array, if the offerModelList.offerListenerEntity.boolId == 0 It's working with this aggregate query:
[
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
}
]
The problem comes, when I try to filter out items from the offerModelList.offers array based on isSelectedFlag field.
I modified my query to this:
db.collection.aggregate([
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
},
{
"$addFields": {
"offerModelList.offers": {
"$filter": {
"input": "$offerModelList.offers",
"as": "x",
"cond": {
"$eq": [
"$$x.isSelectedFlag",
1
]
}
}
}
},
}
])
The problem is, it alwas return empty offers array.
Here comes an example: https://mongoplayground.net/p/kksRpoNKr1k in this specific case the offers array should cointains only 1 item.
Don't think that you are able to directly filter from offerModelList.offers.
Instead, for the last stage,
$set - Set offerModelList field.
1.1. $map - Iterate element in offerModelList array and return a new array.
1.1.1. $mergeObjects - Merge current iterated document with the document resulted from 1.1.1.1.
1.1.1.1. Document with offers array. Via $filter to filter the document(s) with isSelectedFlag: 1.
db.collection.aggregate([
{
"$match": {
"alloweUidList": {
"$elemMatch": {
"uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"boolId": 1
}
},
"boolId": 1,
}
},
{
"$addFields": {
"offerModelList": {
"$filter": {
"input": "$offerModelList",
"as": "i",
"cond": {
"$eq": [
"$$i.offerListenerEntity.boolId",
1
]
}
}
}
},
},
{
"$set": {
"offerModelList": {
$map: {
input: "$offerModelList",
as: "offerModel",
in: {
$mergeObjects: [
"$$offerModel",
{
offers: {
$filter: {
input: "$$offerModel.offers",
as: "x",
cond: {
$eq: [
"$$x.isSelectedFlag",
1
]
}
}
}
}
]
}
}
}
}
}
])
Demo # Mongo Playground

MongoDB. Get every element from array in new field

I have a document with a nested array array_field:
{
"_id": {
"$oid": "1"
},
"id": "1",
"array_field": [
{
"data": [
{
"regions": [
{
"result": {
"item": [
"4",
"5",
"3"
]
}
},
{
"result": {
"item": [
"5"
]
}
},
{
"result": {
"item": [
"1"
]
}
}
]
}
]
}
]
}
I need add new field, new_added_field for example, with each array element from array_field.data.regions.result.item and remove array_field from document.
For example:
{
"_id": {
"$oid": "1"
},
"id": "1",
"new_added_field": [4,5,3,5,1]
}
I think i can do this with help of $unwind or $map but have difficulties and need dome hint, how i can do it with help op aggregation?
As you said,
db.collection.aggregate([
{
"$project": {
newField: {
"$map": {
"input": "$array_field",
"as": "m",
"in": "$$m.data.regions.result.item"
}
}
},
},
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{ "$unwind": "$newField" },
{
"$group": {
"_id": "$_id",
"newField": { "$push": "$newField" }
}
}
])
Working Mongo playground

How to get only multiple counts in Mongodb?

Im trying to get multiple count values only from multiple documents in a collection which looks like this,( basically I want to get a count of how many are from the 4 directions)
{
"empno": 1500,
"province": "North"
}
{
"empno": 1600,
"province": "West"
}
early I found a solution and implemented following query;
([
{ "$facet": {
"N": [
{ "$match": { "province": "North" }},
{ "$count": "N" }
],
"E": [
{ "$match": { "province": "East" }},
{ "$count": "E" }
],
"S": [
{ "$match": { "province": "South" }},
{ "$count": "S" }
],
"W": [
{ "$match": { "province": "West" }},
{ "$count": "W" }
]
}},
{ "$project": {
"N": { "$arrayElemAt": ["$N.N", 0] },
"E": { "$arrayElemAt": ["$E.E", 0] },
"S": { "$arrayElemAt": ["$S.S", 0] },
"W": { "$arrayElemAt": ["$W.W", 0] },
}}
])
The output I get is
{ N: 1, W: 1 }
How can I get the values only like without the keys and also I want the blank fields that are empty to be with a 0. Like this;
{1, 0, 0, 1}
Facet
Query
group by null, is the thing that you needed to add to get the count
Test code here
db.collection.aggregate([
{
"$facet": {
"g0": [
{
"$match": {
"province": {
"$eq": "North"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0
}
}
],
"g1": [
{
"$match": {
"province": {
"$eq": "East"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0
}
}
],
"g2": [
{
"$match": {
"province": {
"$eq": "South"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0
}
}
],
"g3": [
{
"$match": {
"province": {
"$eq": "West"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
},
{
"$project": {
"_id": 0
}
}
]
}
},
{
"$set": {
"data": {
"$map": {
"input": {
"$objectToArray": "$$ROOT"
},
"in": {
"$cond": [
{
"$eq": [
"$$d.v",
[]
]
},
0,
{
"$let": {
"vars": {
"m": {
"$arrayElemAt": [
"$$d.v",
0
]
}
},
"in": "$$m.count"
}
}
]
},
"as": "d"
}
}
}
},
{
"$project": {
"data": 1
}
}
])
Group
Query
group is used instead of facet (facet is like 1 aggregation per field)
each group have its index (from the array), some indexes will be missing (because no documents exist)
add a zero-data field that has all indexes and count=0 (see bellow)
add to zero-data, the data found (the ones that existed in the collection,and we have groups for them) the rest keep the count=0
Test code here
db.collection.aggregate([
{
"$group": {
"_id": {
"$switch": {
"branches": [
{
"case": {
"$eq": [
"$province",
"North"
]
},
"then": {
"index": 0,
"province": "North"
}
},
{
"case": {
"$eq": [
"$province",
"East"
]
},
"then": {
"index": 1,
"province": "East"
}
},
{
"case": {
"$eq": [
"$province",
"South"
]
},
"then": {
"index": 2,
"province": "South"
}
},
{
"case": {
"$eq": [
"$province",
"West"
]
},
"then": {
"index": 3,
"province": "West"
}
}
],
"default": {
"index": 5
}
}
},
"count": {
"$sum": 1
}
}
},
{
"$group": {
"_id": null,
"data": {
"$push": {
"index": "$_id.index",
"province": "$province",
"count": "$count"
}
}
}
},
{
"$project": {
"_id": 0
}
},
{
"$set": {
"zero-data": [
{
"index": 0,
"count": 0
},
{
"index": 1,
"count": 0
},
{
"index": 2,
"count": 0
},
{
"index": 3,
"count": 0
}
]
}
},
{
"$set": {
"data": {
"$reduce": {
"input": "$zero-data",
"initialValue": [],
"in": {
"$let": {
"vars": {
"all_data": "$$value",
"d": "$$this"
},
"in": {
"$let": {
"vars": {
"found_data": {
"$filter": {
"input": "$data",
"cond": {
"$eq": [
"$$d.index",
"$$d1.index"
]
},
"as": "d1"
}
}
},
"in": {
"$concatArrays": [
"$$all_data",
[
{
"$cond": [
{
"$eq": [
"$$found_data",
[]
]
},
{
"index": "$$d.index",
"count": 0
},
{
"$arrayElemAt": [
"$$found_data",
0
]
}
]
}
]
]
}
}
}
}
}
}
}
}
},
{
"$project": {
"data": {
"$map": {
"input": "$data",
"in": "$$this.count"
}
}
}
}
])

Mongoose - Search an array and return the matched results

I am looking for a way to return matched items under an array in Mongo (mongoose).
The data structure is like this:
[
{
"_id": {
"$oid": "123456"
},
"make": "Lamborghini",
"makeLogo": "/images/makeLogos/lamborghini.png",
"models": [
{
"model": "Aventador",
"_id": {
"$oid": "678909t"
}
},
{
"model": "Countach",
"_id": {
"$oid": "678909"
}
}
],
"__v": 0
},
{
"_id": {
"$oid": "2345678i90"
},
"make": "Nissan",
"makeLogo": "/images/makeLogos/nissan.png",
"models": [
{
"model": "350z",
"_id": {
"$oid": "678909gggg"
}
},
{
"model": "370z",
"_id": {
"$oid": "678909rrrrr"
}
}
],
"__v": 0
}
]
I would like to search: 3 and it should return 350z and 370z to me.
I tried the below:
modelsModel.find(
{"models.model": { $regex: req.query.carModel + '.*', $options:'i'}},
)
.exec(function(err, models) {
if(err){
res.status(400).json({error: err});
} else {
res.status(200).json({cars: models});
}
});
Where the data returned is this:
[
{ _id: 5ca893b7587ab519613b806e,
make: 'Lamborghini',
makeLogo: '/images/makeLogos/lamborghini.png',
__v: 0,
models: [
[Object], [Object]
]
}
]
This is when I searched for Countach which has a match.
I know I am doing something very obviously wrong here but this is so new to me I don't even know where to begin.
Thanks in advance!
You can use below aggregation with mongodb 4.2
db.collection.aggregate([
{ "$match": {
"models.model": { "$regex": "A" }
}},
{ "$project": {
"models": {
"$filter": {
"input": "$models",
"cond": {
"$regexMatch": {
"input": "$$this.model",
"regex": "A"
}
}
}
}
}}
])

Is there a way in mongodb to group at multiple levels

I have a document which contains an array of array as given below.
This is the first document.
{
"_id": "5d932a2178fdfc4dc41d75da",
"data": [
{
"nestedData": [
{
"_id": "5d932a2178fdfc4dc41d75e1",
"name": "Special 1"
},
{
"_id": "5d932a2178fdfc4dc41d75e0",
"name": "Special 2"
}
]
}
]
}
I need to lookup(join) to another collection with the _id in the nestedData array in the aggregation framework.
The 2nd document from which I need to lookup is
{
"_id": "5d8b1ac3b15bc72d154408e1",
"status": "COMPLETED",
"rating": 4
}
I know I need to $unwind it twice to convert nestedData array into object.
But how do I group back again to form the same object like given below
{
"_id": "5d932a2178fdfc4dc41d75da",
"data": [
{
"array": [
{
"_id": "5d932a2178fdfc4dc41d75e1",
"name": "Special 1",
"data": {
"_id": "5d8b1ac3b15bc72d154408e1",
"status": "COMPLETED",
"rating": 4
},
{
"_id": "5d932a2178fdfc4dc41d75e0",
"name": "Special 2",
"data": {
"_id": "5d8b1ac3b15bc72d154408e0",
"status": "COMPLETED",
"rating": 4
},
}
]
}
]
}
Try this query
db.testers.aggregate([
{$lookup: {
from: 'demo2',
pipeline: [
{ $sort: {'_id': 1}},
],
as: 'pointValue',
}},
{
$addFields:{
"data":{
$map:{
"input":"$data",
"as":"doc",
"in":{
$mergeObjects:[
"$$doc",
{
"nestedData":{
$map:{
"input":"$$doc.nestedData",
"as":"nestedData",
"in":{
$mergeObjects:[
{ $arrayElemAt: [ {
"$map": {
"input": {
"$filter": {
"input": "$pointValue",
"as": "sn",
"cond": {
"$and": [
{ "$eq": [ "$$sn._id", "$$nestedData._id" ] },
]
}
}
},"as": "data",
"in": {
"name": "$$nestedData.name",
"data":"$$data",
}}
}, 0 ] },'$$nestedData'
],
}
}
}
}
]
}
}
}
}
},
{$project: { pointValue: 0 } }
]).pretty()