Filter out items from mongoDb nested array and add new field - mongodb

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

Related

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"
}
}
}
}
])

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()

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here

Query MongoDB for nested Arrays

Need help for formatting query to find/get values using search parameters with nested Array.
I have an collection as follows
[
{
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z",
"deviceid": "123456",
"values": [
{
"minval": 1,
"minvalues": [
{
"secval": 51,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 1
}
},
{
"secval": 52,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 1
}
},
{
"secval": 56,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 0
}
}
]
}
]
}
]
need the out as follows with search properties as "values.minvalues.secvalues.priority"
[
{
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z",
"deviceid": "123456",
"values": [
{
"minval": 1,
"minvalues": [
{
"secval": 56,
"secvalues": {
"alt": "300",
"mcc": "404",
"mnc": "46",
"priority": 0
}
}
]
}
]
}
]
I tried the following query but with out success
dbRetval.db('ls_gpsdatabase').collection('gpsevent').aggregate([
{ "$match": { "deviceid": { "$in": idList}}},
{ "$sort": { "_id": -1} },
{"$unwind":"$values.minvalues.secvalues"},
//{"$project":{"deviceid":1,"values.minvalues.secvalues.lat":1,"values.minvalues.secvalues.min":1}} ,
{ "$match": { "values.minvalues.secvalues.priority": { "$eq": 1}}},
{ "$group": { "_id": "$deviceid" , "doc": { "$push": "$values.minvalues.secvalues" }}} ]).toArray();
If any can help that would be great full.
You can use $addFields to replace existing field. Since you have two levels of nested arrays you can use $map for outer and $filter for inner to check your condition:
db.col.aggregate([
{
$match: {
"_id": "5b3ad55f66479332a0482961",
"timestamp": "2018-06-17T00:30:00.000Z"
}
},
{
$addFields: {
values: {
$map: {
input: "$values",
as: "value",
in: {
minval: "$$value.minval",
minvalues: {
$filter: {
input: "$$value.minvalues",
as: "minvalue",
cond: {
$eq: [ "$$minvalue.secvalues.priority", 0 ]
}
}
}
}
}
}
}
}
])