MongoDB aggregate query - How to make array of array list in to single array - mongodb

I have a document like this:
this is my example data is attached below,
[
{
"_id": ObjectId("6218b836405919280c209f7e"),
"projectId": ObjectId("6218a31f405919280c209e18"),
"accountId": ObjectId("621888e852bd8836c04b8f82"),
"personalIdRoot": [
{
"_id": ObjectId("6221e7514195b43f24c9953f"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c99540"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdFill": [
{
"_id": ObjectId("6221e7514195b43f24c9953d"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953e"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"personalIdCap": [
{
"_id": ObjectId("6221e7514195b43f24c9953b"),
"personalId": ObjectId("6218b48c405919280c209f6c"),
},
{
"_id": ObjectId("6221e7514195b43f24c9953c"),
"personalId": ObjectId("621ef1e40bd3a220f487cd96"),
}
],
"aps": ObjectId("6218bc18405919280c209f8e"),
}
]
i used this example data for my aggregate query.
my aggregate query is attached below:
please find below code:
db.getCollection('funds').aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$facet: {
"results": [
{
$group: {
_id: 0,
values: { "$addToSet": "$personalIdRoot.welderId" },
values: { "$addToSet": "$personalIdFill.welderId" },
values: { "$addToSet": "$personalIdCap.welderId" }
}
},
],
}
}
])
my result:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
[
ObjectId("6218b48c405919280c209f6c")
],
[
ObjectId("6218b2e4405919280c209f68")
],
[
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
]
}
]
}
But i need my result in a single query:
/* 1 */
{
"results" : [
{
"_id" : 0.0,
"values" : [
ObjectId("6218b48c405919280c209f6c"),
ObjectId("621ef1e40bd3a220f487cd96")
]
}
]
}
I have a result of array of array collections.
but i need it in a single array. like above result
Thanks in advance.

It sounds like what you want is:
db.collection.aggregate([
{
$match: {
accountId: ObjectId("621888e852bd8836c04b8f82"),
projectId: ObjectId("6218a31f405919280c209e18"),
aps: {
$in: [
ObjectId("6218bc18405919280c209f8e")
]
}
}
},
{
$project: {
values: {
$setUnion: [
"$personalIdCap.personalId",
"$personalIdFill.personalId",
"$personalIdRoot.personalId"
]
}
}
}
])
See how it works on the playground example

Related

mongodb query filter documents by array value or size

I have a document that's look like this if it hasn't got any items in the itemList field:
{
"_id":{
"$oid":"62e12a0b73a8c3469e635d93"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId": 1,
}
],
"itemList":[
],
"crDate":"2022-07-27 14:05",
"modDate":"2022-07-27 14:05",
"boolId":1
}
and looks like this if i have some elements in the itemList field:
{
"_id":{
"$oid":"62e12a0b73a8c3469e635d93"
},
"listName":"Kuli Gábor listája nr: 1",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId": 1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item 1 name",
"price":459,
},
"crDate":"2022-07-27 14:13",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
},
{
"itemDetail":{
"itemName":"item 2 name",
"price":169,
},
"crDate":"2022-07-27 14:15",
"checkFlag":0,
"boolId":0,
"volume":1,
"modDate":"2022-07-27 14:16"
}
],
"crDate":"2022-07-27 14:05",
"modDate":"2022-07-27 14:05",
"boolId":1
}
I would like to find documents that has at least one element with boolId: 1 in the itemList array or the itemList array is empty. This query works only if i have item in my array with boolId: 1 but not works if the array is empty:
db.shoppingList.find(
{
"itemList.boolId": 1,
"alloweUidList.uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"alloweUidList.boolId": 1,
"boolId": 1
},
{
listName: 1,
alloweUidList: 1,
crDate: 1,
modDate: 1,
boolId: 1,
itemList: {
$elemMatch: {
$or: [
{boolId: 1},
{itemList:{$exists:true,$eq:[]}}
]
},
},
}
)
Also tried: {$size : 0} thats not works either.
Update, I Forget. If I have elements with boolId:1 and also with boolId:0 in itemList i only want to return values that has boolId:1 So if the document looks like this:
{
"_id":{
"$oid":"62e1855473a8c3469e635d94"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId":1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item name 1",
"price":459,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
},
{
"itemDetail":{
"itemName":"item name 2",
"price":549,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":0,
"volume":1,
"modDate":"2022-07-27 20:35"
}
],
"crDate":"2022-07-27 20:34",
"modDate":"2022-07-27 20:34",
"boolId":1
}
I would like to get this:
{
"_id":{
"$oid":"62e1855473a8c3469e635d94"
},
"listName":"name of list",
"alloweUidList":[
{
"uid":"prQUKkIxljVqbHlCKah7T1Rh7l22",
"role":"creator",
"boolId":1,
}
],
"itemList":[
{
"itemDetail":{
"itemName":"item name 1",
"price":459,
},
"crDate":"2022-07-27 20:35",
"checkFlag":0,
"boolId":1,
"volume":1,
"modDate":null
}
],
"crDate":"2022-07-27 20:34",
"modDate":"2022-07-27 20:34",
"boolId":1
}
And if the itemList array is empty i would return the document with empty itemList array
clarification: if the document has only boolId:0 entries, the query should return the document with empty itemList array.
You can do an $or in $expr to cater to the 2 criteria.
db.collection.aggregate([
{
$match: {
"alloweUidList.uid": "prQUKkIxljVqbHlCKah7T1Rh7l22",
"alloweUidList.boolId": 1,
"boolId": 1
}
},
{
$match: {
$expr: {
$or: [
// itemList is empty array
{
$eq: [
"$itemList",
[]
]
},
// itemList has more than 1 boolId:1 elem
{
$gt: [
{
$size: {
"$filter": {
"input": "$itemList",
"as": "i",
"cond": {
$eq: [
"$$i.boolId",
1
]
}
}
}
},
0
]
}
]
}
}
},
{
"$addFields": {
"itemList": {
"$filter": {
"input": "$itemList",
"as": "i",
"cond": {
$eq: [
"$$i.boolId",
1
]
}
}
}
}
}
])
Here is the Mongo Playground for your reference.
Shouldn't do filtering for at least one element with boolId: 1 in the itemList array or the itemList array is empty in the projection.
This "alloweUidList.boolId": 1 search criteria will lead to no document is returned as the attached documents do not contain alloweUidList.boolId property.
Use $expr operator to use the aggregation operators.
db.shoppingList.find({
$expr: {
$and: [
{
$or: [
{
$eq: [
{
$ifNull: [
"$itemList",
[]
]
},
[]
]
},
{
$in: [
1,
"$itemList.boolId"
]
}
]
},
{
$in: [
"prQUKkIxljVqbHlCKah7T1Rh7l22",
"$alloweUidList.uid"
]
},
{
$in: [
1,
"$alloweUidList.boolId"
]
},
{
$eq: [
"$boolId",
1
]
}
]
}
},
{
listName: 1,
alloweUidList: 1,
crDate: 1,
modDate: 1,
boolId: 1,
itemList: 1
})
Sample Mongo Playground

Getting item in a nested array mongoldb

I am trying to aggregate an object but getting null. Would appreciate help.
here is my sample object
[
{
"_id": 1,
"item": "sweatshirt",
"price.usd": 45.99,
"am": [
{
"one": 100
}
],
qty: 300
},
{
"_id": 2,
"item": "winter coat",
"price.usd": 499.99,
"am": [
{
"one": 50
}
],
qty: 200
},
{
"_id": 3,
"item": "sun dress",
"price.usd": 199.99,
"am": [
{
"one": 10
}
],
qty: 250
}
]
This is my query
db.collection.aggregate([
{
$group: {
_id: {
$getField: {
$literal: "am.one"
}
},
}
}
])
currently I am getting
[
{
"_id": null
}
]
any help is appreciated.
I want my response to look like
{
_id: 1
anotherThin: anotherValue
}
Here's one way you could do it.
db.collection.aggregate([
{ // only pass docs that have a numerical "am.one"
"$match": {
"am.one": {"$type": "number"}
}
},
{ // unwind in case multiple objects in "am"
"$unwind": "$am"
},
{ // group by "id" and average "one" values
"$group": {
"_id": "$am.id",
"am1avg": {"$avg": "$am.one"}
}
}
])
Try it on mongoplayground.net.

Mongo Query to fetch distinct nested documents

I need to fetch distinct nested documents.
Please find the sample document:
{
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z"),
"HList":[
{
"productId": 123,
"name": "Dubai",
"tsh": true
}
],
"PList":[
{
"productId": 123,
"name": "Dubai",
"tsh": false
},
{
"productId": 234,
"name": "India",
"tsh": true
}
],
"CList":[
{
"productId": 234,
"name": "India",
"tsh": false
}
]
}
Expected result is:
{
"produts":[
{
"productId": 123,
"name": "Dubai"
},
{
"productId": 234,
"name": "India"
}
]
}
I tried with this query:
db.property.aggregate([
{
$match: {
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z")
}
},
{
"$project": {
"_id": 0,
"unique": {
"$filter": {
"input": {
"$setDifference": [
{
"$concatArrays": [
"$HList.productId",
"$PList.productId",
"$CList.productId"
]
},
[]
]
},
"cond": {
"$ne": [ "$$this", "" ]
}
}
}
}
}
]);
Is $setDifference aggregation is correct choice here?
My query returns only unique product ids but i need a productId with name.
Could someone help me to solve this?
Thanks in advance
You can use $projectfirst to get rid of tsh field and then run $setUnion which ignores duplicated entries:
db.collection.aggregate([
{
$project: {
"HList.tsh": 0,
"PList.tsh": 0,
"CList.tsh": 0,
}
},
{
$project: {
products: {
$setUnion: [ "$HList", "$PList", "$CList" ]
}
}
}
])
Mongo Playground
The following two aggregations return the expected and same result (you can use any of the two):
db.collection.aggregate( [
{
$project: {
_id: 0,
products: {
$reduce: {
input: { $setUnion: [ "$HList", "$PList", "$CList" ] },
initialValue: [],
in: {
$setUnion: [ "$$value", [ { productId: "$$this.productId", name: "$$this.name" } ] ]
}
}
}
}
}
] )
This one is little verbose:
db.collection.aggregate( [
{
$project: { list: { $setUnion: [ "$HList", "$PList", "$CList" ] } }
},
{
$unwind: "$list"
},
{
$group: {
_id: null,
products: { $addToSet: { "productId": "$list.productId", "name": "$list.name" } }
}
},
{
$project: { _id: 0 }
}
] )
db.collection.aggregate([
{
$match: {
"propertyId": 1001820437,
"date": ISODate("2020-07-17T00:00:00.000Z")
}
},
{
$project: {
products: {
$filter: {
input: { "$setUnion" : ["$CList", "$HList", "$PList"] },
as: 'product',
cond: {}
}
}
}
},
{
$project: {
"_id":0,
"products.tsh": 1,
"products.name": 1,
}
},
])

Count Both Outer and Inner embedded array in a single query

{
_id: ObjectId("5dbdacc28cffef0b94580dbd"),
"comments" : [
{
"_id" : ObjectId("5dbdacc78cffef0b94580dbf"),
"replies" : [
{
"_id" : ObjectId("5dbdacd78cffef0b94580dc0")
},
]
},
]
}
How to count the number of element in comments and sum with number of relies
My approach is do 2 query like this:
1. total elements of replies
db.posts.aggregate([
{$match: {_id:ObjectId("5dbdacc28cffef0b94580dbd")}},
{ $unwind: "$comments",},
{$project:{total:{$size:"$comments.replies"} , _id: 0} }
])
2. count total elements of comments
db.posts.aggregate([
{$match: {_id:ObjectId("5dbdacc28cffef0b94580dbd")}},
{$project:{total:{$size:"$comments.replies"} , _id: 0} }
])
Then sum up both, do we have any better solution to write the query like return the sum of of total element comments + replies
You can use $reduce and $concatArrays to "merge" an inner "array of arrays" into a single list and measure the $size of that. Then simply $add the two results together:
db.posts.aggregate([
{ "$match": { _id:ObjectId("5dbdacc28cffef0b94580dbd") } },
{ "$addFields": {
"totalBoth": {
"$add": [
{ "$size": "$comments" },
{ "$size": {
"$reduce": {
"input": "$comments.replies",
"initialValue": [],
"in": {
"$concatArrays": [ "$$value", "$$this" ]
}
}
}}
]
}
}}
])
Noting that an "array of arrays" is the effect of an expression like $comments.replies, so hence the operation to make these into a single array where you can measure all elements.
Try using the $unwind to flatten the list you get from the $project before using $count.
This is another way of getting the result.
Input documents:
{ "_id" : 1, "array1" : [ { "array2" : [ { id: "This is a test!"}, { id: "test1" } ] }, { "array2" : [ { id: "This is 2222!"}, { id: "test 222" }, { id: "222222" } ] } ] }
{ "_id" : 2, "array1" : [ { "array2" : [ { id: "aaaa" }, { id: "bbbb" } ] } ] }
The query:
db.arrsizes2.aggregate( [
{ $facet: {
array1Sizes: [
{ $project: { array1Size: { $size: "$array1" } } }
],
array2Sizes: [
{ $unwind: "$array1" },
{ $project: { array2Size: { $size: "$array1.array2" } } },
],
} },
{ $project: { result: { $concatArrays: [ "$array1Sizes", "$array2Sizes" ] } } },
{ $unwind: "$result" },
{ $group: { _id: "$result._id", total1: { $sum: "$result.array1Size" }, total2: { $sum: "$result.array2Size" } } },
{ $addFields: { total: { $add: [ "$total1", "$total2" ] } } },
] )
The output:
{ "_id" : 2, "total1" : 1, "total2" : 2, "total" : 3 }
{ "_id" : 1, "total1" : 2, "total2" : 5, "total" : 7 }

Mongo $addField For nested structured data

I have a result like below, Is there any way I can change the _id value which is the same for the all nested data. I want to rename each id with some other name not simply _id
{
"items": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a3da2901d60f9edc4d96",
"options": [
{
"_id": "5bd1a3da2901d60f9edc4d98",
}
],
}
]
}
]
}
Here in the above code, I tried $addfields attribute but it works fine only for the topmost nesting
Here is the code I've tried:
Items.aggregate([
{
$match: { _id: '123'}
},
{ $addFields: { item_id: "$_id" ,
"addons.addon_id" : "$addons._id" ,
"addons.options.options_id" : "$addons.options._id"
}
},
{
$project: {
_id: 0
}
},
],
This code works fine and added item_id in the root element but added addon and option_id as an array value,
{
"item_list": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a44b2901d60f9edc4d9a",
"options": [
{
"_id": "5bd1a44b2901d60f9edc4d9b",
"options_id": [
[
"5bd1a44b2901d60f9edc4d9b"
]
]
}
],
"addon_id": [
"5bd1a44b2901d60f9edc4d9a"
]
}
]
}
]
}
I got something like this a nested array of addon_id and options_id, I want it to be a simple string and not array,
{
"item_list": [
{
"_id": "5bd1a3da2901d60f9edc4d10",
"addons": [
{
"_id": "5bd1a44b2901d60f9edc4d9a",
"options": [
{
"_id": "5bd1a44b2901d60f9edc4d9b",
"options_id": "5bd1a44b2901d60f9edc4d9b"
}
],
"addon_id": "5bd1a44b2901d60f9edc4d9a"
}
]
}
]
}
Am expecting the above result.