Mongo aggregate to exclude objects from array - mongodb

My sample document
{ "pId":12345, "charges": [
{
"type": "asr",
"dId": 123,
"value": 100
},
{
"type": "asr",
"dId": 124,
"value": 120
},
{
"type": "asp",
"dId": 125,
"value": 130
},
{
"type": "asn",
"dId": 126,
"value": 130
},
{
"type": "aso",
"dId": 127,
"value": 150
}....
] }
Excluded charges input:
charges [
{
"type": "asr",
"dId": 123
},
{
"type": "asr",
"dId": 124
} ...
]
I need to fetch all charges from the sample document except Excluded charges. Can someone help me to solve this?
I tried this
{}
{"$project" :{
"_id" : 0, "pId" : 1,
"charges": { "$filter" : { "input" : "$charges", "as" : "charge",
"cond" :{
{ "$not" : { "$and" : [{ "$eq" : ["$$charge.type", "asr"]}, { "$eq" : ["$$charge.dId", 123]}]}}
}
}}
When I have multiple excluded charges how can we do this

use this :
[
{
'$project': {
'charges': {
'$map': {
'input': {
'$filter': {
'input': '$charges',
'as': 'featuresT',
'cond': {
'$eq': [
{
'$or': [
{
'$and': [
{
'$eq': [
'$$featuresT.type', 'asr'
]
}, {
'$eq': [
'$$featuresT.dId', 123
]
}
]
}, {
'$and': [
{
'$eq': [
'$$featuresT.type', 'asr'
]
}, {
'$eq': [
'$$featuresT.dId', 124
]
}
]
}
]
}, false
]
}
}
},
'as': 'featuresF',
'in': {
'type': '$$featuresF.type',
'dId': '$$featuresF.dId',
'value': '$$featuresF.value'
}
}
}
}
}
]

found a simple way.
db.collection.aggregate([
{
$match: {
"pId": {
$eq: 12345
}
}
},
{
"$project": {
"_id": 0,
"pId": 1,
"charges": {
"$filter": {
"input": "$charges",
"as": "charge",
"cond": {
"$not": {
"$or": [
{
"$and": [
{
"$eq": [
"$$charge.type",
"asr"
]
},
{
"$eq": [
"$$charge.dId",
123
]
}
]
},
{
"$and": [
{
"$eq": [
"$$charge.type",
"asr"
]
},
{
"$eq": [
"$$charge.dId",
124
]
}
]
}
]
}
}
}
}
}
}
])
mongoplayground

$filter to filter charges array
$in with $not to exclude only the values that you want
db.collection.aggregate([
{
"$project": {
"_id": 0,
"pId": 1,
"charges": {
"$filter": {
"input": "$charges",
"cond": {
"$not": {
"$in": [
"$$this.dId",
[123, 124]
]
}
}
}
}
}
}
])
Here is the working example: https://mongoplayground.net/p/0uIdoml384h

Related

Update more than one inner document in MongoDb

In my example project, I have employees under manager. Db schema is like this;
{
"employees": [
{
"name": "Adam",
"_id": "5ea36b27d7ae560845afb88e",
"bananas": "allowed"
},
{
"name": "Smith",
"_id": "5ea36b27d7ae560845afb88f",
"bananas": "not-allowed"
},
{
"name": "John",
"_id": "5ea36b27d7ae560845afb88g",
"bananas": "not-allowed"
},
{
"name": "Patrick",
"_id": "5ea36b27d7ae560845afb88h",
"bananas": "allowed"
}
]
}
In this case Adam is allowed to eat bananas and Smith is not. If I have to give the permission of eating bananas from Adam to Smith I need to perform update operation twice like this:
db.managers.update(
{ 'employees.name': 'Adam' },
{ $set: { 'employees.$.bananas': 'not-allowed' } }
);
and
db.managers.update(
{ 'employees.name': 'Smith' },
{ $set: { 'employees.$.bananas': 'allowed' } }
);
Is it possible to handle this in a single query?
You can use $map and $cond to perform conditional update to the array entries depending on the name of the employee. A $switch is used for potential extension of cases.
db.collection.update({},
[
{
"$set": {
"employees": {
"$map": {
"input": "$employees",
"as": "e",
"in": {
"$switch": {
"branches": [
{
"case": {
$eq: [
"$$e.name",
"Adam"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "not-allowed"
}
]
}
},
{
"case": {
$eq: [
"$$e.name",
"Smith"
]
},
"then": {
"$mergeObjects": [
"$$e",
{
"bananas": "allowed"
}
]
}
}
],
default: "$$e"
}
}
}
}
}
}
])
Mongo Playground
db.managers.update(
{
$or: [
{"employees.name": "Adam"},
{"employees.name": "Smith"}
]
},
{
$set: {
"employees.$[e].bananas": {
$cond: [{ $eq: ["$e.name", "Adam"] }, "not-allowed", "allowed"]
}
}
},
{
arrayFilters: [{ "e.name": { $in: ["Adam", "Smith"] } }]
}
)

Zip two array and create new array of object

hello all i'm working with a MongoDB database where each data row is like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
"userhasRate" : [
"51",
"52",
],
"ratings" : [
4,
3
],
}
and i need to change it to be like:
{
"_id" : ObjectId("5cf12696e81744d2dfc0000c"),
"contributor": "user1",
"title": "Title 1",
rate : [
{userhasrate: "51", value: 4},
{userhasrate: "52", value: 3},
]
}
I already try using this method,
db.getCollection('contens').aggregate([
{ '$group':{
'rates': {$push:{ value: '$ratings', user: '$userhasRate'}}
}
}
]);
and my result become like this
{
"rates" : [
{
"value" : [
5,
5,
5
],
"user" : [
"51",
"52",
"53"
]
}
]
}
Can someone help me to solve my problem,
Thank you
You can use $arrayToObject and $objectToArray inside $map to achieve the required output.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$objectToArray": {
"$arrayToObject": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
}
}
},
"as": "el",
"in": {
"userhasRate": "$$el.k",
"value": "$$el.v"
}
}
}
}
}
])
Alternative Method
If userhasRate contains repeated values then the first solution will not work. You can use arrayElemAt and $map along with $zip if it contains repeated values.
db.collection.aggregate([
{
"$project": {
"rate": {
"$map": {
"input": {
"$zip": {
"inputs": [
"$userhasRate",
"$ratings"
]
}
},
"as": "el",
"in": {
"userhasRate": {
"$arrayElemAt": [
"$$el",
0
]
},
"value": {
"$arrayElemAt": [
"$$el",
1
]
}
}
}
}
}
}
])
Try below aggregate, first of all you used group without _id that grouped all the JSONs in the collection instead set it to "$_id" also you need to create 2 arrays using old data then in next project pipeline concat the arrays to get desired output:
db.getCollection('contens').aggregate([
{
$group: {
_id: "$_id",
rate1: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
0
]
},
value: {
$arrayElemAt: [
"$ratings",
0
]
}
}
},
rate2: {
$push: {
userhasrate: {
$arrayElemAt: [
"$userhasRate",
1
]
},
value: {
$arrayElemAt: [
"$ratings",
1
]
}
}
}
}
},
{
$project: {
_id: 1,
rate: {
$concatArrays: [
"$rate1",
"$rate2"
]
}
}
}
])

MongoDB | get max value from nested object

db.getCollection('post').find({'post_no':47}, {'comment': 1})
The resulting values are:
{
"_id" : ObjectId("5bc05c038e798ccb0309658b"),
"comment" : [
{
"comment_no" : 112
},
{
"comment_no" : 113,
"comment_group" : 1
},
{
"comment_no" : 116,
"comment_group" : 2
},
{
"comment_no" : 117,
"comment_group" : 3
},
{
"comment_group" : 4,
"comment_no" : 118
}
]
}
I want to get the maximum value 4 of the comment_group.
What can I do?
Thank you for your advice.
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: {
path: "$comment",
}
},
// Stage 2
{
$sort: {
"comment.comment_group": -1
}
},
// Stage 3
{
$limit: 1
},
]
);
You can try below aggregation
db.collection.aggregate([
{ "$project": {
"comment": {
"$map": {
"input": "$comment",
"in": {
"comment_no": "$$this.comment_no",
"comment_group": { "$ifNull": [ "$$this.comment_group", 0 ] }
}
}
}
}},
{ "$project": {
"comment": {
"$arrayElemAt": [
"$comment",
{
"$indexOfArray": [
"$comment.comment_group",
{ "$max": "$comment.comment_group" }
]
}
]
}
}}
])
You can alse use $reduce in the second project in Anthony Winzlet's answer.
{
"$project": {
"comment": {
'$reduce': {
'input': '$comment',
'initialValue': {'$arrayElemAt': ['$comment', 0]},
'in': {
'$cond': {
'if': {'$gt': ['$$this.comment_group', '$$value.comment_group']},
'then': '$$this',
'else': '$$value'
}
}
}
}
}
}

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

MongoDB - Complex Grouping Query

I have the following MongoDB aggregation query which groups by IDC, type and cluster - which works perfectly.
I would like to additionally group "environment", inside this existing grouping. Please see my query below, my existing output, and what I would like to see (desired output).
If you have any questions or wish to see the source (I didn't think it was necessary, as it would take up room on the question, then please comment).
Thanks
Example Source (around 1000 documents):
{
"_id":"55d5dc40281077b6d8af1bfa",
"hostname":"1",
"domain":"domain",
"description":"VMWare ESXi 5",
"cluster":1,
"type":"Physical",
"os":"EXSi",
"idc":"AMS",
"environment":"DR",
"deviceclass":"host",
"cores":64,
"memory":256,
"clusters":0,
"customer":"MnS",
"mounts":[],
"roles":["ESX-HOST"],
"ipset":{"backnet":"1"},
"frontnet":[],
"created":"2015-09-28T11:12:36.526Z"
}
Query:
Machine.aggregate([
{ "$match": {
"idc": req.query.idc, "customer": req.query.customer}
} ,
{ "$group": {
"_id": {
"cluster": "$cluster",
"idc":"$idc",
"type": "$type"
},
"SumCores": { "$sum":"$cores" },
"SumMemory": { "$sum":"$memory" }
}},
{ "$group": {
"_id": {
"cluster": "$_id.cluster",
"idc": "$_id.idc"
},
"data": {
"$push": {
"type": "$_id.type",
"SumCores": "$SumCores",
"SumMemory": "$SumMemory"
}
}
}},
{ "$project": {
"Physical": {
"$setDifference": [
{ "$map": {
"input": "$data",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.type", "Physical" ] },
{
"SumCores": "$$el.SumCores",
"SumMemory": "$$el.SumMemory"
},
false
]
}
}},
[false]
]
},
"Virtual": {
"$setDifference": [
{ "$map": {
"input": "$data",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.type", "Virtual" ] },
{
"SumCores": "$$el.SumCores",
"SumMemory": "$$el.SumMemory"
},
false
]
}
}},
[false]
]
}
}},
{ "$unwind": "$Physical" },
{ "$unwind": "$Virtual"},
{ "$sort" : { "_id.idc": -1, "_id.cluster": 1 } }
]);
Which gives me the following output:
{
"_id" : {
"cluster" : 1,
"idc" : "LH5"
},
"Physical" : {
"SumCores" : 192,
"SumMemory" : 768
},
"Virtual" : {
"SumCores" : 112,
"SumMemory" : 384
}
}
My desired output is:
[
{
"_id": {
"cluster": 1,
"idc": "LH8"
},
"Physical": [
{
"environment": "DR",
"SumCores": 256,
"SumMemory": 1024
},
{
"environment": "PROD",
"SumCores": 256,
"SumMemory": 1024
}
],
"Virtual": [
{
"environment": "DR",
"SumCores": 232,
"SumMemory": 469
},
{
"environment": "PROD",
"SumCores": 232,
"SumMemory": 469
}
]
}
]
Essentially, I want to group the sums based on the environment
Very much as in your initial query ( actually written by myself ), all you really need to do is add in that field detail to the initial _id of $group and then carry that through into the subsequent array entries:
Machine.aggregate([
{ "$match": {
"idc": req.query.idc, "customer": req.query.customer}
} ,
{ "$group": {
"_id": {
"cluster": "$cluster",
"idc":"$idc",
"type": "$type",
"environment": "$environment"
},
"SumCores": { "$sum":"$cores" },
"SumMemory": { "$sum":"$memory" }
}},
{ "$group": {
"_id": {
"cluster": "$_id.cluster",
"idc": "$_id.idc"
},
"data": {
"$push": {
"type": "$_id.type",
"environment": "$_id.environment",
"SumCores": "$SumCores",
"SumMemory": "$SumMemory"
}
}
}},
{ "$project": {
"Physical": {
"$setDifference": [
{ "$map": {
"input": "$data",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.type", "Physical" ] },
{
"environment": "$$el.environment",
"SumCores": "$$el.SumCores",
"SumMemory": "$$el.SumMemory"
},
false
]
}
}},
[false]
]
},
"Virtual": {
"$setDifference": [
{ "$map": {
"input": "$data",
"as": "el",
"in": {
"$cond": [
{ "$eq": [ "$$el.type", "Virtual" ] },
{
"environment": "$$el.environment",
"SumCores": "$$el.SumCores",
"SumMemory": "$$el.SumMemory"
},
false
]
}
}},
[false]
]
}
}},
{ "$unwind": "$Physical" },
{ "$unwind": "$Virtual"},
{ "$sort" : { "_id.idc": -1, "_id.cluster": 1 } }
]);
But you also "really" should be using the query form I recommended you did in the first place, since it is clear that all you want to do is diplay this in a template, and looping array content should be very simple:
Machine.aggregate([
{ "$match": {
"idc": req.query.idc, "customer": req.query.customer}
} ,
{ "$group": {
"_id": {
"cluster": "$cluster",
"idc":"$idc",
"type": "$type",
"environment": "$environment"
},
"SumCores": { "$sum":"$cores" },
"SumMemory": { "$sum":"$memory" }
}},
{ "$group": {
"_id": {
"cluster": "$_id.cluster",
"idc": "$_id.idc"
},
"data": {
"$push": {
"type": "$_id.type",
"environment": "$_id.environment",
"SumCores": "$SumCores",
"SumMemory": "$SumMemory"
}
}
}},
{ "$sort" : { "_id.idc": -1, "_id.cluster": 1 } }
]);