Mongo Unwind child object and Filter - mongodb

Here is the MongoPlayground about this problem
Today I have 2 nested arrays of object:
I have an array of meetings and inside of it an array of invites.
I need to project only the meetings, but with the invites filtered by the PartnerId. So I have many invites to different partners, but when each partner logs to the system, he can only see the invites for him and not the others. In other words, the array Invites will have only invites to it´s PartnerId.
I could get to the project part, but filter the the invites I am lost. Should I use a $filter or $group to another array?
here is what I got so far:
MongoPlayground
db.collection.aggregate([
{
"$match": {
"$nor": [
{
"Meetings": {
"$exists": false
}
},
{
"Meetings": {
"$size": 0.0
}
}
]
}
},
{
"$unwind": {
"path": "$Meetings"
}
},
{
"$project": {
"Meetings": "$Meetings"
}
},
{
"$replaceRoot": {
"newRoot": "$Meetings"
}
}
])
ANSWER that I did...
db.getCollection("ClientProject").aggregate(
[
{
"$match" : {
"$and" : [
{
"PartnerIds" : {
"$in" : [
"5f9b247f0ca60a000232cacf"
]
}
},
{
"$nor" : [
{
"Meetings" : {
"$exists" : false
}
},
{
"Meetings" : {
"$size" : 0.0
}
},
{
"Meetings" : {
"$eq" : null
}
}
]
}
]
}
},
{
"$unwind" : {
"path" : "$Meetings"
}
},
{
"$project" : {
"Meetings" : "$Meetings"
}
},
{
"$replaceRoot" : {
"newRoot" : "$Meetings"
}
},
{
"$addFields" : {
"Invites" : {
"$filter" : {
"input" : "$Invites",
"as" : "invite",
"cond" : {
"$eq" : [
"$$invite.PartnerId",
"5f9b247f0ca60a000232cacf"
]
}
}
}
}
}
],
{
"allowDiskUse" : false
}
);

Using $filter is a good way to achieve your goal, and here is how you can do that:
db.collection.aggregate([
{
"$match": {
"$nor": [
{
"Meetings": {
"$exists": false
}
},
{
"Meetings": {
"$size": 0.0
}
}
]
}
},
{
"$unwind": {
"path": "$Meetings"
}
},
{
"$project": {
"invites": {
"$filter": {
"input": "$Meetings.Invites",
"as": "invite",
"cond": {
"$eq": [
"$$invite.PartnerName",
"Thiago Parceiro "
]
}
}
}
}
}
])

Related

MongoDB: Get only specific fields in nested array documents

I'm trying to project the values from nested array documents which are in the below format. What I expect is to display only the specValue of the specific specType selected in the find query.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specType": "SEDAN"
"specValue" : "1"
},
{
"specType": "BR_INC"
"specValue" : "yes"
},
{
"specType": "PLAN"
"specValue" : "gold"
}
]
}
]
}
}
This is what I have tried.
db.cars.find({carId:'345677','car.model.specs.specType':'SEDAN'},{'car.model.specs.specValue':1})
This approach gives me the all the specValues instead like below.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specValue" : "1"
},
{
"specValue" : "yes"
},
{
"specValue" : "gold"
}
]
}
]
}
}
How do I make this right to get in the right format like this. Could anyone please help.
{
"carId": "345677"
"car" : {
"model" : [
{
"specs" : [
{
"specType": "SEDAN"
"specValue" : "1"
}
]
}
]
}
}
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"car": {
"model": {
"$filter": {
"input": {
"$map": {
"input": "$car.model",
"in": {
"specs": {
"$filter": {
"input": "$$this.specs",
"cond": { "$eq": ["$$this.specType", "SEDAN"] }
}
}
}
}
},
"cond": { "$ne": ["$$this.specs", []] }
}
}
}
}}
])

Remove aggregate with conditions

I have the a collection of documents as follows an example document:
{
'publicacao' : { 'data': '2013-13-13', 'hora': '13:13:13'},
'conteudo' : 'https://docs.google.com/document/d/1EQynJTiBa6FNI2O8XfoV0clMPxS5uOAu0_jKyEwsTBE/edit?usp=sharing',
'titulo' : 'As histórias de ciclano',
'categoria' : 'Romance',
'autor' : 'Ciclano',
'avaliacoes': [
{
'leitor': 'Fulano',
'nota': 1
},
{
'leitor': 'Beltrano',
'nota': 0
}
],
'denuncias': [
{
'denunciante': 'Ciclano'
},
{
'denunciante': 'Beltrano'
}
]
}
then i made an aggregate to define some documents to be removed:
var cursor = db.livro.aggregate( [
{
$project: {
id:1,
remover: {
$gt: [
{ $size: "$denuncias" },
{
$divide: [
{ $size: "$avaliacoes" },
2
]
}
]
}
}
}
]);
this aggregation returns the following docs:
{ "_id" : ObjectId("5cf5a9be7d48c53504974439"), "remover" : false }
{ "_id" : ObjectId("5cf5a9be7d48c5350497443a"), "remover" : false }
{ "_id" : ObjectId("5cf5a9be7d48c5350497443b"), "remover" : true }
{ "_id" : ObjectId("5cfd746e40d53565ca52132b"), "remover" : false }
{ "_id" : ObjectId("5cfd746e40d53565ca52132c"), "remover" : false }
{ "_id" : ObjectId("5cfd746e40d53565ca52132d"), "remover" : true }
I need to remove all docs with "remove": true.
I don't know how to get those ones!
Resolved:
I used the cursor.forEach() to iterate the objects and find the objects with remove = true.
cursor.forEach(function (doc){
if(doc.remover == true) {
db.livro.remove({"_id": doc._id});
print("Doc removido: "+ doc._id)
}
});
Instead of doing this with code after the query it is better overall to do it in the actual aggregation. Just check with a $match if the array has an element:
db.collection.aggregate([
{
$match: {
"denuncias.0": {
$exists: true
}
}
},
{
$project: {
id: 1,
remover: {
$gt: [
{
$size: "$denuncias"
},
{
$divide: [
{
$size: "$avaliacoes"
},
2
]
}
]
}
}
}
])
You can see if working here

How to access the second level array in mongodb (with group)

here is sample collection:
{
"_id":ObjectId("5cc7d8e88c33e065c56b0883"),
"age":70,
"child":[
{
"id":"son1",
"age":40,
"grandSon":[
{
"id":"grand1",
"age":10,
"like":[
{
"id":"like1",
"info":"apple"
},
{
"id":"like2",
"info":"banana"
}
],
"grandGrandSon" :[
{
"id":"grandGrand1",
"age":10
},
{
"id":"grandGrand2",
"age":13
}
]
},
{
"id":"grand2",
"age":13,
"like":[
{
"id":"like1",
"info":"apple"
}
],
"grandGrandSon" :[
{
"id":"grandGrand1",
"age":12
},
{
"id":"grandGrand2",
"age":14
}
]
}
]
},
{
"id":"son2",
"age":40,
"grandSon":[
{
"id":"grand1",
"age":10,
"like":[
{
"id":"like1",
"info":"apple"
},
{
"id":"like2",
"info":"banana"
}
],
"grandGrandSon" :[
{
"id":"grandGrand1",
"age":15
},
{
"id":"grandGrand2",
"age":16
}
]
},
{
"id":"grand2",
"age":14,
"like":[
{
"id":"like1",
"info":"apple"
},
{
"id":"like2",
"info":"banana"
}
],
"grandGrandSon" :[
{
"id":"grandGrand1",
"age":12
},
{
"id":"grandGrand2",
"age":13
}
]
}
}
]
}
]
I want to result like this
parent age and child'size and like grandSons Count, grandGrandSons count and child's like count
{
"_id": ObjectId("5cc7d8e88c33e065c56b0883"),
"age": 70,
"childCount": 2,
"grandSonsCount": 4
"likeCount": 7
"grandGrandSonsCount": 8,
"grandSonsAgeCount": 105
}
here is my code
{
$lookup:
{
from: "..."
localField: "...",
foreignField: "..",
as: "child"
},
},
{ $unwind: "$child" }
{ $group : {
_id : "$_id",
age: {$first:"$age"},
childCount: {$sum: 1},
grandSonsCount : {$sum : {$size : "$child.grandSon"}},
likeCount: {$sum : {$size : "$child.like"}},
grandGrandSonsCount :
{$sum : {$sum : {$size : "$child.grandSon.grandGrandSon"}}},
//it is return 4(grandSonsCount)
}},
I used lookup, making for above collection(child)
it return grandSon count, but I want to get grandGrandSon Count
how can I get nested array in nested array of size?
how can I have to do??
Add $reduce to the group stage:
{
"$group" : {
....
....
"grandGrandSonsCount" : {
"$sum" : {
"$reduce" : {
"input" : "$child.grandSon",
"initialValue" : 0,
"in" : {
"$sum" : [
"$$value",
{
"$size" : "$$this.grandGrandSon"
}
]
}
}
}
}
}
}

Combine results based on condition during group by

Mongo query generated out of java code:
{
"pipeline": [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},
{
"$group": {
"_id": "$result",
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}]
}
Field 'result' can have values like Approved, Rejected, null and "" (empty string). What I am trying to achieve is combining the count of both null and empty together.
So that the empty string Id will have the count of both null and "", which is equal to 4
I'm sure theres a more "proper" way but this is what i could quickly come up with:
[
{
"$group" : {
"_id" : "$result",
"id" : {
"$first" : "$result"
},
"labelKey" : {
"$first" : {
"$ifNull" : [
"$result",
"$result"
]
}
},
"value" : {
"$sum" : 1.0
}
}
},
{
"$group" : {
"_id" : {
"$cond" : [{
$or: [
{"$eq": ["$_id", "Approved"]},
{"$eq": ["$_id", "Rejected"]},
]}},
"$_id",
""
]
},
"temp" : {
"$push" : {
"_id" : "$_id",
"labelKey" : "$labelKey"
}
},
"count" : {
"$sum" : "$value"
}
}
},
{
"$unwind" : "$temp"
},
{
"$project" : {
"_id" : "$temp._id",
"labelKey": "$temp.labelKey",
"count" : "$count"
}
}
],
);
Due to the fact the second group is only on 4 documents tops i don't feel too bad about doing this.
I have used $facet.
The MongoDB stage $facet lets you run several independent pipelines within the stage of a pipeline, all using the same data. This means that you can run several aggregations with the same preliminary stages, and successive stages.
var queries = [{
"$match": {
"Id": "09cd9a5a-85c5-4948-808b-20a52d92381a"
}
},{
$facet: {//
"empty": [
{
$match : {
result : { $in : ['',null]}
}
},{
"$group" : {
"_id" : null,
value : { $sum : 1}
}
}
],
"non_empty": [
{
$match : {
result : { $nin : ['',null]}
}
},{
"$group" : {
"_id" : '$result',
value : { $sum : 1}
}
}
]
}
},
{
$project: {
results: {
$concatArrays: [ "$empty", "$non_empty" ]
}
}
}];
Output :
{
"results": [{
"_id": null,
"value": 52 // count of both '' and null.
}, {
"_id": "Approved",
"value": 83
}, {
"_id": "Rejected",
"value": 3661
}]
}
Changing the group by like below solved the problem
{
"$group": {
"_id": {
"$ifNull": ["$result", ""]
},
"id": {
"$first": "$result"
},
"labelKey": {
"$first": {
"$ifNull": ["$result",
"$result"]
}
},
"value": {
"$sum": 1
}
}
}

Mongodb how to get OR of two aggregated queries?

I'm using following aggregated query to get results (let say query1):
db.fb.aggregate(
[
{
$addFields : { noOfLikes : { $sum : { $map : { input : "$facebookEvents", as : "f", in : {$cond : [ { $eq : ["$$f.type" , "like"] }, 1, 0 ]} }}}}
},
{
$match : {"noOfLikes" : {$gte : 2}}
}
]
)
and another query to get some other results (let's say query2):
db.fb.aggregate(
[
{ $match : { author : "dave" } },
{ $match : { test : "test1" } }
]
)
Is it possible to get query1 OR query2 using a single aggregate query? In other words, I want to get results that match either query1 or query2 using a single query. Appreciate any help
mongo version: 3.4.4
Within a single aggregate query, you would need to run your pipeline as follows:
db.fb.aggregate([
{
"$addFields": {
"noOfLikes": {
"$sum" : {
"$map": {
"input": "$facebookEvents",
"as": "f",
"in": {
"$cond": [{ "$eq": ["$$f.type" , "like"] }, 1, 0 ]
}
}
}
}
}
},
{
"$match" : {
"$or": [
{ "noOfLikes" : { "$gte" : 2 } },
{ "author": "dave", "test": "test1" }
]
}
}
])
or using $redact as:
db.fb.aggregate([
{
"$redact": {
"$cond": [
{
"$or": [
{ "$gte": [
{
"$sum" : {
"$map": {
"input": "$facebookEvents",
"as": "f",
"in": {
"$cond": [
{ "$eq": ["$$f.type" , "like"] },
1,
0
]
}
}
}
}, 2 ]
},
{
"$and": [
{ "$eq": ["$author", "dave"] },
{ "$eq": ["$test", "test1"] }
]
}
]
},
"$$KEEP",
"$$PRUNE"
]
}
}
])
You can use $facet
db.fb.aggregate(
{
$facet : {
byLikes : [
{ $addFields : { noOfLikes : { $sum : { $map : { input : "$facebookEvents", as : "f", in : {$cond : [ { $eq : ["$$f.type" , "like"] }, 1, 0 ]} }}}} },
{ $match : {"noOfLikes" : {$gte : 2}}}
],
byAuthor : [
{ $match : { author : "dave" } },
{ $match : { test : "test1" } }
]
}
}
)