Consider following collection in mongoDB :
{a:[4,2,8,71,21]}
{a:[24,2,2,1]}
{a:[4,1]}
{a:[4,2,8,21]}
{a:[2,8,71,21]}
{a:[4,2,8]}
How can I get following results in a most easily:
Getting nth element of array
{a:4}
{a:24}
{a:4}
{a:4}
{a:2}
{a:4}
Getting elements 2 to 4
{a:[8,71,21]}
{a:[2,1]}
{a:[]}
{a:[8,21]}
{a:[71,21]}
{a:[8]}
And other similar queries.
What you are looking for is the $slice projection.
Getting a number of elements from the beginning of an array
You can pass a simple $limit with a number of values to return (eg. 1):
> db.mycoll.find({}, {_id: 0, a: { $slice: 1}})
{ "a" : [ 4 ] }
{ "a" : [ 24 ] }
{ "a" : [ 4 ] }
{ "a" : [ 4 ] }
{ "a" : [ 2 ] }
{ "a" : [ 4 ] }
Getting a range of elements
You can pass an array with parameters of ( $skip, $limit ).
Note: to match your expected output you would have to find elements 3 to 5 (skip the first 2 elements, return the next 3):
> db.mycoll.find({}, {_id: 0, a: { $slice: [2,3]}})
{ "a" : [ 8, 71, 21 ] }
{ "a" : [ 2, 1 ] }
{ "a" : [ ] }
{ "a" : [ 8, 21 ] }
{ "a" : [ 71, 21 ] }
{ "a" : [ 8 ] }
Getting the nth element of array
Pass the number of elements to $skip and a value of 1 for the limit.
For example, to find the second element you need to skip 1 entry:
> db.mycoll.find({}, {_id: 0, a: { $slice: [1,1]}})
{ "a" : [ 2 ] }
{ "a" : [ 2 ] }
{ "a" : [ 1 ] }
{ "a" : [ 2 ] }
{ "a" : [ 8 ] }
{ "a" : [ 2 ] }
Note that the $slice operator:
always returns an array
will return an empty array for documents that match the find criteria but return an empty result for the $slice selection (eg. if you ask for the 5th element of an array with only 2 elements)
Related
This question already has answers here:
Retrieve only the queried element in an object array in MongoDB collection
(18 answers)
Closed 5 years ago.
The community reviewed whether to reopen this question 4 months ago and left it closed:
Original close reason(s) were not resolved
I have array in subdocument like this
{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 1
},
{
"a" : 2
},
{
"a" : 3
},
{
"a" : 4
},
{
"a" : 5
}
]
}
Can I filter subdocument for a > 3
My expect result below
{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 4
},
{
"a" : 5
}
]
}
I try to use $elemMatch but returns the first matching element in the array
My query:
db.test.find( { _id" : ObjectId("512e28984815cbfcb21646a7") }, {
list: {
$elemMatch:
{ a: { $gt:3 }
}
}
} )
The result return one element in array
{ "_id" : ObjectId("512e28984815cbfcb21646a7"), "list" : [ { "a" : 4 } ] }
and I try to use aggregate with $match but not work
db.test.aggregate({$match:{_id:ObjectId("512e28984815cbfcb21646a7"), 'list.a':{$gte:5} }})
It's return all element in array
{
"_id" : ObjectId("512e28984815cbfcb21646a7"),
"list" : [
{
"a" : 1
},
{
"a" : 2
},
{
"a" : 3
},
{
"a" : 4
},
{
"a" : 5
}
]
}
Can I filter element in array to get result as expect result?
Using aggregate is the right approach, but you need to $unwind the list array before applying the $match so that you can filter individual elements and then use $group to put it back together:
db.test.aggregate([
{ $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
{ $unwind: '$list'},
{ $match: {'list.a': {$gt: 3}}},
{ $group: {_id: '$_id', list: {$push: '$list.a'}}}
])
outputs:
{
"result": [
{
"_id": ObjectId("512e28984815cbfcb21646a7"),
"list": [
4,
5
]
}
],
"ok": 1
}
MongoDB 3.2 Update
Starting with the 3.2 release, you can use the new $filter aggregation operator to do this more efficiently by only including the list elements you want during a $project:
db.test.aggregate([
{ $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
{ $project: {
list: {$filter: {
input: '$list',
as: 'item',
cond: {$gt: ['$$item.a', 3]}
}}
}}
])
$and:
get data between 0-5:
cond: {
$and: [
{ $gt: [ "$$item.a", 0 ] },
{ $lt: [ "$$item.a", 5) ] }
]}
Above solution works best if multiple matching sub documents are required.
$elemMatch also comes in very use if single matching sub document is required as output
db.test.find({list: {$elemMatch: {a: 1}}}, {'list.$': 1})
Result:
{
"_id": ObjectId("..."),
"list": [{a: 1}]
}
Use $filter aggregation
Selects a subset of the array to return based on the specified
condition. Returns an array with only those elements that match the
condition. The returned elements are in the original order.
db.test.aggregate([
{$match: {"list.a": {$gt:3}}}, // <-- match only the document which have a matching element
{$project: {
list: {$filter: {
input: "$list",
as: "list",
cond: {$gt: ["$$list.a", 3]} //<-- filter sub-array based on condition
}}
}}
]);
{
"_id" : 123,
"a" : [
{
"b" : 1,
"bb" : 2
},
{
"c" : 2,
"cc" : 3
}
],
"ab" : [
{
"d" : 4,
"dd" : 5
},
{
"e" : 5,
"ee" : 6
}
]
}
Need to remove mongo specific nested document in array for each document
Output should be like: based on inputs _id:123,ab.d=4
{
"_id" : 123,
"a" : [
{
"b" : 1,
"bb" : 2
},
{
"c" : 2,
"cc" : 3
}
],
"ab" : [
{
"e" : 5,
"ee" : 6
}
]
}
Your are looking for an update with $pull operator (https://docs.mongodb.com/manual/reference/operator/update/pull/)
In your case:
db.mycollection.update({"_id":123}, {$pull: {"ab":{"d":4}}})
I have a collection with array elements containing A,B or C values. I have to calculate a weight of each element value.
The logic of this weight is sample :
We give 1.0 as weight of the last element (dfferente to C), and others 0
If the last element of the array is C, we give it to the previous last element (different to C).
if there is 1 element in the array (A,B or C) we give it 1 as a weight.
This is how my collection looking like :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C"
},
{
"value" : "A
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "A"
},
{
"value" : "A"
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"myArray" : [
{
"value" : "A"
},
{
"value" : "C"
},
{
"value" : "B"
},
{
"value" : "C"
},
{
"value" : "C"
}
]
}
I did some aggregation to make it happen but I can here just to affect 1 to the last (different to C)
and (if the last is C I give 1 to the last one -1) so I have to fixe the case of having C in the last and last-1 and last -2 .. last-n
so I have to fix it to affect 1 to the last one different to C
db.col.aggregate([{'$addFields':{
'myArray_temp':{
'$switch':{
'branches':[{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$eq':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},2]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-2]},'weight':1}],
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':0}]
]}
},
{
'case':{'$eq':[{'$size':'$myArray'},1]},
'then':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
},
{
'case':{'$and':[{'$gt':[{'$size':'$myArray'},1]},{'$ne':[{'$arrayElemAt':['$myArray.value',-1]},'C']}]},
'then':{'$concatArrays':[
{'$map':{
'input':{'$slice':['$myArray',{'$subtract':[{'$size':'$myArray'},1]}]},
'as':'val',
'in':{'value':'$$val.value','weight':0 }
}},
[{'value':{'$arrayElemAt':['$myArray.value',-1]},'weight':1}]
]}
}
],
'default':{'$concatArrays':[
[{'value':{'$arrayElemAt':['$myArray.value',0]},'weight':1}]
]}
}
}
}}])
The results should be :
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8618"),
"myArray" : [
{
"value" : "C",
"weight": 0
},
{
"value" : "A" ,
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 0
},
{
"value" : "A",
"weight": 0
},
{
"value" : "A",
"weight": 1
}
]
}
{
"_id" : ObjectId("5a535c48a4d86ed94a7e8619"),
"total" : 4.5,
"myArray" : [
{
"value" : "A",
"weight": 0
},
{
"value" : "C",
"weight": 0
},
{
"value" : "B" ,
"weight": 1 // here we give 1 to the last differente to C
},
{
"value" : "C" ,
"weight": 0 // my code affect 1 here cause it find C in the last and affect to the last-1 element.
},
{
"value" : "C" ,
"weight": 0
}
]
}
We have to skip all the last (C) elements and give the 1 weight to the last not-C element.
Thank you in advance!
Longest aggregation in my career but seems to be working:
db.collection.aggregate([
{
$addFields : {
size: { $size: "$myArray" },
reversed: {
$reverseArray: "$myArray"
}
}
},
{
$addFields: {
otherThanC: {
$filter: {
input: "$reversed",
as: "item",
cond: { $ne: [ "$$item.value", "C" ] }
}
}
}
},
{
$addFields: {
firstOtherThanCIndex : {
$indexOfArray: [ "$reversed", { $arrayElemAt: [ "$otherThanC", 0 ] } ]
}
}
},
{
$unwind: {
path: "$reversed",
includeArrayIndex: "arrayIndex"
}
},
{
$addFields: {
weight: {
$switch: {
branches: [
{ case: { $eq: [ "$size", 1 ] }, then: 1 },
{ case: { $and: [ { $eq: [ "$arrayIndex", 0 ] }, { $eq: [ { $size: "$otherThanC" }, 0 ] } ] } , then: 1},
{ case: { $eq: [ "$arrayIndex", "$firstOtherThanCIndex" ] }, then: 1 }
],
default: 0
}
}
}
},
{
$group: {
_id: "$_id",
myArrayReversed: {
$push: {
value: "$reversed.value",
weight: "$weight"
}
}
}
},
{
$project: {
_id: 1,
myArray: { $reverseArray: "$myArrayReversed" }
}
}
])
Brief description of each pipeline stage:
We need to add two extra fields: $size of and array and second array with reversed items
Second and third steps are to find first (last) item not equal to C using $filter and $arrayElemAt which returns first matching index
Then we can $unwind our reversed array using special syntax which adds index of array when unwinding
That is the moment when we can calculate weight using $switch - simply setting 1 if array has one element or indexes are matching and zero otherwise
Then we just need to reshape the data: grouping back by _id and reversing the array
My Document Structure:
{
"_id" : ObjectId("59edc58af33e9b5988b875fa"),
"Agent" : {
"Name" : "NomanAgent",
"Location" : "Lahore",
"AgentId" : 66,
"Reward" : "Thumb Up",
"Suggestion" : [
"Knowledge",
"Professionalisn"
]
}
}
What I want to achieve in this query:
I want to find the count of each suggestion given by a customer to every agent, it should look something like,
{
"AgentName": "Xyz",
"SuggestionCounts": {
"Knowledge": 2,
"Professionalism": 3,
"Friendliness": 1
}
}
What I have done so far,
db.getCollection('_survey.response').aggregate([
{
$group:{
_id: "$Agent.Name",
Suggestions: {$push:"$Agent.Suggestion"}
}
}
]);
Output:
/* 1 */
{
"_id" : "GhazanferAgent",
"Suggestions" : [
[
"Clarity",
"Effort"
],
[
"Friendliness"
]
]
}
/* 2 */
{
"_id" : "NomanAgent",
"Suggestions" : [
[
"Knowledge",
"Professionalisn"
]
]
}
How I want it to be(As Suggestion in the document is an array and when when i group documents by Agent.Name so it become array of arrays as shown in my output, it want to merge all arrays into single with duplication and then i will find the count of each element in array):
/* 1 */
{
"_id" : "GhazanferAgent",
"SuggestionsCombined" : [
[
"Clarity",
"Effort",
"Friendliness"
]
]
}
/* 2 */
{
"_id" : "NomanAgent",
"SuggestionsCombined" : [
[
"Knowledge",
"Professionalisn"
]
]
}
Thanks in advance!!
One way would be like this - the output structure is not identical to what you suggested but probably close enough:
db.getCollection('_survey.response').aggregate([
{
$unwind: "$Agent.Suggestion" // flatten "Suggestion" array
}, {
$group:{ // group by agent and suggestion
_id: { "AgentName": "$Agent.Name", "Suggestion": "$Agent.Suggestion" },
"Count": { $sum: 1} // calculate count of occurrencs
}
}, {
$group:{
_id: "$_id.AgentName", // group by agent only
"Suggestions": { $push: { "Suggestion": "$_id.Suggestion", "Count": "$Count" } } // create array of "Suggestion"/"Count" pairs per agent
}
}
]);
I have collection path_test with 2 documents in it
Document 1
{
"_id" : 1,
"tpc" : 5,
"path" : [
{
"nids" : [ 0, 10, 11 ],
"ctc" : 2
},
{
"nids" : [ 0, 10 ],
"ctc" : 2
},
{
"nids" : [ 0, 10, 21 ],
"ctc" : 1
}
]
}
Document 2
{
"_id" : 2,
"tpc" : 5,
"path" : [
{
"nids" : [ 0, 10, 110 ],
"ctc" : 1
},
{
"nids" : [ 0, 10, 11 ],
"ctc" : 2
},
{
"nids" : [ 0, 5 ],
"ctc" : 2
}
]
}
What I'm trying to get as a result are documents with path array in which all elements have nids like [0, 10, *]. Order is important, so [10, 0, *] will be wrong.
It should find Document 1, but not Document 2. Was hoping I can resolve this with a query, before I start using map-reduce or aggregation.
This is what I've tried so far
Query1
db.getCollection('path_test').find( {
"path": { $not: { $elemMatch: { "nids.0": { $nin: [0] }, "nids.1": { $nin: [10] } } } }
});
Query 2
db.getCollection('path_test').find( {
"path.nids": { $not: { $elemMatch: { $nin: [0, 10] } } }
});
but both queries give me results where only 0 is in or where only 10 is in, but I need both and in that exact order.
Is that possible?
not at least one means noone
Query 1
For simplification, lets assign
A = "nids.0": { $ne: 0 }
B = "nids.1": { $ne: 10 }
C = { A, B }
then
{ "path" : { $elemMatch: C } }
will find documents where at least one element in path array satisfies condition C, while
{ "path" : { $not: { $elemMatch: C } } }
will find documents where there are no element in path array that satisfies condition C.
Document 1 and Document 2 don't have elements in their path arrays that satisfy condition C, thus the Query1 output contains both of them. If, f.e, you add to the path array of the Document 1
{ "nids": [ 1, 11, 110], "ctc" : 1 }
then Document 1 will not be in the output of Query 1 becase this added element satisfies C.
Query 2
For simplification, lets assign
C = { $nin: [0, 10] }
then
{ "path.nids" : { $not: { $elemMatch: C } } }
will find documents where there are no element in path.nids array that satisfies condition C.
Document 1 and Document 2 in their path.nids arrays have elements that satisfy condition C, thus the Query 2 output contains neither of them. If, f.e, you add to you collection document
{ "_id" : 6, "tpc" : 5, "path" : [ { "nids" : [ 0, 10 ], "ctc" : 1 } ] }
then it will be in the output of Query 2 because in path.nids array there are no elements that satisfy C.
Solution
In Query 1 replace
{ $elemMatch: { "nids.0": { $nin: [0] }, "nids.1": { $nin: [10] } } }
with
{ $elemMatch: { $or: [ { "nids.0": { $ne: 0 } }, { "nids.1": { $ne: 10 } } ] } }
This new Query will find documents where there are no element in path array that satisfies at least one of conditions A and B. So, it will find Document 1, but not Document 2 (where "nids" : [ 0, 5 ] does not satisfy condition B.
Note that { $ne: 10 } is equivalent to { $nin: [10] }.