Here is the document
{
'_id': ObjectId('61a4262a53ddaa8b93374613'),
'userid': 'renyi',
'data1': [{'a': 1}, 1, 2, 3],
'data2': [{'c': 1}, {'b': 2}, {'c': 3}, {'c': 2}],
'data': {'0': {'a': 1}}
}
With this
coll.find_one({'userid':'renyi','data2.c':1},{'_id':0,"data2.$":1})
I can get
{'data2': [{'c': 1}]}
But how to get
{'data2': [{'c': 1},{'c': 2}]}
You can use $filter to filter the element within the array in projection.
db.collection.find({
"userid": "renyi",
"data2.c": {
$in: [
1,
2
]
}
},
{
"_id": 0,
"data2": {
$filter: {
input: "$data2",
cond: {
$in: [
"$$this.c",
[
1,
2
]
]
}
}
}
})
Sample Mongo Playground
Related
[
{_id: 1, query: 'A', createdAt: 1660610671 },
{_id: 2, query: 'A', createdAt: 1660610672 },
{_id: 3, query: 'A', createdAt: 1660610673 },
{_id: 4, query: 'A', createdAt: 1660610674 },
{_id: 5, query: 'B', createdAt: 1660610675 },
{_id: 6, query: 'C', createdAt: 1660610676 },
{_id: 7, query: 'C', createdAt: 1660610677 },
{_id: 8, query: 'C', createdAt: 1660610678 },
{_id: 9, query: 'D', createdAt: 1660610680 },
{_id: 10, query: 'D', createdAt: 1660610681 },
]
I have the above database structure. I want to get rank from the frequency of the query value in a specific period.
Maybe it would be something like this.
Queries.getRank({ key: 'query', createdAt: {$gte: startUnix, $lt: endUnix } })
I expect the result as below.
Rank
[
{rank: 1, query: 'A', frequency: 4},
{rank: 2, query: 'C', frequency: 3},
{rank: 3, query: 'D', frequency: 2},
{rank: 4, query: 'B', frequency: 1}
]
Is there a way to achieve it? Thanks.
$match - Filter document within the range for createdAt field (if needed).
$group - Group by query and perform $count as frequency.
$project - Decorate the output document(s).
$setWindowFields - With $rank to perform ranking by sorting frequency descending. May consider $denseRank for the document with the same rank.
db.collection.aggregate([
// $match stage
{
$group: {
_id: "$query",
frequency: {
$sum: 1
}
}
},
{
$project: {
_id: 0,
query: "$_id",
frequency: "$frequency"
}
},
{
$setWindowFields: {
partitionBy: null,
sortBy: {
frequency: -1
},
output: {
rank: {
$rank: {}
}
}
}
},
])
Demo # Mongo Playground
You can write the following aggregation pipeline:
db.collection.aggregate([
{
"$group": {
"_id": "$query",
"frequency": {
"$sum": 1
}
}
},
{
"$project": {
"query": "$_id",
"frequency": 1,
"_id": 0
}
},
{
"$sort": {
frequency: -1
}
},
{
"$group": {
"_id": null,
"array": {
"$push": "$$ROOT"
}
}
},
{
"$unwind": {
path: "$array",
"includeArrayIndex": "rank"
}
},
{
"$project": {
_id: 0,
rank: {
"$add": [
"$rank",
1
]
},
frequency: "$array.frequency",
query: "$array.query"
}
}
]);
Playground link.
In this, we first calculate the frequency for each query, then we sort it by the frequency, and finally, we push all documents in an array and calculate the rank, using array index.
I have aggregation that contains array field. This array field contains documents (objects). For these I have some matching criteria and I would like create a new fields named lowestCheapIndex and highestExpensiveIndex, each with array index of matching element.
Matching criteria:
lowestCheapIndex - Should contain lowest array index number of any record item, that has price below 20.
highestExpensiveIndex - Should contain highest array index number of any record item, that has price over 30.
My current aggregation output:
{
'_id': 'Egg shop',
'records': [
{'_id': 1, 'price': 22},
{'_id': 2, 'price': 18},
{'_id': 3, 'price': 34},
{'_id': 4, 'price': 31},
{'_id': 5, 'price': 13},
]
}
Desired output:
{
'_id': 'Egg shop',
'records': [
{'_id': 1, 'price': 22},
{'_id': 2, 'price': 18},
{'_id': 3, 'price': 34},
{'_id': 4, 'price': 31},
{'_id': 5, 'price': 13},
],
'lowestCheapIndex': 1,
'highestExpensiveIndex': 3,
}
Question:
How can i retrieve array index based on my criteria? I found $indexOfArray in docs, but still I am having hard time how it would be used in my case.
You can do following in an aggregation pipeline:
use $map to augment your records array with booleans indicating below 20 and over 30
use $indexOfArray to search for the booleans; For highestExpensiveIndex, reverse the array first to get the index then subtract it from size of array - 1 to get the expected index.
db.collection.aggregate([
{
"$addFields": {
"records": {
"$map": {
"input": "$records",
"as": "r",
"in": {
"_id": "$$r._id",
"price": "$$r.price",
"below20": {
$lt: [
"$$r.price",
20
]
},
"over30": {
$gt: [
"$$r.price",
30
]
}
}
}
}
}
},
{
"$addFields": {
"lowestCheapIndex": {
"$indexOfArray": [
"$records.below20",
true
]
},
"highestExpensiveIndex": {
"$subtract": [
{
"$subtract": [
{
$size: "$records"
},
{
"$indexOfArray": [
{
"$reverseArray": "$records.over30"
},
true
]
}
]
},
1
]
}
}
}
])
Mongo playground
I have a set of documents of the form:
{
skill_id: 2,
skill_recs: [
{
_id: 4,
member_ids: [1, 4, 5]
}
]
},
{
skill_id: 5,
skill_recs: [
{
_id: 4,
member_ids: [1, 7, 9]
}
]
}
Now I want to aggregate a set of these documents such that skill_recs are combined by _id and the member_ids of all combined docs are merged into a single union of values...
{ _id: 4,
member_ids: [1, 4, 5, 7, 9]
}
I get most of the way with:
db.aggregate([
{
$unwind: '$skill_recs'
},
{
$group: {
_id: '$skill_recs._id',
all_member_ids: {$push: '$skill_recs.member_ids'}
}
},
{
$addFields: {
member_ids: {$setUnion: '$all_member_ids'}
}
}
])
but the $setUnion doesn't do a union of the array of arrays that it is passed.
Instead it produces:
{ _id: 4,
member_ids: [[1, 4, 5], [1, 7, 9]]
}
Any way to produce the union of these arrays?
You're quite close, Here's a quick example of how to achieve this using $reduce
db.collection.aggregate([
{
$unwind: "$skill_recs"
},
{
$group: {
_id: "$skill_recs._id",
all_member_ids: {
$push: "$skill_recs.member_ids"
}
}
},
{
$addFields: {
member_ids: {
$reduce: {
input: "$all_member_ids",
initialValue: [],
in: {
$setUnion: [
"$$this",
"$$value"
]
}
}
}
}
}
])
Mongo Playground
I currently have a MongoDB aggregation pipeline that ends with the following type of synthetic documents
[
{
'_id': '2019-09-10',
'grouped_foos':
[
{... 'foo': [1, 78, 100]},
{... 'foo': [8, 66, 98]},
{... 'foo': [99, 5, 33]},
{... 'foo': [120, 32, 2]}
]
},
{
'_id': '2019-09-09',
'grouped_foos':
[
{... 'foo': [10, 27]},
{... 'foo': [19, 66]}
]
},
{
'_id': '2019-09-08',
'grouped_foos':
[
{... 'foo': [1]}
]
}
]
I would like to continue this pipeline and average the indices of the foo lists together to form documents that look like
[
{
'_id': '2019-09-10',
'avg_foo': [57, 45.25, 58.25]
},
{
'_id': '2019-09-09',
'avg_foo': [14.5, 46.5]
},
{
'_id': '2019-09-08',
'avg_foo': [1]
}
]
Is this type of averaging possible during aggregation? Do I potentially need to $unwind the lists with indexing and assign new _id for uniqueness to make documents that look like
[
{
'_id': UUID,
'old_id': '2019-09-10',
'foo': 1,
'index': 0
},
{
'_id': UUID,
'old_id': '2019-09-10',
'foo': 78,
'index': 1
},
........
]
Basically you can try with $unwind but easier and faster approach would be to use $reduce to $map and $sum all the rows from grouped_foos. Then you'll be able to run another $map and use $divide to get the average.
db.collection.aggregate([
{
$project: {
size: { $size: "$grouped_foos" },
foo_sum: {
$reduce: {
input: "$grouped_foos",
initialValue: [],
in: {
$map: {
input: { $range: [ 0, { $size: "$$this.foo" }, 1 ] },
as: "index",
in: {
$add: [
{ $arrayElemAt: [ "$$this.foo", "$$index" ] },
{ $ifNull: [ { $arrayElemAt: [ "$$value", "$$index" ] }, 0 ] }
]
}
}
}
}
}
}
},
{
$project: {
_id: 1,
avg_foo: {
$map: {
input: "$foo_sum",
in: {
$divide: [ "$$this", "$size" ]
}
}
}
}
}
])
Mongo Playground
I have a large set of documents that may have two arrays or one of the two. I want to merge them in a $project.
I am currently using $concatArrays but as the documentation says it returns null when one of the arrays is null. I can figure out how to add a condition statement in there that will either return the $concatArrays or what ever array is in there.
Example
I have:
{_id: 1, array1: ['a', 'b', 'b'], array2: ['e', 'e']}
{_id: 2, array1: ['a', 'b', 'b']}
{_id: 3, array2: ['e', 'e']}
I want:
{_id: 1, combinedArray: ['a','b', 'b', 'e', 'e']}
{_id: 2, combinedArray: ['a','b', 'b']}
{_id: 3, combinedArray: ['e', 'e']}
I tried:
$project: {
combinedArray: { '$concatArrays': [ '$array1', '$array2' ] }
}
//output (unexpected result):
{_id: 1, combinedArray: ['a','b', 'b', 'e', 'e']}
{_id: 2, combinedArray: null}
{_id: 3, combinedArray: null}
I also tried:
$project: {
combinedArray: { '$setUnion': [ '$array1', '$array2' ] }
}
//output (unexpected result):
{_id: 1, combinedArray: ['a','b', 'e']}
{_id: 2, combinedArray: ['a','b']}
{_id: 3, combinedArray: ['e']}
As documentation for $concatArrays says
If any argument resolves to a value of null or refers to a field that
is missing, $concatArrays returns null.
So we need to be sure that we are not passing arguments which refer to a missing field or null. You can do that with $ifNull operator:
Evaluates an expression and returns the value of the expression if the
expression evaluates to a non-null value. If the expression evaluates
to a null value, including instances of undefined values or missing
fields, returns the value of the replacement expression.
So just return empty array if filed expression will not evaluate to non-null value:
db.collection.aggregate([
{$project: {
combinedArray: { '$concatArrays': [
{$ifNull: ['$array1', []]},
{$ifNull: ['$array2', []]}
] }
}
}
])
You can easily achieve this with the $ifNull operator:
db.arr.aggregate([
{
$project:{
combinedArray:{
$concatArrays:[
{
$ifNull:[
"$array1",
[]
]
},
{
$ifNull:[
"$array2",
[]
]
}
]
}
}
}
])
output:
{ "_id" : 1, "combinedArray" : [ "a", "b", "b", "e", "e" ] }
{ "_id" : 2, "combinedArray" : [ "a", "b", "b" ] }
{ "_id" : 3, "combinedArray" : [ "e", "e" ] }
I tried to do this with nested $cond, answer with $ifNull is better, but still posting my answer.
db.getCollection('test').aggregate( [{
$project: {
combinedArray: { $cond: [
{ $and: [ { $isArray: ["$array1"] }, { $isArray: ["$array2"] } ] },
{ '$concatArrays': [ '$array1', '$array2' ] },
{ $cond: [
{ $isArray: ["$array1"] },
"$array1",
"$array2"
] }
] }
}
}] )