Extract a value from an object in array matching a condition - mongodb

I need help to make an aggregation pipeline in mongodb.
The mongodb version i'm using is 4.
The documents stored in database looks like this:
[{
_id : "xxxxxx",
names : [
{ "lang" : "EN", value : "foo" },
{ "lang" : "IT", value : "bar" },
{ "lang" : "NOLANG", value : "baz" }
],
some : "value"
},{
_id : "yyyyyy",
names : [
{ "lang" : "FR", value : "quux" },
{ "lang" : "IT", value : "quuux" },
{ "lang" : "NOLANG", value : "quuuux" }
],
some : "value"
}]
I need to add a field with aggregation that contains the value of a certain language (for this example i'll take "EN"), if no element with requested language is found i need to get the "NOLANG" object value.
So, the result of the aggregation must looks like:
[{
_id : "xxxxxx",
name : "foo",
some : "value"
},{
_id : "yyyyyy",
name : "quuuux",
some : "value"
}]
This is the pipeline i wrote:
[
{
$project : {
names : 0,
name: {
$filter: {
input: '$names',
as: 'name',
cond: {
$switch: {
$branches: [
{
case : {
$eq : ["$$name.lang", "EN"]
},
then : "$$name.value"
} ,{
case : {
$eq : ["$$name.lang", "NOLANG"]
},
then : "$$name.value"
}
],
default : ''
}
}
}
}
}
}
]
It gives me the error: Expected "[" or AggregationStage but "{" found.
What i'm doing wrong? Someone can help me please?
Thanks

You can use below aggregation
db.collection.aggregate([
{ "$project": {
"some": 1,
"name": {
"$arrayElemAt": [
"$names.value",
{
"$cond": [
{
"$ne": [
{ "$indexOfArray": ["$names.lang", "EN"] },
-1
]
},
{ "$indexOfArray": ["$names.lang", "EN"] },
{ "$indexOfArray": ["$names.lang", "NOLANG"] }
]
}
]
}
}}
])
Output
[
{
"_id": "xxxxxx",
"name": "baz",
"some": "value"
},
{
"_id": "yyyyyy",
"name": "quuuux",
"some": "value"
}
]

Related

how to sort an array in a nested array which is located under an object in mongodb

I have a collection data like below.
{
"name": "Devices",
"exten": {
"parameters": [{
"name": "Date",
"value": ["5","2"]
}, {
"name": "Time",
"value": ["2"]
}, {
"name": "Season",
"value": ["6"]
}
]
}
}
I want to take all data which is name "Devices" and sort by first index of "Value" which is parameter name is "Date"
ex: mongo will get
name = "devices"
exten.parameters.name = "Date"
will sort it by
exten.parameters.value[0]
in this example it will be sorted by "5".
below query returns 0 record.
db.brand.aggregate(
{ $match: {
"name" : "Devices"
}},
{ $unwind: "$exten.parameters" },
{ $match: {
'exten.parameters.name': 'Date'
}},
{ $sort: {
'exten.parameters.value': -1
}}
)
The following query can get us the expected output:
db.collection.aggregate([
{
$match:{
"name":"Devices"
}
},
{
$unwind:"$exten.parameters"
},
{
$match:{
"exten.parameters.name":"Date"
}
},
{
$project:{
"name":1,
"exten":1,
"firstParam":{
$arrayElemAt:["$exten.parameters.value",0]
}
}
},
{
$sort:{
"firstParam":1
}
},
{
$project:{
"firstParam":0
}
}
]).pretty()
Data set:
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : [
{
"name" : "Date",
"value" : [
"5",
"2"
]
},
{
"name" : "Date",
"value" : [
"2",
"7"
]
},
{
"name" : "Time",
"value" : [
"2"
]
},
{
"name" : "Season",
"value" : [
"6"
]
}
]
}
}
Output:
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : {
"name" : "Date",
"value" : [
"2",
"7"
]
}
}
}
{
"_id" : ObjectId("5da02fb86472ba670fd8c159"),
"name" : "Devices",
"exten" : {
"parameters" : {
"name" : "Date",
"value" : [
"5",
"2"
]
}
}
}

Updating single object in array in mongoDB

My collection is in below format,
{
"_id" : ObjectId("5d01fddd3f21c407582e578d"),
"device_name" : "Test Alarm Device",
"description" : "test",
"parameters" : [
{
"parameter_name" : "CO2",
"status" : "on"
},
{
"parameter_name" : "NO",
"status" : "on"
},
{
"parameter_name" : "O2",
"status" : "on"
}
}
Now I wish to overwrite(update) complete object where "parameter_name" : "O2".
For example, in new collection it will look like...
{
"parameter_name" : "O2",
"status" : "on",
"is_active": true,
"min": 5
}
Use $out to replace existing collection, $addFields to overwrite existing array and $map with $cond to update elements based on some criteria. To build a new object based on existing one you can use $mergeObjects, try:
db.collection.aggregate([
{
$addFields: {
parameters: {
$map: {
input: "$parameters",
in: {
$cond: [
{ "$eq": [ "$$this.parameter_name", "O2" ] },
{ $mergeObjects: [ "$$this", { "is_active": true, "min": 5 } ] },
"$$this"
]
}
}
}
}
},
{ $out: "collection" }
])
This will do, but I don't think it is the best solution:
db.collection.updateOne({
_id: ObjectId("5d01fddd3f21c407582e578d"),
{ $set: { "parameters" : [
{
"parameter_name" : "CO2",
"status" : "on"
},
{
"parameter_name" : "NO",
"status" : "on"
},
{
"parameter_name" : "O2",
"status" : "on",
"is_active": true,
"min": 5
}
]
}
}
)

Issue retrieving subdocuments from MongoDB

I have the following dataset:
{
"_id" : ObjectId("59668a22734d1d48cf34de08"),
"name" : "Nobody Cares",
"menus" : [
{
"_id" : "menu_123",
"name" : "Weekend Menu",
"description" : "A menu for the weekend",
"groups" : [
{
"name" : "Spirits",
"has_mixers" : true,
"sizes" : [
"Single",
"Double"
],
"categories" : [
{
"name" : "Vodka",
"description" : "Maybe not necessary?",
"drinks" : [
{
"_id" : "drink_123",
"name" : "Absolut",
"description" : "Fancy ass vodka",
"sizes" : [
{
"_id" : "size_123",
"size" : "Single",
"price" : 300
}
]
}
]
}
]
}
],
"mixers" : [
{
"_id" : "mixer_1",
"name" : "Coca Cola",
"price" : 150
},
{
"_id" : "mixer_2",
"name" : "Lemonade",
"price" : 120
}
]
}
]
}
And I'm attempting to retrieve a single drink from that dataset, I'm using the following aggregate query:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $unwind: "$menus" },
{ $project: { "_id": 1, "menus": { "groups": { "categories": { "drinks": { "name": 1 } } } } } }
])
However, it's returning the full structure of the dataset along with the correct data.
So instead of:
{
"_id": "drink_123",
"name": "Absolut"
}
I get:
{
"_id": ObjectId("59668a22734d1d48cf34de08"),
"menus": {
"groups": {
"categories": {
"drinks": { "name": "Absolut" }
}
}
}
}
For example. Any ideas how to just retrieve the subdocument?
If you need to retain the deeply nested model then this call will produce the desired output:
db.getCollection('places').aggregate([
{ $match : {"menus.groups.categories.drinks._id" : "drink_123"} },
{ $project: {"_id": '$menus.groups.categories.drinks._id', name: '$menus.groups.categories.drinks.name'}},
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$name" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" },
{ $unwind: "$_id" }
])
The numerous unwinds are the result of the deep nesting of the drinks subdocuments.
Though, FWIW, this sort of query does perhaps suggest that the model isn't 'read friendly'.

count documents by specific nested fields values with aggregation-framework in MongoDB

Here's my db.
{
"_id" : ObjectId("579cab6c6aba30f42a57a979"),
"iecode" : "P1111",
"country" : "India",
"totalTreatmentArms" : 3,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism2"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : "mechanism1"
}
]
}
{
"_id" : ObjectId("579cab7a6aba30f42a57a97a"),
"iecode" : "P1111",
"country" : "Canada",
"totalTreatmentArms" : 3,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism2"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
{
"_id" : ObjectId("579cabac6aba30f42a57a97b"),
"iecode" : "P2222",
"country" : "India",
"totalTreatmentArms" : 1,
"treatmentArms" : [
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
{
"_id" : ObjectId("579cabe76aba30f42a57a97c"),
"iecode" : "P3333",
"country" : "India",
"totalTreatmentArms" : 2,
"treatmentArms" : [
{
"mechanismOrPkg" : "Mechanism",
"mechanism" : "mechanism1"
},
{
"mechanismOrPkg" : "Package",
"mechanism" : ""
}
]
}
Please note that there are two records with iecode : P1111 and I want result distinguished on iecode so only one record will be considered(any one).
Now my requirement is I want count on field mechanismOrPkg. If it contains value Package then we will increment package with one. If the value is Mechanism then we will consider value of field mechanism and its respected values count will be considered.
So final result will be like this
{
"_id" : null,
"totalPackage" : 3,
"totalMechanism1" : 2,
"totalMechanism2" : 1
}
Please ask if it sounds confusing. Let me know even if this kind of aggregation is possible with query or I have to do server side filtering?
Thanks.
Edit
Possible values of mechanismOrPkg : Package or Mechanism
Possible values of mechanism : mechanism1 or mechanism2
Accomplishing the above would require using the $cond operator in the $sum accumulator operator. The $cond operator will evaluate a logical condition based on its first argument (if) and then returns the second argument where the evaluation is true (then) or the third argument where false (else). This converts the true/false logic into 1 and 0 numerical values that feed into $sum respectively. So for instance, when you want to aggregate the count for the "Package" value, the logic follows:
{
"$sum": {
"$cond": [ { "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] }, 1, 0 ]
}
}
As a resulting pipeline, you need to run the aggregation operation
db.collection.aggregate([
{
"$group": {
"_id": "$iecode",
"treatmentArms": { "$first": "$treatmentArms" }
}
},
{ "$unwind": "$treatmentArms" },
{
"$group": {
"_id": null,
"totalPackage": {
"$sum": {
"$cond": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Package" ] },
1, 0
]
}
},
"totalMechanism1":{
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanism", "mechanism1" ] }
]
},
1,
0 ]
}
},
"totalMechanism2": {
"$sum": {
"$cond": [
{
"$and": [
{ "$eq": [ "$treatmentArms.mechanismOrPkg", "Mechanism" ] },
{ "$eq": [ "$treatmentArms.mechanism", "mechanism2" ] }
]
},
1,
0 ]
}
}
}
}
])
Sample Output
{
"_id" : null,
"totalPackage" : 3,
"totalMechanism1" : 2,
"totalMechanism2" : 1
}

Mongodb output field with multiple $cond

Here's an example of documents I use :
{
"_id" : ObjectId("554a1f5fe36a768b362ea5c0"),
"store_state" : 1,
"services" : [
{
"id" : "XXX",
"state" : 1,
"active": true
},
{
"id" : "YYY",
"state" : 1,
"active": true
},
...
]
}
I want to output a new field with "Y" if the id is "XXX" and active is true and "N" in any other cases. The service element with "XXX" as id is not present on every documents (output "N" in this case).
Here's my query for the moment :
db.stores.aggregate({
$match : {"store_state":1}
},
{ $project : {
"XXX_active": {
$cond: [ {
$and:[
{$eq:["services.$id","XXX"]},
{$eq:["services.$active",true]}
]},"Y","N"
] }
}
}).pretty()
But it always output "N" for "XXX_active" field.
The expected output I need is :
{
"_id" : ObjectId("554a1f5de36a768b362e7e6f"),
"XXX_active" : "Y"
},
{
"_id" : ObjectId("554a1f5ee36a768b362e9d25"),
"XXX_active" : "N"
},
{
"_id" : ObjectId("554a1f5de36a768b362e73a5"),
"XXX_active" : "Y"
}
Other example of possible result :
{
"_id" : ObjectId("554a1f5de36a768b362e7e6f"),
"XXX_active" : "Y",
"YYY_active" : "N"
},
{
"_id" : ObjectId("554a1f5ee36a768b362e9d25"),
"XXX_active" : "N",
"YYY_active" : "N"
},
{
"_id" : ObjectId("554a1f5de36a768b362e73a5"),
"XXX_active" : "Y",
"YYY_active" : "Y"
}
Only one XXX_active per object and no duplicates objects but I need all objects with an XXX_active even if the services id element "XXX" is not present. Could someone help please?
First $unwind services array and then used $cond as below :
db.stores.aggregate({
"$match": {
"store_state": 1
}
}, {
"$unwind": "$services"
}, {
"$project": {
"XXX_active": {
"$cond": [{
"$and": [{
"$eq": ["$services.id", "XXX"]
}, {
"$eq": ["$services.active", true]
}]
}, "Y", "N"]
}
}
},{"$group":{"_id":"$_id","XXX_active":{"$first":"$XXX_active"}}}) //group by id
The following aggregation pipeline will give the desired result. You would need to first apply the $unwind operator on the services array field first as your initial aggregation pipeline step. This will deconstruct the services array field from the input documents to output a document for each element. Each output document replaces the array with an element value.
db.stores.aggregate([
{
"$match" : {"store_state": 1}
},
{
"$unwind": "$services"
},
{
"$project": {
"store_state" : 1,
"services": 1,
"XXX_active": {
"$cond": [
{
"$and": [
{"$eq":["$services.id", "XXX"]},
{"$eq":["$services.active",true]}
]
},"Y","N"
]
}
}
},
{
"$match": {
"services.id": "XXX"
}
},
{
"$group": {
"_id": {
"_id": "$_id",
"store_state": "$store_state",
"XXX_active": "$XXX_active"
},
"services": {
"$push": "$services"
}
}
},
{
"$project": {
"_id": "$_id._id",
"store_state" : "$_id.store_state",
"services": 1,
"XXX_active": "$_id.XXX_active"
}
}
])