Count same types element in array mongodb - mongodb

{
_id:1, members: [
{
name:"John",
status:"A"
},
{
name:"Alex",
status:"D"
},
{
name:"Jack",
status:"A"
},
{
name:"Robin",
status:"D"
}
]}
That is Channel document.
Now I need to count all elements in members array where status equal to 'A'.
For example the above doc has 2 members with status 'A'.
How can I achieve this?

You can use mongodb-count to achieve the desired result.
Returns the count of documents that would match a find() query. The db.collection.count() method does not perform the find() operation but instead counts and returns the number of results that match a query.
So your query will be
var recordcount = db.collName.count({"members.status":"A"});
Now recordCount will be number of records that matches {"members.status":"A"} query.

Here Is your Json file
{
"_id" : ObjectId("575915653b3cc43fca1fca4c"),
"members" : [
{
"name" : "John",
"status" : "A"
},
{
"name" : "Alex",
"status" : "D"
},
{
"name" : "Jack",
"status" : "A"
},
{
"name" : "Robin",
"status" : "D"
}
]
}
And you want to the count of all elements in members array where
status equal to 'A'.
you have to try this one to find out your count
db.CollectionName.aggregate([{
"$project": {
"members": {
"$filter": {
"input": "$members",
"as": "mem",
"cond": {
"$eq": ["$$mem.status", "A"]
}
}
}
}
}, {
"$project": {
"membersize": {
"$size": "$members"
}
}
}]).pretty()
And you found your answer is like that { "_id" :
ObjectId("575915653b3cc43fca1fca4c"), "membersize" : 2 }
try this one for old version......
db.CollectionName.aggregate([{"$unwind":"$members"},{"$match":{"members.status":"A"}},{"$group":{_id:"$_id","memberscount":{"$sum":1}}}]).pretty()
{ "_id" : ObjectId("575915653b3cc43fca1fca4c"), "memberscount" : 2 }

Here Is your Json file
{
"_id" : ObjectId("575915653b3cc43fca1fca4c"),
"members" : [
{
"name" : "John",
"status" : "A"
},
{
"name" : "Alex",
"status" : "D"
},
{
"name" : "Jack",
"status" : "A"
},
{
"name" : "Robin",
"status" : "D"
}
]
}
And you want to the count of all elements in members array where
status equal to 'A'.
you have to try this one to find out your count
db.CollectionName.aggregate([{
"$project": {
"members": {
"$filter": {
"input": "$members",
"as": "mem",
"cond": {
"$eq": ["$$mem.status", "A"]
}
}
}
}
}, {
"$project": {
"membersize": {
"$size": "$members"
}
}
}]).pretty()
And you found your answer is like that { "_id" :
ObjectId("575915653b3cc43fca1fca4c"), "membersize" : 2 }

Related

Filter and count the number of element in an array [duplicate]

I have a mongoDB collection called "conference" with an array of participants as below :
[
{
"_id" : 5b894357a0c84d5a5d221f25,
"conferenceName" : "myFirstConference",
"startDate" : 1535722327,
"endDate" : 1535722420,
"participants" : [
{
"name" : "user1",
"origin" : "internal",
"ip" : "192.168.0.2"
},
{
"name" : "user2",
"origin" : "external",
"ip" : "172.20.0.3"
},
]
},
...
]
I would like to get the following result :
[
{
"conferenceName" : "myFirstConference",
"startDate" : 1535722327,
"endDate" : 1535722420,
"internalUsersCount" : 1
"externalUsersCount" : 1,
},
...
]
I tried the request below but it's not working :
db.getCollection("conference").aggregate([
{
$addFields: {
internalUsersCount : {
$size : { "$participants" : {$elemMatch : { origin : "internal" }}}
},
externalUsersCount : {
$size : { "$participants" : {$elemMatch : { origin : "external" }}}
}
}
}
])
How is it possible to count "participant" array elements that match {"origin" : "internal"} and {"origin" : "external"} ?
You need to use $filter aggregation to filter out the external origin and internal origin along with the $size aggregation to calculate the length of the arrays.
Something like this
db.collection.aggregate([
{ "$addFields": {
"internalUsersCount": {
"$size": {
"$filter": {
"input": "$participants",
"as": "part",
"cond": { "$eq": ["$$part.origin", "internal"]}
}
}
},
"externalUsersCount": {
"$size": {
"$filter": {
"input": "$participants",
"as": "part",
"cond": { "$eq": ["$$part.origin", "external"] }
}
}
}
}}
])
Output
[
{
"conferenceName": "myFirstConference",
"endDate": 1535722420,
"externalUsersCount": 1,
"internalUsersCount": 1,
"startDate": 1535722327
}
]

regroup after unwind of subdocument of subdocument

This is my Document.
{
"_id" : ObjectId("589b6132fafb5a09549b46cb"),
"name" : "foo",
"users" : [
{
"_id" : ObjectId("589b6132fafb5a09549b46cc"),
"name" : "Peter",
"emails" : [
{
"address" : "peter#email.com"
},
{
"address" : "test2#email.com"
}
]
},
{
"_id" : ObjectId("589b6132fafb5a09549b46cd"),
"name" : "Joe",
"emails" : []
}
]
}
I'm unwinding users and users.email
And when I try to regroup, I get a duplicate on user named Peter because it has 2 emails.
Query:
db.test.aggregate([
{ "$unwind": {
"path": "$users",
"preserveNullAndEmptyArrays": true
} },
{ "$unwind": {
"path": "$users.emails",
"preserveNullAndEmptyArrays": true
} },
{
"$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"users": { "$addToSet": "$users"},
"allEmails": { "$push": "$users.emails.address" }
}
}
])
Result:
{
"_id" : ObjectId("589b6132fafb5a09549b46cb"),
"name" : "foo",
"users" : [
{
"_id" : ObjectId("589b6132fafb5a09549b46cd"),
"name" : "Joe"
},
{
"_id" : ObjectId("589b6132fafb5a09549b46cc"),
"name" : "Peter",
"emails" : {
"address" : "test2#email.com"
}
},
{
"_id" : ObjectId("589b6132fafb5a09549b46cc"),
"name" : "Peter",
"emails" : {
"address" : "peter#email.com"
}
}
],
"allEmails" : [
"peter#email.com",
"test2#email.com"
]
}
I need the users object to be exact the same before the unwind with allEmails on the document parent as shown in the following example.
{
"_id" : ObjectId("589b6132fafb5a09549b46cb"),
"name" : "foo",
"users" : [
{
"_id" : ObjectId("589b6132fafb5a09549b46cc"),
"name" : "Peter",
"emails" : [
{ "address" : "test2#email.com" },
{ "address" : "peter#email.com" }
]
},
{
"_id" : ObjectId("589b6132fafb5a09549b46cd"),
"name" : "Joe",
"emails" : []
}
],
"allEmails" : [
"peter#email.com",
"test2#email.com"
]
}
Running the following aggregate pipeline should give you the desired result:
db.test.aggregate([
{
"$addFields": {
"allEmails": {
"$reduce": {
"input": {
"$map": {
"input": "$users",
"as": "user",
"in": "$$user.emails"
}
},
"initialValue": [],
"in": { "$concatArrays": ["$$value", "$$this.address"] }
}
}
}
}
])
The above pipeline works by initially creating a two dimensional array of emails addresses objects using $map. To show an example result produced by apply the expression
{
"$map": {
"input": "$users",
"as": "user",
"in": "$$user.emails"
}
}
run a test pipeline with just a single field that holds the results:
db.test.aggregate([
{
"$project": {
"twoDarray": {
"$map": {
"input": "$users",
"as": "user",
"in": "$$user.emails"
}
}
}
}
}
])
which will produce the 2D array
{
"_id" : ObjectId("589b6132fafb5a09549b46cb"),
"twoDarray" : [
[
{ "address" : "peter#email.com" },
{ "address" : "test2#email.com" }
],
[]
]
}
Now, denormalise this 2-D array
[
[
{ "address" : "peter#email.com" },
{ "address" : "test2#email.com" }
],
[]
]
by using the $reduce operator which applies an expression to each element in an array and combines them into a single value. With the help of the $concatArrays operator, you can concatenate each element within the $reduce expression to form the final desired array
[
"peter#email.com",
"test2#email.com"
]

Mongodb Search with embedded document.

Structure of mongodb collection is like this.
collection User
{
"name":"sufaid",
"age":"22",
"address":"zzzz",
"product":[{"id":1,"name":"A"},
{"id":6,"name":"N"},
{"id":3,"name":"D"},
{"id":7,"name":"q"},
]
}
I need to find users those who have product id "3"
Out put should be like this
{
"name":"sufaid",
"age":"22",
"address":"zzzz",
"product":{"id":3,"name":"D"}
}
Note : With out using $unwind and projection like "product.$"
"product.$" through error while using pymongo.
Any other option is there ???
use $elemMatch. https://docs.mongodb.com/manual/reference/operator/projection/elemMatch/
for your query:
db.User.find({},{name:1,age:1,address:1,product:{$elemMatch:{id:3}}})
or
db.User.find({},{product:{$elemMatch:{id:3}}})
o/p: {
"name" : "sufaid",
"age" : "22",
"address" : "zzzz",
"product" : [
{
"id" : 3.0,
"name" : "D"
}
]
}
As you require it for aggregation:
db.User.aggregate([
{$unwind:'$product'},
{$match:{'product.id':3}},
{$project:{_id:0,name:1,age:1,aaddress:1,product:1}}
])
o/p:
{
"name" : "sufaid",
"age" : "22",
"address" : "zzzz",
"product" : {
"id" : 3.0,
"name" : "D"
}
}
This will give exactly what you indicated in the question.
You could use the aggregation framework which has a plethora of operators that you can use, in particular you'd need the $filter and $arrayElemAt operators in a $project pipeline.
For instance, you could return just the product field as an embedded document by running the following pipeline:
db.user.aggregate([
{ "$match": { "product.id": 3 } },
{
"$project": {
"name": 1,
"age": 1,
"address": 1,
"product": {
"$arrayElemAt": [
{
"$filter": {
"input": "$product",
"as": "item",
"cond": { "$eq": [ "$$item.id", 3 ] }
}
},
0
]
}
}
}
])
Sample Output
{
"_id" : ObjectId("5829ac89628123dcf8a64b7a"),
"name" : "sufaid",
"age" : "22",
"address" : "zzzz",
"product" : {
"id" : 3,
"name" : "D"
}
}
If you just need an output with the array filtered, skip the $arrayElemAt expression and use the $filter only:
db.user.aggregate([
{ "$match": { "product.id": 3 } },
{
"$project": {
"name": 1,
"age": 1,
"address": 1,
"product": {
"$filter": {
"input": "$product",
"as": "item",
"cond": { "$eq": [ "$$item.id", 3 ] }
}
}
}
}
])
Sample Output
{
"_id" : ObjectId("5829ac89628123dcf8a64b7a"),
"name" : "sufaid",
"age" : "22",
"address" : "zzzz",
"product" : [
{ "id" : 3, "name" : "D" }
]
}
db.User.find({},{product:{$elemMatch:{id:3}}})
it's enough

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

MongoDB Projection of Nested Arrays

I've got a collection "accounts" which contains documents similar to this structure:
{
"email" : "john.doe#acme.com",
"groups" : [
{
"name" : "group1",
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c2", "address" : "some address 2" },
{ "localId" : "c3", "address" : "some address 3" }
]
},
{
"name" : "group2",
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c3", "address" : "some address 3" }
]
}
]
}
Via
q = { "email" : "john.doe#acme.com", "groups" : { $elemMatch: { "name" : "group1" } } }
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1" } } }
db.accounts.find( q, p ).pretty()
I'll successfully get just the group of a specified account I'm interested in.
Question: How can I get a limited list of "contacts" within a certain "group" of a specified "account"? Let's suppose I've got the following arguments:
account: email - "john.doe#acme.com"
group: name - "group1"
contact: array of localIds - [ "c1", "c3", "Not existing id" ]
Given these arguments I'd like to have the following result:
{
"groups" : [
{
"name" : "group1", (might be omitted)
"contacts" : [
{ "localId" : "c1", "address" : "some address 1" },
{ "localId" : "c3", "address" : "some address 3" }
]
}
]
}
I don't need anything else apart from the resulting contacts.
Approaches
All queries try to fetch just one matching contact instead of a list of matching contacts, for the sake of simplicity.
I've tried the following queries without any success:
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts" : { $elemMatch: { "localId" : "c1" } } } } }
p = { "groups.name" : 0, "groups" : { $elemMatch: { "name" : "group1", "contacts.localId" : "c1" } } }
not working: returns whole array or nothing depending on localId
p = { "groups.$" : { $elemMatch: { "localId" : "c1" } } }
error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
"code" : 17287
}
p = { "groups.contacts" : { $elemMatch: { "localId" : "c1" } } }
error: {
"$err" : "Can't canonicalize query: BadValue Cannot use $elemMatch projection on a nested field.",
"code" : 17287
}
Any help is appreciated!
2017 Update
Such a well put question deserves a modern response. The sort of array filtering requested can actually be done in modern MongoDB releases post 3.2 via simply $match and $project pipeline stages, much like the original plain query operation intends.
db.accounts.aggregate([
{ "$match": {
"email" : "john.doe#acme.com",
"groups": {
"$elemMatch": {
"name": "group1",
"contacts.localId": { "$in": [ "c1","c3", null ] }
}
}
}},
{ "$addFields": {
"groups": {
"$filter": {
"input": {
"$map": {
"input": "$groups",
"as": "g",
"in": {
"name": "$$g.name",
"contacts": {
"$filter": {
"input": "$$g.contacts",
"as": "c",
"cond": {
"$or": [
{ "$eq": [ "$$c.localId", "c1" ] },
{ "$eq": [ "$$c.localId", "c3" ] }
]
}
}
}
}
}
},
"as": "g",
"cond": {
"$and": [
{ "$eq": [ "$$g.name", "group1" ] },
{ "$gt": [ { "$size": "$$g.contacts" }, 0 ] }
]
}
}
}
}}
])
This makes use of of the $filter and $map operators to only return the elements from the arrays as would meet the conditions, and is far better for performance than using $unwind. Since the pipeline stages effectively mirror the structure of "query" and "project" from a .find() operation, the performance here is basically on par with such and operation.
Note that where the intention is to actually work "across documents" to bring details together out of "multiple" documents rather than "one", then this would usually require some type of $unwind operation in order to do so, as such enabling the array items to be accessible for "grouping".
This is basically the approach:
db.accounts.aggregate([
// Match the documents by query
{ "$match": {
"email" : "john.doe#acme.com",
"groups.name": "group1",
"groups.contacts.localId": { "$in": [ "c1","c3", null ] },
}},
// De-normalize nested array
{ "$unwind": "$groups" },
{ "$unwind": "$groups.contacts" },
// Filter the actual array elements as desired
{ "$match": {
"groups.name": "group1",
"groups.contacts.localId": { "$in": [ "c1","c3", null ] },
}},
// Group the intermediate result.
{ "$group": {
"_id": { "email": "$email", "name": "$groups.name" },
"contacts": { "$push": "$groups.contacts" }
}},
// Group the final result
{ "$group": {
"_id": "$_id.email",
"groups": { "$push": {
"name": "$_id.name",
"contacts": "$contacts"
}}
}}
])
This is "array filtering" on more than a single match which the basic projection capabilities of .find() cannot do.
You have "nested" arrays therefore you need to process $unwind twice. Along with the other operations.
You could use the $unwind operator of the aggregation framework.
For example:
db.contact.aggregate({$unwind:'$groups'}, {$unwind:'$groups.contacts'}, {$match:{email:'john.doe#acme.com', 'groups.name':'group1', 'groups.contacts.localId':{$in:['c1', 'c3', 'whatever']}}});
Should give the following result:
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe#acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c1", "address" : "some address 1" } } }
{ "_id" : ObjectId("5500103e706342bc096e2e14"), "email" : "john.doe#acme.com", "groups" : { "name" : "group1", "contacts" : { "localId" : "c3", "address" : "some address 3" } } }
If you want only one object, you can then use the $group operator.