MongoDB - Find array index of document in array field - mongodb

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

Related

MongoDB - Get rank of the document based on frequency

[
{_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.

Count the documents and sum of values of fields in all documents of a mongodb

I have a set of documents modified from mongodb using
[{"$project":{"pred":1, "base-url":1}},
{"$group":{
"_id":"$base-url",
"invalid":{"$sum": { "$cond": [{ "$eq": ["$pred", "invalid"] }, 1, 0] }},
"pending":{"$sum": { "$cond": [{ "$eq": ["$pred", "null"] }, 1, 0] }},
}},
]
to get the below documents
[{'_id': 'https://www.example1.org/', 'invalid': 3, 'pending': 6},
{'_id': 'https://example2.com/', 'invalid': 10, 'pending': 4},
{'_id': 'https://www.example3.org/', 'invalid': 2, 'pending': 6}]
How to get the count of documents and sum of other fields to obtain the following result
{"count":3, "invalid":15,"pending":16}
you just need a $group stage with $sum
playground
The $sum docs and here has good examples
db.collection.aggregate([
{
$group: {
_id: null,
pending: {
$sum: "$pending"
},
invalid: {
$sum: "$invalid"
},
count: {
$sum: 1 //counting each record
}
}
},
{
$project: {
_id: 0 //removing _id field from the final output
}
}
])

MongoDB - How to get specified values of an array in a document

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

MongoDB: how to find documents of same type with min value and $lt value

I have the following docs in collection:
{'type': 1, 'price': 150, 'quantity': 10}
{'type': 1, 'price': 90, 'quantity': 5}
{'type': 2, 'price': 150, 'quantity': 10}
{'type': 2, 'price': 90, 'quantity': 10}
{'type': 2, 'price': 100, 'quantity': 5}
How could i find a doc of each type with min price and quantity lower than 10?
i need to query and get:
{'type': 1, 'price': 90, 'quantity': 5}
{'type': 2, 'price': 100, 'quantity': 5}
thanks
You could try this pipeline:
Match document with a quantity lower than 10.
Group the first document by type.
Deconstructs the docs array
Replace its root.
.
db.collection.aggregate([
{
"$match": {
"quantity": {
"$lt": 10
}
}
},
{
"$group": {
"_id": "$type",
"docs": {
"$first": "$$ROOT"
}
}
},
{
"$unwind": "$docs"
},
{
"$replaceRoot": {
"newRoot": "$docs"
}
}
])
Output:
[
{
"price": 100,
"quantity": 5,
"type": 2
},
{
"price": 90,
"quantity": 5,
"type": 1
}
]

Querying Array of Embedded Documents in MongoDB based on Range

How can I query array of embedded document in mongodb.
{
_id: 1,
zipcode: "63109",
students: [
{ name: "john", school: 102, age: 14 },
{ name: "jess", school: 102, age: 11 },
{ name: "jeff", school: 108, age: 15 }
]
}
{
_id: 2,
zipcode: "63110",
students: [
{ name: "ajax", school: 100, age: 7 },
{ name: "achilles", school: 100, age: 8 },
]
}
{
_id: 3,
zipcode: "63109"
}
Let's say for the above data how will retrieve only those lines where age>=7 and age<=10. For id_1 only the row with age 10 should be returned. Both the rows in id_2 and id_3. Also, id_4 has no field called students. So, I DO NOT, want to see it in the output.
Edit:-
The output I get looks something like this when I do a filter. But I DO NOT want the row that has "None".
The last document _id: 3 does not have the field students but filtering is done on students, so the output has a "None" corresponding to it.
I wish to handle 2 cases:
No filtering should be applied where the array "students" don't exist under an id.
Array students exists but is empty []
Both these cases end up in the output if simply filtered.
{'_id': ObjectId('5cdaefd393436906b016ddb4'),
'students': [{'name': ajax,
'school': '100',
'age': '7'},
{'name': achilles,
'school': '100',
'age': '8'}],
{'_id': ObjectId('5cdaefd393436906b016ddb3'), 'students': None}
Note: I am using a different but identical data set, so the output is not exact, but I hope you get the idea. The 'student' is an embedded document which may or may not be present in all documents.
Edit : Finally I used unwind to achieve what I wanted.
You can use below aggregation
db.collection.aggregate([
{ "$match": { "$expr": { "$gte": [{ "$size": { "$ifNull": ["$students", []] } }, 1] }}},
{ "$addFields": {
"students": {
"$filter": {
"input": { "$ifNull": ["$students", []] },
"cond": {
"$and": [
{ "$gte": ["$$this.age", 7] },
{ "$lte": ["$$this.age", 10] }
]
}
}
}
}}
])