mongo aggregate - group - show number fo childrens - mongodb

This is how my documents looks like:
[
{
"_id": {
"$oid": "633a982186c443b693dc240c"
},
"date": "2022-09-27",
"file": "file_1",
"package": 1,
"packagecode": "xy/1",
"pshIdList": [],
"userList": [
{
"userName": "user_1",
"crDate": "2022.09.28",
"boolId": 1
}
]
},
{
"_id": {
"$oid": "633a982186c443b693dc240d"
},
"date": "2022-09-27",
"file": "file_2",
"package": 2,
"packagecode": "xy/2",
"pshIdList": [],
"userList": []
}
]
Because of the appearance of the frontend (i have to display a tree structure) i need to group by the documents this way:
db.collection.aggregate([
{
$set: {
"username": {
$filter: {
input: "$userList",
as: "user",
cond: {
"$eq": [
"$$user.boolId",
1
]
}
}
}
}
},
{
$set: {
"username": {
$arrayElemAt: [
"$username",
0
]
}
}
},
{
$set: {
"username": {
$ifNull: [
"$username.userName",
"na"
]
}
}
},
{
$group: {
_id: {
"date": {
$concat: [
"date: ",
"$date",
]
},
"file": {
$concat: [
"file: ",
"$file"
]
},
},
"items": {
$push: {
"items": {
$concat: [
"$packagecode",
" - ",
"$username"
]
}
}
}
}
},
{
$group: {
_id: "$_id.date",
"items": {
$push: {
"file": "$_id.file",
"items": "$items"
}
}
}
}
])
This is the result of the aggregate:
[
{
"_id": "date: 2022-09-27",
"items": [
{
"file": "file: file_1",
"items": [
{
"items": "xy/1 - user_1"
}
]
},
{
"file": "file: file_2",
"items": [
{
"items": "xy/2 - na"
}
]
}
]
}
]
Also need to display the number of childrens of each level, so the output should look like this:
[
{
"_id": "date: 2022-09-27 - 2",
"items": [
{
"file": "file: file_1 - 1",
"items": [
{
"items": "xy/1 - user_1"
}
]
},
{
"file": "file: file_2 - 1",
"items": [
{
"items": "xy/2 - na"
}
]
}
]
}
]
I have no clue how to solve it. I was trying with $set operator, but cannot use group inside that. This is an example playground

You can use $map, to loop over the items array and append the number of children in file, like this:
db.collection.aggregate([
{
$set: {
"username": {
$filter: {
input: "$userList",
as: "user",
cond: {
"$eq": [
"$$user.boolId",
1
]
}
}
}
}
},
{
$set: {
"username": {
$arrayElemAt: [
"$username",
0
]
}
}
},
{
$set: {
"username": {
$ifNull: [
"$username.userName",
"na"
]
}
}
},
{
$group: {
_id: {
"date": {
$concat: [
"date: ",
"$date",
]
},
"file": {
$concat: [
"file: ",
"$file"
]
},
},
"items": {
$push: {
"items": {
$concat: [
"$packagecode",
" - ",
"$username"
]
}
}
}
}
},
{
$group: {
_id: "$_id.date",
"items": {
$push: {
"file": "$_id.file",
"items": "$items"
}
}
}
},
{
"$set": {
"items": {
"$map": {
"input": "$items",
"as": "element",
"in": {
items: "$$element.items",
file: {
"$concat": [
"$$element.file",
" - ",
{
"$toString": {
"$size": "$$element.items"
}
}
]
}
}
}
},
_id: {
"$concat": [
"$_id",
" - ",
{
"$toString": {
"$sum": {
"$map": {
"input": "$items",
"as": "element",
"in": {
"$size": "$$element.items"
}
}
}
}
}
]
}
}
}
])
Playground link.

Related

mongoDB $concat string with array item

This is my collection:
[
{
"_id": {
"$oid": "6332dda58121948311cbdb67"
},
"date": "2022-09-13",
"file": "xxx",
"package": 1,
"userList": [
{
"userName": "user_1",
"crDate": "2022.09.28",
"boolId": 1
}
]
},
{
"_id": {
"$oid": "6332dda58121948311cbdb68"
},
"date": "2022-09-13",
"file": "xxx",
"package": 2,
"userList": []
}
]
My desired output would be this (if all of the userList array is empty):
[
{
"_id": "2022-09-13",
"items": [
{
"fileName": "xxx",
"items": [
{
"package": "1 - na"
},
{
"package": "2 - na"
}
]
}
]
}
]
And this if I would have an object inside the userList array:
[
{
"_id": "2022-09-13",
"items": [
{
"fileName": "xxx",
"items": [
{
"package": "1 - user_1"
},
{
"package": "2 - na"
}
]
}
]
}
]
I try to run this aggregate:
db.collection.aggregate([
{
$group: {
_id: {
"date": "$date",
"file": "$file",
},
"items": {
$push: {
"package": {
$concat: [
{
$toString: "package"
},
" - ",
{
$toString: {
$arrayElemAt: [
"$userList",
0
]
}
}
]
}
}
}
}
},
{
$group: {
_id: "$_id.date",
"items": {
$push: {
"fileName": "$_id.file",
"items": "$items"
}
}
},
},
])
It's running if the userList array is empty, but it's not returning the desired output and if the userList array is not empty it throws this error:
Mongo Server error (MongoCommandException): Command failed with error 241 (ConversionFailure): 'Unsupported conversion from object to string in $convert with no onError value' on server
Here comes an example: mongo_playground
How should I modify the aggregate?
It requires 2 fixes in the first $group stage,
missed the $ sign in package property name, in { $toString: "$package" }
use property name userName while accessing first element in $userList.userName
use $ifNull operator to check if the property is not present then it returns "na" string
db.collection.aggregate([
{
$group: {
_id: {
"date": "$date",
"file": "$file"
},
"items": {
$push: {
"package": {
$concat: [
{ $toString: "$package" }, // <====== here
" - ",
{
$toString: {
$ifNull: [
{ $arrayElemAt: ["$userList.userName", 0] }, // <====== here
"na"
]
}
}
]
}
}
}
}
},
{
$group: {
_id: "$_id.date",
"items": {
$push: {
"fileName": "$_id.file",
"items": "$items"
}
}
}
}
])
Playground

MongoDB query inside an array

I want to use this mongoDB collection:
[
{
"_id": {
"$oid": "627c4eb87e7c2b8ba510ac4c"
},
"Contact": [
{
"name": "ABC",
"phone": 5501234,
"mail": "abc#mail.com"
},
{
"name": "DEF",
"phone": 6001234,
"mail": "def#mail.com"
}
],
"nomatter": "trash"
}
]
search for {"name":"ABC"} and return only {"mail":"abc#mail.com"}.
It's possible to use find or it's necessary to use aggregate?
Try this one:
db.collection.aggregate([
{ $match: { "Contact.name": "ABC" } },
{
$project: {
Contact: {
$filter: {
input: "$Contact",
cond: { $eq: [ "$$this.name", "ABC" ] }
}
}
}
},
{ "$replaceWith": { mail: { $first: "$Contact.mail" } } }
])
Mongo Playground

How to remove obejct from multi level array in nodejs using mongo?

{
"_id" : ObjectId("52f504bb2f9dd91186211537"),
"Data": {
"Stage": {
"FirstArray": [
{
"Name": "FirstLevelArray-FirstObject",
"_id": ObjectId("5fe1a5fa2d8e360ac4093b7e"),
"SecondArray": [
{
"Name": "1-SecondLevelArray-FirstObject",
"_id": ObjectId("5fe1a7a52d8e360ac4093b81")
},
{
"Name": "1-SecondLevelArray-SecondObject",
"_id": ObjectId("5fe1a7a52d8e360ac4093b82")
}
]
},
{
"Name": "FirstLevelArray-SecondObject",
"_id": ObjectId("5fdc9dced45fa417d417c441"),
"SecondArray": [
{
"Name": "2-SecondLevelArray-FirstObject",
"_id": ObjectId("5fde08564d28f313acc0c93b")
},
{
"Name": "2-SecondLevelArray-SecondObject",
"_id": ObjectId("5fde08d64d28f313acc0c93c")
}
]
}
]
}
}
}
This is the sample format of my code.
I want to delete this object { "Name": "2-SecondLevelArray-SecondObject", "_id": ObjectId("5fde08d64d28f313acc0c93c") } from this record.
I tried this query
model.update(
{ $and: [{ "_id": ObjectId("52f504bb2f9dd91186211537") }},
{"Data.Stage.FirstArray.SecondArray._id":ObjectId("5fde08d64d28f313acc0c93c")}] ,
{ $pull:{
"Data.Stage.FirstArray.$.SecondArray._id": ObjectId("5fe1a7a52d8e360ac4093b82")
}
},
{new:true,upsert:false})
How would I achieve this in MongoDB ?
Here is the expected result of find({"_id" : ObjectId("52f504bb2f9dd91186211537")}) after the update
EDIT: {
"_id" : ObjectId("52f504bb2f9dd91186211537"),
"Data": {
"Stage": {
"FirstArray": [
{
"Name": "FirstLevelArray-FirstObject",
"_id": ObjectId("5fe1a5fa2d8e360ac4093b7e"),
"SecondArray": [
{
"Name": "1-SecondLevelArray-FirstObject",
"_id": ObjectId("5fe1a7a52d8e360ac4093b81")
},
{
"Name": "1-SecondLevelArray-SecondObject",
"_id": ObjectId("5fe1a7a52d8e360ac4093b82")
}
]
},
{
"Name": "FirstLevelArray-SecondObject",
"_id": ObjectId("5fdc9dced45fa417d417c441"),
"SecondArray": [
{
"Name": "2-SecondLevelArray-FirstObject",
"_id": ObjectId("5fde08564d28f313acc0c93b")
}
]
}
]
}
}
}
model.update({ _id: ObjectId("52f504bb2f9dd91186211537"), Data.Stage.FirstArray:{ $elemMatch: { SecondArray:{$elemMatch:{"_id":ObjectId("5fde08d64d28f313acc0c93c")}}}}},
{ $pull:{ "Data.Stage.FirstArray.$.SecondArray":{"_id": ObjectId("5fde08d64d28f313acc0c93c") }}},{new:true,upsert:false})
My idea is to filter out those with your given Name field
model.updateMany({}, {
$set: { "Data.Stage.FirstArray.SecondArray": { $filter: {
input: "$Data.Stage.FirstArray.SecondArray",
as: "item",
cond: { $eq: [ "$$item.Name", '2-SecondLevelArray-SecondObject' ] }
} } },
});
Honestly, I'm not sure it will be working but it worths a try.

How to combine mongodb original output of query with some new fields?

Collection:
[
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
My query:
db.collection.aggregate([
{
"$addFields": {
"arrayofkeyvalue": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$arrayofkeyvalue"
},
{
"$group": {
"_id": null,
"allkeys": {
"$addToSet": "$arrayofkeyvalue.k"
}
}
}
])
The output currently:
[
{
"_id": null,
"allkeys": [
"additional",
"_id",
"para",
"type",
"name"
]
}
]
Detail see Playground
What I want to do is add a new column which includes all of top key of the mongodb query output, exclude "para". And then combine it with the old collection to form a new json.
Is it possible?
The expected result:
{
"column": [{"prop": "name"}, {"prop": "type"}, {"prop": "additional"}],
"columnData": [
{
"name": "device1",
"type": "a",
"para": {
"number": 3
}
},
{
"name": "device2",
"type": "b",
"additional": "c",
"para": {
"number": 1
}
}
]
}
You have the right general idea in mind, here's how I would do it by utilizing operators like $filter, $map and $reduce to manipulate the objects structure.
I separated the aggregation into 3 parts for readability but you can just merge stage 2 and 3 if you wish.
db.collection.aggregate([
{
"$group": {
"_id": null,
columnData: {
$push: "$$ROOT"
},
"keys": {
"$push": {
$map: {
input: {
"$objectToArray": "$$ROOT"
},
as: "field",
in: "$$field.k"
}
}
}
}
},
{
"$addFields": {
unionedKeys: {
$filter: {
input: {
$reduce: {
input: "$keys",
initialValue: [],
in: {
"$setUnion": [
"$$this",
"$$value"
]
}
}
},
as: "item",
cond: {
$not: {
"$setIsSubset": [
[
"$$item"
],
[
"_id",
"para"
]
]
}
}
}
}
}
},
{
$project: {
_id: 0,
columnData: 1,
column: {
$map: {
input: "$unionedKeys",
as: "key",
in: {
prop: "$$key"
}
}
}
}
}
])
Mongo Playground

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