MongoDB query only the inner document - mongodb

My mongodb collection looks like this:
{
"_id" : ObjectId("5333bf6b2988dc2230c9c924"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb1",
"content" : "mongo content1"
},
{
"title" : "replicaset1",
"content" : "replca content1"
}
]
}
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"name" : "Mongo2",
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo content2"
},
{
"title" : "replicaset1",
"content" : "replca content1"
},
{
"title" : "mongodb2",
"content" : "mongo content3"
}
]
}
I want to query only notes that have the title "mongodb2" but do not want the complete document.
I am using the following query:
> db.test.find({ 'notes.title': 'mongodb2' }, {'notes.$': 1}).pretty()
{
"_id" : ObjectId("5333fd402988dc2230c9c925"),
"notes" : [
{
"title" : "mongodb2",
"content" : "mongo bakwas2"
}
]
}
I was expecting it to return both notes that have title "mongodb2".
Does mongo return only the first document when we query for a document within a document ?

The positional $ operator can only return the first match index that it finds.
Using aggregate:
db.test.aggregate([
// Match only the valid documents to narrow down
{ "$match": { "notes.title": "mongodb2" } },
// Unwind the array
{ "$unwind": "$notes" },
// Filter just the array
{ "$match": { "notes.title": "mongodb2" } },
// Reform via group
{ "$group": {
"_id": "$_id",
"name": { "$first": "$name" },
"notes": { "$push": "$notes" }
}}
])
So you can use this to "filter" specific documents from the array.

$ always refers to the first match, as does the $elemMatch projection operator.
I think you have three options:
separate the notes so each is a document of its own
accept sending more data over the network and filter client-side
use the aggregation pipeline ($match and $project)
I'd probably choose option 1, but you probably have a reason for your data model.

Related

How to return just the nested documents of an array from all documents

I have a question about querying nested documents. I tried to search but nothing answered my question or I am maybe overlooking it. I have structure like this:
{
"_id" : ObjectId("592aa441e0f8de09b0912fe9"),
"name" : "Patrick Rothfuss",
"books" : [
{
"title" : "Name of the wind",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "Wise Man's Fear",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
},
},
{
"_id" : ObjectId("592aa441e0f8de09b0912fe9"),
"name" : "Rober Jordan",
"books" : [
{
"title" : "The Eye of the World",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "The Great Hunt",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
}
},
And I would like to query for the list of all books in entire colletion of Authors - something like:
"books" : [
{
"title" : "The Eye of the World",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "The Great Hunt",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912feb")
},
{
"title" : "Name of the wind",
"pages" : 400,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
},
{
"title" : "Wise Man's Fear",
"pages" : 500,
"_id" : ObjectId("592aa441e0f8de09b0912fea")
}]
You can do this using .aggregate() and predominantly the $unwind pipeline operator:
In modern MongoDB 3.4 and above you can use in tandem with $replaceRoot
Model.aggregate([
{ "$unwind": "$books" },
{ "$replaceRoot": { "newRoot": "$books" } }
],function(err,results) {
})
In earlier versions you specify all fields with $project:
Model.aggregate([
{ "$unwind": "$books" },
{ "$project": {
"_id": "$books._id",
"pages": "$books.pages",
"title": "$books.title"
}}
],function(err,results) {
})
So $unwind is what you use to deconstruct or "denormalise" the array entries for processing. Effectively this creates a copy of the whole document for each member of the array.
The rest of the task is about returning "only" those fields present in the array.
It's not a very wise thing to do though. If your intent is to only return content embedded within an array of a document, then you would be better off putting that content into a separate collection instead.
It's far better for performance, pulling apart a all documents from a collection with the aggregation framework, just to list those documents from the array only.
According to above mentioned description please try executing following query in MongoDB shell.
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$unwind: "$books"
},
// Stage 2
{
$group: {
_id:null,
books:{$addToSet:'$books'}
}
},
// Stage 3
{
$project: {
books:1,
_id:0
}
},
]
);

mongoDB query to find the document in nested array

[{
"username":"user1",
"products":[
{"productID":1,"itemCode":"CODE1"},
{"productID":2,"itemCode":"CODE1"},
{"productID":3,"itemCode":"CODE2"},
]
},
{
"username":"user2",
"products":[
{"productID":1,"itemCode":"CODE1"},
{"productID":2,"itemCode":"CODE2"},
]
}]
I want to find all the "productID" of "products" for "user1" such that "itemCode" for the product is "CODE1".
What query in mongoDB should be written to do so?
If you only need to match a single condition, then the dot notation is sufficient.
In Mongo shell:
db.col.find({"products.itemCode" : "CODE1", "username" : "user1"})
This will return all users with nested product objects having itemCode "CODE1".
Updated
Wasn't clear on your requirements at first but this should be it.
If you want each product as a separate entry, then you would need to use the aggregate framework. First split the entries in the array using $unwind, then use $match for your conditions.
db.col.aggregate(
{ $unwind: "$products" },
{ $match: { username: "user1", "products.itemCode": "CODE1" } }
);
response:
{ "_id" : ObjectId("57cdf9c0f7f7ecd0f7ef81b6"), "username" : "user1", "products" : { "productID" : 1, "itemCode" : "CODE1" } }
{ "_id" : ObjectId("57cdf9c0f7f7ecd0f7ef81b6"), "username" : "user1", "products" : { "productID" : 2, "itemCode" : "CODE1" } }
The answer to your question is
db.col.aggregate([
{ $unwind: "$products" },
{ $match: { username: "user1", "products.itemCode": CODE1 } },
{ $project: { _id: 0, "products.productID": 1 } }
]);
In my case didn't work without [ ] tags.
You need multiple filter for this like below which is nothing but AND condition (assuming your collection name is collection1)
db.collection1.find({"username":"user1", "products.itemCode" : "CODE1"})

combining distinct on projection in mongodb

Is there a query i can use on the following collection to get the result at the bottom?
Example:
{
"_id" : ObectId(xyz),
"name" : "Carl",
"something":"else"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny",
"something":"else"
},
{
"_id" : ObectId(bbb),
"name" : "Carl",
"something":"other"
}
I need a query to get this result:
{
"_id" : ObectId(xyz),
"name" : "Carl"
},
{
"_id" : ObectId(aaa),
"name" : "Lenny"
},
A set of documents with no identical names. Its not important which _ids are kept.
You can use aggregation framework to get this shape, the query could look like this:
db.collection.aggregate(
[
{
$group:
{
_id: "$name",
id: { $first: "$_id" }
}
},
{
$project:{
_id:"$id",
name:"$_id"
}
}
]
)
As long as you don't need other fields this will be sufficient.
If you need to add other fields - please update document structure and expected result.
as you don't care about ids it can be simplified
db.collection.aggregate([{$group:{_id: "$name"}}])

How to get mongodb deeply embeded document id

I have the following mongo document, which is part of a bigger document called attributes, which also has Colour and Size
> db.attributes.find({'name': {'en-UK': 'Fabric'}}).pretty()
{
"_id" : ObjectId("543261cda14c971132fa2b91"),
"values" : [
{
"source" : [
{
"_id" : ObjectId("543261cda14c971132fa2b79"),
"name" : {
"en-UK" : "Combed Cotton"
}
},
],
"name" : [
{
"_id" : ObjectId("543261cda14c971132fa2b85"),
"name" : {
"en-UK" : "Brushed 3-ply"
}
},
{
"_id" : ObjectId("543261cda14c971132fa2b8f"),
"name" : {
"en-UK" : "Plain Weave"
}
},
{
"_id" : ObjectId("543261cda14c971132fa2b90"),
"name" : {
"en-UK" : "1x1 Rib"
}
}
]
}
],
"name" : {
"en-UK" : "Fabric"
}
}
I am trying to return the _id for a sub document and have the following:
db.attributes.aggregate([
{ '$match': {'name.en-UK': 'Fabric'} },
{ '$unwind' : '$values' },
{ '$project': { 'name' : '$values.name'} },
{ '$match': { '$and': [{"name.name.en-UK" : "1x1 Rib"} ] }}
])
What is the correct way to do this?
Also, the values of Fabric is an array with two items, source and name, but if I populate it like:
> db.attributes.find({'name': {'en-UK': 'Fabric'}}).pretty()
{
"_id" : ObjectId("543261cda14c971132fa2b91"),
"values" : {
"source" : [{ ... }]
"name": [{ ... }]
}
}
I get the following error
"errmsg" : "exception: $unwind: value at end of field path must be an array"
But if I wrap it inside a square brackets this then works, so that
> db.attributes.find({'name': {'en-UK': 'Fabric'}}).pretty()
{
"_id" : ObjectId("543261cda14c971132fa2b91"),
"values" : [{
"source" : [{ ... }],
"name": [{ ... }]
}]
}
what am I missing as values is an array of two objects, source and name each containing a list of arrays
Any advice much appreciated
What you seem to be "missing" here is that "some" of your documents do either not contain a "value" property at all or at the very least it is "not an array". This is the basic context of the error you have been given.
Fortunately there are a couple of ways to get around this. Namely, either "testing" for the presence of an array when submitting you original query. Or actually "substituting" the missing element for some kind of array when processing the pipeline.
Here are both approaches in what is effectively an redundant form since the first $match condition really sorts this out:
db.attributes.aggregate([
{ "$match": {
"name.en-UK": "Fabric",
"values.0": { "$exists": true }
}},
{ "$project": {
"name": 1,
"values": { "$ifNull": [ "$values", [] ] }
}},
{ "$unwind": "$values" },
{ "$unwind": "$values.name" },
{ "$match": { "values.name.name.en-UK" : "1x1 Rib" }}
])
So as I said. Really redundant in that the initial $match actually asks if an "initial array element" actually exists. Which kind of means that there is an array there.
The second $project phase actually uses the $ifNull operator to "fill in" a value ( or basically an empty array ) where the tested element does not exist. We tested for that anyway before, but this demonstrates the different approaches.
But the basic idea id either "avoiding" or "filling-in" where your document does not have the expected data that you want to process. Which is the cause of your error.

add where condition in aggregate and group function in mongodb

I have mongo model lets say MYLIST containing data like:-
{
"_id" : ObjectId("542139f31284ad1461dbc15f"),
"Category" : "CENTER",
"Name" : "STAND",
"Url" : "center/stand",
"Img" : [ {
"url" : "www.google.com/images",
"main" : "1",
"home" : "1",
"id" : "34faf230-43cf-11e4-8743-311ea2261289"
},
{
"url" : "www.google.com/images1",
"main" : "1",
"home" : "0",
"id" : "34faf230-43cf-11e4-8743-311e66441289"
} ]
}
I execute the following query to the MYLIST collection:
db.MYLIST.aggregate([
{ "$group": {
"_id": "$Category",
"Name": { "$addToSet": {
"name": "$Name",
"url": "$Url",
"img": "$Img"
}}
}},
{ "$sort": { "_id" : 1 } }
]);
And I got the following result -
[
{ _id: 'CENTER',
Name:
[ { "name" : "Stand",
"url" : "center/stand",
"img": { "url" : "www.google.com/images" , "main" : "1", "home" : "1", "id" : "350356a0-43cf-11e4-8743-311ea2261289" }
}]
},
{ _id: 'CENTER',
Name:
[ { "name" : "Stand",
"url" : "center/stand",
"img": { "url" : "www.google.com/images1" , "main" : "1", "home" : "0", "id" : "34faf230-43cf-11e4-8743-311ea2261289" }
}]
}
]
As you can see my img key itself is an array of objects, Hence I am getting multiple entries for the same category of each entry in img array.
What I actually need is to get only those images that have some value for home key.
expected result:-
[
{ _id: 'CENTER',
Name:
[ { "name" : "Stand",
"url" : "center/stand",
"img": { "url" : "www.google.com/images" , "main" : "1", "home" : "1", "id" : "350356a0-43cf-11e4-8743-311ea2261289" }
}]
},
]
Hence I would like to add where the condition for img.home > 0 on the above-mentioned query, Could anybody help me to resolve this issue as my relatively new to MongoDB.
Still really not sure if this is what you want or even why you would be using $addToSet on this grouping. But if all you want to do is "filter" the content of the array returned in your result, then what you want to do is $match the array elements to your condition after processing an $unwind pipeline in order to "de-normalize" the content:
db.MYLIST.aggregate([
// If you only want those matching array members it makes sense to match the
// documents that contain them first
{ "$match": { "Img.home": 1 } },
// Unwind to de-normalize or "un-join" the documents
{ "$unwind": "$Img" },
// Match again to "filter" out those elements that do not match
{ "$match": { "Img.home": 1 } },
// Then do your grouping
{ "$group": {
"_id": "$Category",
"Name": {
"$addToSet": {
"name": "$Name",
"url": "$Url",
"img": "$Img"
}
}
}},
// Finally sort
{ "$sort": { "_id" : 1 } }
]);
So the $match pipeline is the equivalent of a general query or "where clause" in SQL terms, and can be used at any stage. It is usually best to have this as a first stage when there is some type of filtering that results from this. It reduces the overall load by reducing documents to be processed even if "all" of the end results are not removed as would be the case of working with an array.
The $unwind stage allows the array elements to be processed just like another document. And of course you can just use another $match pipeline stage after this in order to just match the documents to your query condition.