here is a typical document
{
title : 'someTitle',
places : [{name : 'someName', location : 'someLocation'}, {name ...}]
}
I have the following query
var qs = {title : 'someTitle', places : {$elemMatch : {name : 'someName' } } };
where I select a document which matches the title and which contains a document entry within its 'places' array that has name equal to 'someName'. However the issue is that the entries within the places array are large documents, and I only need a couple of fields from that document. I tried projecting the fields like so but it did not work.
var projection = {'places.$.name': 1, 'places.$.location' : 1};
Which is supposed to return an array with a document containing only the 'name' and 'location' property.
I got the following error
Can't canonicalize query: BadValue Cannot specify more than one positional proj. per query.
to be clear, I would like to accomplish this without the aggregate framework
You are doing it wrong. According to the documentation
Only one positional $ operator may appear in the projection document.
But you still need to use the $ operator to get the expected result:
var qs = { title : 'someTitle', 'places.name' : 'someName' };
var projection = {'places.$': 1 };
db.collection.find(qs, projection);
Which returns:
{
"_id" : ObjectId("564f52d7d9a433df958b5630"),
"places" : [
{
"name" : "someName",
"location" : "someLocation"
}
]
}
Also you don't need the $elemMatch operator here use "dot notation" instead.
Now if what you want is an array of "name" and "location" for each subdocument in the array then aggregation is the way to go.
db.collection.aggregate([
{ '$match': {
'title' : 'someTitle',
'places.name' : 'someName'
}},
{ '$project': {
'places': {
'$map': {
'input': '$places',
'as': 'place',
'in': {
'name': '$$place.name',
'location': '$$place.location'
}
}
}
}}
])
Which yields:
{
"_id" : ObjectId("564f52d7d9a433df958b5630"),
"places" : [
{
"name" : "someName",
"location" : "someLocation"
},
{
"name" : "bar",
"location" : "foo"
}
]
}
For the fields inside an array, you can project them the same as in embedded object
var projection = {'places.name': 1, 'places.location' : 1};
Check this guideline
https://docs.mongodb.com/manual/reference/operator/aggregation/project/#include-specific-fields-from-embedded-documents
Related
I did this in my mongodb:
db.teams.insert({name:"Alpha team",employees:[{name:"john"},{name:"david"}]});
db.teams.insert({name:"True team",employees:[{name:"oliver"},{name:"sam"}]});
db.teams.insert({name:"Blue team",employees:[{name:"jane"},{name:"raji"}]});
db.teams.find({"employees.name":/.*o.*/});
But what I got was:
{ "_id" : ObjectId("5ddf3ca83c182cc5354a15dd"), "name" : "Alpha team", "employees" : [ { "name" : "john" }, { "name" : "david" } ] }
{ "_id" : ObjectId("5ddf3ca93c182cc5354a15de"), "name" : "True team", "employees" : [ { "name" : "oliver" }, { "name" : "sam" } ] }
But what I really want is
[{"name":"john"},{"name":"oliver"}]
I'm having a hard time finding examples of this without using some kind of programmatic iterator/loop. Or examples I find return the parent document, which means I'd have to parse out the embedded array employees and do some kind of UNION statement?
Eg.
How to get embedded document in mongodb?
Retrieve only the queried element in an object array in MongoDB collection
Can someone point me in the right direction?
Please add projections to filter out the fields you don't need. Please refer the project link mongodb projections
Your find query should be constructed with the projection parameters like below:
db.teams.find({"employees.name":/.*o.*/}, {_id:0, "employees.name": 1});
This will return you:
[{"name":"john"},{"name":"oliver"}]
Can be solved with a simple aggregation pipeline.
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
])
EDIT:
OP Wants to skip the parent fields. Modified query:
db.teams.aggregate([
{$unwind : "$employees"},
{$match : {"employees.name":/.*o.*/}},
{$project : {"name":"$employees.name",_id:0}}
])
Output:
{ "name" : "john" }
{ "name" : "oliver" }
Here is my news document structure
{
"_id" : ObjectId("5bff0903bd9a221229c7c9b2"),
"title" : "Test Page",
"desc" : "efdfr",
"mediaset_list" : [
{
"_id" : ObjectId("5bfeff94bd9a221229c7c9ae"),
"medias" : [
{
"_id" : ObjectId("5bfeff83bd9a221229c7c9ac"),
"file_type" : "png",
"file" : "https://aws.com/gg.jpg",
"file_name" : "edf.jpg"
},
{
"_id" : ObjectId("5bfeff83bd9a221229c7c9ad"),
"file_type" : "mov",
"file" : "https://aws.com/gg.mov",
"file_name" : "abcd.mov"
}
]
}
]}
The queries that i've tried are given below
Approach 1
db.news.find_and_modify({},{'$pull': {"mediaset_list": {"medias": {"$elemMatch" : {"_id": ObjectId('5bfeff83bd9a221229c7c9ac')}} }}})
Approach 2
db.news.update({},{'$pull': {"mediaset_list.$.medias": {"_id": ObjectId('5bfeff83bd9a221229c7c9ac')}} })
Issue we are facing
The above queries are removing entire elements inside 'mediaset_list' . But i only want to remove the element inside 'medias' matching object ID.
Since you have two nested arrays you have to use arrayFilters to indicate which element of outer array should be modified, try:
db.news.update({ _id: ObjectId("5bff0903bd9a221229c7c9b2") },
{ $pull: { "mediaset_list.$[item].medias": { _id: ObjectId("5bfeff83bd9a221229c7c9ad") } } },
{ arrayFilters: [ { "item._id": ObjectId("5bfeff94bd9a221229c7c9ae") } ] })
So item is used here as a placeholder which will be used by MongoDB to determine which element of mediaset_list needs to be modified and the condition for this placeholder is defined inside arrayFilters. Then you can use $pull and specify another condition for inner array to determine which element should be removed.
From #micki's mongo shell query (Answer above) , This is the pymongo syntax which will update all news document with that media id .
db.news.update_many({},
{
"$pull":
{ "mediaset_list.$[item].medias": { "_id": ObjectId("5bfeff83bd9a221229c7c9ad") } } ,
},
array_filters=[{ "item._id": ObjectId("5bfeff94bd9a221229c7c9ae")}],
upsert=True)
It's one of my data as JSON format:
{
"_id" : ObjectId("5bfdb412a80939b6ed682090"),
"accounts" : [
{
"_id" : ObjectId("5bf106eee639bd0df4bd8e05"),
"accountType" : "DDA",
"productName" : "DDA1"
},
{
"_id" : ObjectId("5bf106eee639bd0df4bd8df8"),
"accountType" : "VSA",
"productName" : "VSA1"
},
{
"_id" : ObjectId("5bf106eee639bd0df4bd8df9"),
"accountType" : "VSA",
"productName" : "VSA2"
}
]
}
I want to make a query to get all productName(no duplicate) of accountType = VSA.
I write a mongo query:
db.Collection.distinct("accounts.productName", {"accounts.accountType": "VSA" })
I expect: ['VSA1', 'VSA2']
I get: ['DDA','VSA1', 'VSA2']
Anybody knows why the query doesn't work in distinct?
Second parameter of distinct method represents:
A query that specifies the documents from which to retrieve the distinct values.
But the thing is that you showed only one document with nested array of elements so whole document will be returned for your condition "accounts.accountType": "VSA".
To fix that you have to use Aggregation Framework and $unwind nested array before you apply the filtering and then you can use $group with $addToSet to get unique values. Try:
db.col.aggregate([
{
$unwind: "$accounts"
},
{
$match: {
"accounts.accountType": "VSA"
}
},
{
$group: {
_id: null,
uniqueProductNames: { $addToSet: "$accounts.productName" }
}
}
])
which prints:
{ "_id" : null, "uniqueProductNames" : [ "VSA2", "VSA1" ] }
My Document structure is
"MainAccounts" : [
{
"orgs" : "5808ba773fe315441b9e0a9e",
"_id" : ObjectId("5808bc0c3fe315441b9e0b1a"),
"accounts" : [
"5808baf33fe315441b9e0aa7",
"5808baf33fe315441b9e0aa8",
"5808baf33fe315441b9e0aa9",
"5808baf33fe315441b9e0aa1"
]
},
{
"orgs" : "5808ba773fe315441b9e0a9f",
"_id" : ObjectId("5808bc0c3fe315441b9e0b1b"),
"accounts" : [
"5808baf33f35425852s255s7",
"5808baf3sd23s2d3d4w5s2s8",
"5808baf33sd211ds2d2sdsa9",
"5808baf33dssd2d21b9e0aa1"
]
}
],
I want to pull out a particular account say "5808baf33fe315441b9e0aa8" from this, i wrote the query like this.
{ $pull: { "MainAccounts.$.accounts": "5808baf33fe315441b9e0aa8"} }
It gives only error as "The positional operator did not find the match needed from the query. Unexpanded update: MainAccounts.$.accounts"
{ $pull: { "MainAccounts.0.accounts": "5808baf33fe315441b9e0aa8" } }
If i give like this it will remove that value only which gives the expected output.
i need output as
"MainAccounts" : [
{
"orgs" : "5808ba773fe315441b9e0a9e",
"_id" : ObjectId("5808bc0c3fe315441b9e0b1a"),
"accounts" : [
"5808baf33fe315441b9e0aa7",
"5808baf33fe315441b9e0aa9",
"5808baf33fe315441b9e0aa1"
]
},
{
"orgs" : "5808ba773fe315441b9e0a9f",
"_id" : ObjectId("5808bc0c3fe315441b9e0b1b"),
"accounts" : [
"5808baf33f35425852s255s7",
"5808baf3sd23s2d3d4w5s2s8",
"5808baf33sd211ds2d2sdsa9",
"5808baf33dssd2d21b9e0aa1"
]
}
],
here i am not able to delete value from second array i need to give
{ $pull: { "MainAccounts.1.accounts": "5808baf33fe315441b9e0aa8" } }
But i need to loop through, any help is appreciated.
You will get an error:
"Cannot apply $pull to a non-array value"
This should be :
db.collection.update({'MainAccounts.accounts': '5808baf33fe315441b9e0aa8'}, {$pull: {MainAccounts:{ accounts: '5808baf33fe315441b9e0aa8'}}})
Here is a reference to this:
mongodb Cannot apply $pull/$pullAll modifier to non-array, How to remove array element
db.collection.update({someId,{$pull : {"MainAccounts":{"accounts":"5808baf33fe315441b9e0aa8"}}}})
someId could be your _id.
Remember if you have to access the document inside the array you cant access it without . operator only.You have to use the index with it.The other way mongodb can access it is by the use of braces.
This will do what you want:
db.collection.update({'MainAccounts.accounts': '5808baf33fe315441b9e0aa8'}, {$pull: {'MainAccounts.$.accounts': '5808baf33fe315441b9e0aa8'}})
Aggregate, $unwind and $group is not my solution as they make query very slow, there for I am looking to get my record by db.collection.find() method.
The problem is that I need more then one value from sub array. For example from the following example I want to get the "type" : "exam" and "type" : "quiz" elements.
{
"_id" : 22,
"scores" : [
{
"type" : "exam",
"score" : 75.04996547553947
},
{
"type" : "quiz",
"score" : 10.23046475899236
},
{
"type" : "homework",
"score" : 96.72520512117761
},
{
"type" : "homework",
"score" : 6.488940333376703
}
]
}
I am looking something like
db.students.find(
// Search criteria
{ '_id': 22 },
// Projection
{ _id: 1, scores: { $elemMatch: { type: 'exam', type: 'quiz' } }}
)
The result should be like
{ "_id": 22, "scores" : [ { "type" : "exam", "type" : "quiz" } ] }
But this over ride the type: 'exam' and returns only type: 'quiz'. Have anybody any idea how to do this with db.find()?
This is not possible directly using find and elemMatch because of following limitation of elemMatch and mongo array fields.
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition. ref. from $elemMacth
and mongo array field limitations as below
Only one positional $ operator may appear in the projection document.
The query document should only contain a single condition on the array field being projected. Multiple conditions may override each other internally and lead to undefined behavior. ref from mongo array field limitations
So either you tried following this to find out only exam or quiz
db.collectionName.find({"_id":22,"scores":{"$elemMatch":{"type":"exam"}}},{"scores.$.type":1}).pretty()
is shows only exam scores array.
Otherwise you should go through aggregation