Mongodb Search with embedded document. - mongodb

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

Related

match element in the array with aggregation

i have mongo db collection the follwing structure
{
{
"_id" : ObjectId("63e37afe7a3453d5014c011b"),
"schemaVersion" : NumberInt(1),
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"List" : [
{
"countryId" : NumberInt(1),
"name" : "Afghanistan",
},{
"countryId" : NumberInt(1),
"name" : "India",
},
{
"countryId" : NumberInt(1),
"name" : "USA",
}
}
i need to match the value with id, id1, id2, collectionName and name in the list to get country id for example if match the below value
"Id" : "ObjectId("63e37afe7a3453d5014c0112")",
"Id1" : "ObjectId("63e37afe7a3453d5014c0113")",
"Id2" : "ObjectId("63e37afe7a3453d5014c0114")",
"collectionName" : "Country",
"name" : "Afghanistan",
i need result
{
"countryId" : 1,
"name" : "Afghanistan",
}
i tried like below
db.country_admin.aggregate([
{ $match: { collectionName: "Country" } },
{ $unwind : '$countryList' },
{ $project : { _id : 0, 'countryList.name' : 1, 'countryList.countryId' : 1 } }
]).pretty()
and i have following output
[
{
"List" : {
"countryId" : 1.0,
"name" : "Afghanistan"
}
},
{
"List" : {
"countryId" : 2.0,
"name" : "india"
}
},
{
"List" : {
"countryId" : 3.0,
"name" : "USA"
}
}]```
You can try using $filter to avoid $unwind like this example:
First $match by your desired condition(s).
Then $filter and get the first element (as "List.name": "Afghanistan" is used into $match stage there will be at least one result).
And output only values you want using $project.
db.collection.aggregate([
{
"$match": {
"Id": ObjectId("63e37afe7a3453d5014c0112"),
"Id1": ObjectId("63e37afe7a3453d5014c0113"),
"Id2": ObjectId("63e37afe7a3453d5014c0114"),
"collectionName": "Country",
"List.name": "Afghanistan",
}
},
{
"$project": {
"country": {
"$arrayElemAt": [
{
"$filter": {
"input": "$List",
"cond": {
"$eq": [
"$$this.name",
"Afghanistan"
]
}
}
},
0
]
}
}
},
{
"$project": {
"_id": 0,
"countryId": "$country.countryId",
"name": "$country.name"
}
}
])
Example here
By the way, using $unwind is also possible and you can check this example

Finding intersection between two object arrays based on field

In mongo collection I have documents of following structure.
{
"_id" : "Suzuki",
"qty" : 10,
"plates" : [
{
"rego" : "1QX-WA-123",
"date" : 1516374000000.0
},
{
"rego" : "1QX-WA-456",
"date" : 1513369800000.0
}
],
"accounts" : [
{
"_id" : "23kpi9MD4KnTvnaW7",
"createdAt" : 1513810712802.0,
"date" : 1503446400000.0,
"type" : "Suzuki",
"rego" : "1QX-WA-123",
},
{
"_id" : "2Wqrd4yofvLmqLm5H",
"createdAt" : 1513810712802.0,
"date" : 1501632000000.0,
"type" : "Suzuki",
"rego" : "1QX-WA-111",
}
]
}
I am trying to filter objects in accounts array so that it contains only those objects whose rego exists in plates array.
I tried following query, however, it throws an error: all operands of $setIntersection must be arrays. One argument if of type object.
db.getCollection('dummy').aggregate([{
$project: {
plates: 1,
accounts: 1,
intersect: {
$setIntersection: [
{ $arrayElemAt: [ "$plates", 0 ] },
{ $arrayElemAt: [ "$accounts", 4 ] }
]
}
}
}])
The expected output I am looking for is:
{
"_id" : "Suzuki",
"qty" : 10,
"plates" : [
{
"rego" : "1QX-WA-123",
"date" : 1516374000000.0
},
{
"rego" : "1QX-WA-456",
"date" : 1513369800000.0
}
],
"accounts" : [
{
"_id" : "23kpi9MD4KnTvnaW7",
"createdAt" : 1513810712802.0,
"date" : 1503446400000.0,
"type" : "Suzuki",
"rego" : "1QX-WA-123",
}
]
}
So there are a couple of ways, but what you really are after is simply to $filter instead.
Using $in would likely be the first choice:
db.getCollection('dummy').aggregate([
{ "$addFields": {
"accounts": {
"$filter": {
"input": "$accounts",
"cond": {
"$in": [ "$$this.rego", "$plates.rego" ]
}
}
}
}}
])
Or if you don't have MongoDB 3.4 at least, then using $anyElementTrue:
db.getCollection('dummy').aggregate([
{ "$project": {
"qty": 1,
"plates": 1,
"accounts": {
"$filter": {
"input": "$accounts",
"as": "acc",
"cond": {
"$anyElementTrue": {
"$map": {
"input": "$plates.rego",
"as": "rego",
"in": { "$eq": [ "$$rego", "$$acc.rego" ] }
}
}
}
}
}
}}
])
Or even $setIsSubset:
db.getCollection('dummy').aggregate([
{ "$project": {
"qty": 1,
"plates": 1,
"accounts": {
"$filter": {
"input": "$accounts",
"as": "acc",
"cond": {
"$setIsSubset": [ ["$$acc.rego"], "$plates.rego" ]
}
}
}
}}
])
It's really not a $setIntersection for this type of operation, since that would need a comparison on "just the field values" as a "set", and the output is really just "that" and not the "objects".
You could do something silly with matching array indexes to the produced "set" positions:
db.getCollection('dummy').aggregate([
{ "$addFields": {
"accounts": {
"$map": {
"input": { "$setIntersection": ["$plates.rego", "$accounts.rego"] },
"in": {
"$arrayElemAt": [
"$accounts",
{ "$indexOfArray": [ "$accounts.rego", "$$this" ] }
]
}
}
}
}}
])
But in reality you probably really just want the $filter result as being far more practical. And if you want that output as a "set" then you can simply wrap the $filter output with a $setDifference or like operator to make the entries "unique".
In all variations these return:
{
"_id" : "Suzuki",
"qty" : 10.0,
"plates" : [
{
"rego" : "1QX-WA-123",
"date" : 1516374000000.0
},
{
"rego" : "1QX-WA-456",
"date" : 1513369800000.0
}
],
"accounts" : [
{
"_id" : "23kpi9MD4KnTvnaW7",
"createdAt" : 1513810712802.0,
"date" : 1503446400000.0,
"type" : "Suzuki",
"rego" : "1QX-WA-123"
}
]
}
Showing the items in the "accounts" array "filtered" as matching the respective "rego" amounts from the "plates" array.

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

Count same types element in array 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 }

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.