How to aggregate list in MongoDB? - mongodb

I have a set of containers each with a set of items. The MongoDB document for a container looks like below. How do I list all the items in all the containers with the container name in each item?
{
"Container": 28392
...
"Items": [
{
"ItemName": "Foo",
...
},
{
"ItemName": "Bar",
...
}
]
}
Expected output:
[
{
"ItemName": "Foo",
"Container": 28392
...
},
{
"ItemName": "Bar",
"Container": 28392
...
},
{
"ItemName": "Baz",
"Container": 52892
...
}
]
Would this be possible with some kind of an unwind? If so how would I aggregate this?

After unwind you can use project stage to do what you want:
db.collection.aggregate([
{
$unwind: "$Items"
},
{
$project: {
Container: "$Container",
ItemName: "$Items.ItemName"
}
}
])
Playground:
https://mongoplayground.net/p/H-aWx07pCDt
Or if your mongodb version is equal or greater than 4.2, you can use replaceWith with mergeObjects, to get all fields in items array.
db.collection.aggregate([
{
$unwind: "$Items"
},
{
$replaceWith: {
$mergeObjects: [
{
Container: "$Container"
},
"$Items"
]
}
}
])
Playground:
https://mongoplayground.net/p/E5KqFlNq1CL
Input:
[
{
"Container": 28392,
"Items": [
{
"ItemName": "Foo",
"ItemDesc": "Foo desc"
},
{
"ItemName": "Bar",
"ItemDesc": "Bar desc"
}
]
}
]
Output:
[
{
"Container": 28392,
"ItemDesc": "Foo desc",
"ItemName": "Foo"
},
{
"Container": 28392,
"ItemDesc": "Bar desc",
"ItemName": "Bar"
}
]

Are you looking for a query like this?
db.collection.aggregate([
{
"$unwind": "$Items"
},
{
"$group": {
"_id": "$Container",
"All Items": {
"$push": {
"ItemName": "$Items.ItemName",
"Container": "$Container"
}
}
}
},
{
"$project": {
"_id": 0
}
}
])
Here's a link to a demo of the query - https://mongoplayground.net/p/QDxr-2VUa_f

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

Upadating documents not possible due to lack of atomic operators?

I am trying to update my collection through mongodb shell but unsuscessfully due to this error:
"Update document requires atomic operators"
db.dogs_main_breed.updateMany([
{},
{
$unwind: "$Behavior"
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
"$Behavior"
]
}
}
},
{
$project: {
Behavior: 0
}
}
])
How could be possible to update my documents given this query?
My goal is to flatten "Behavior" field and include it in Root directory of my document:
"breed": "Akita",
"origin": "Japan",
"url": "https://en.wikipedia.org/wiki/Akita_(dog)",
"img": "some img url",
"WikiDescr": [
{
"contenido": "Content"
}
],
"Behavior": [
{
"good_with_children": 3,
"good_with_other_dogs": 1,
}
Desired output:
"breed": "Akita",
"origin": "Japan",
"url": "https://en.wikipedia.org/wiki/Akita_(dog)",
"img": "some img url",
"good_with_children": 3,
"good_with_other_dogs": 1,
"WikiDescr": [
{
"contenido": "Content"
}
],
Can you try out this query?
db.dogs_main_breed.aggregate([
{
'$unwind': '$Behavior'
}, {
'$replaceRoot': {
'newRoot': {
'$mergeObjects': [
'$$ROOT', '$Behavior'
]
}
}
}, {
'$project': {
'Behavior': 0
}
}, {
'$out': 'dogs_main_breed'
}
])
Here the link of Mongo Playground to try out the query

Sort records by array field values in MongoDb

I have a collection which has documents like;
{
"name": "Subject1",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "100",
"valueKey": "ABC"
}
}, {
"_id": "security_score1",
"level": {
"value": "1000",
"valueKey": "CDE"
}
}
]
},
{
"name": "Subject2",
"attributes": [{
"_id": "security_level1",
"level": {
"value": "99",
"valueKey": "XYZ"
}
}, {
"_id": "security_score1",
"level": {
"value": "2000",
"valueKey": "EDF"
}
}
]
},
......
Each document will have so many attributes generated dynamically, can be different in size.
Is it possible to sort records based on level.value of security_level1? (security_level1 is _id field value)
As per above example, the second document ("name": "Subject2") should come first as the value ('level.value') of _id:security_level1 is 99, which is less than of Subject1's security_level1 value (100) - (Ascending order)
Use $filter and $arrayElemAt to get security_level1 item. Then you can use $toInt to convert that value to an integer so that $sort can be applied:
db.collection.aggregate([
{
$addFields: {
level: {
$let: {
vars: {
level_1: { $arrayElemAt: [ { $filter: { input: "$attributes", cond: { $eq: [ "$$this._id", "security_level1" ] } } } ,0] }
},
in: {
$toInt: "$$level_1.level.value"
}
}
}
}
},
{
$sort: {
level: 1
}
}
])
Mongo Playground

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

Aggregation on complex objects

I have a collection with documents like the following:
{
"towers": [
{
"name": "foo",
"towers": [
{
"name": "A",
"buildType": "Apartament"
},
{
"name": "B",
"buildType": "Apartament"
}
]
},
{
"name": "xpto",
"towers": [
{
"name": "C",
"buildType": "House"
},
{
"name": "D",
"buildType": "Office"
}
]
}
]
}
All I need to know is what are all the possible values for "buildType", like:
Apartment
House
Office
It's a complex object and the data to aggregate is deep inside it. Is there any way to achieve the results I want?
You need to $unwind the two nested array that is "towers" and "towers.towers" and then use $group with "towers.towers.buildType" field to get the distinct values
db.collection.aggregate([
{ "$unwind": "$towers" },
{ "$unwind": "$towers.towers" },
{ "$group": {
"_id": "$towers.towers.buildType"
}}
])
Output
[
{
"_id": "Office"
},
{
"_id": "House"
},
{
"_id": "Apartament"
}
]
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$towers",
}
},
// Stage 2
{
$unwind: {
path: "$towers.towers",
}
},
// Stage 3
{
$group: {
_id: '$_id',
buildType: {
$addToSet: '$towers.towers.buildType'
}
}
},
]
);