Sum from array object value by mongodb - mongodb

I am trying to solve a problem. I want to write a query that finds a document among my documents which one is greater by the sum of columns A and B's in an array. I write an example down here. I am new to MongoDB and I've been searching a lot but I could not find my solution. So can somebody help me to solve this problem? Here are my sample documents:
document1:
{
"_id" : "1",
"array": [
{
"user": "1",
"A": 2,
"B": 0
},
{
"user": "2",
"A": 3,
"B": 1},
{
"user": "3",
"A": 0,
"B": 5
}
]
}
and document 2:
{
"_id" : "2",
"array": [
{
"user": "4",
"A": 1,
"B": 1
},
{
"user": "5",
"A": 2,
"B": 2
}
]
}
for example, the sum of A and B's in all elements of an array in document 1 is 11 and the sum of A and B's in elements of an array in document 2 is 6. So I want to get document 1 for output because it is greater than 2 after summing all A and B's in all of the elements.

You can try this query:
Create an auxiliar field called total (or whatever name you want) and $add values. This add the $sum of the arrays. That means here you are adding all values from A and B together.
Then sort by the auxiliar field to get the greatest at first position
$limit to only one (the greatest)
And $project to not output the auxiliar field.
db.collection.aggregate([
{
"$addFields": {
"total": {
"$add": [
{
"$sum": "$array.A"
},
{
"$sum": "$array.B"
}
]
}
}
},
{
"$sort": {
"total": -1
}
},
{
"$limit": 1
},
{
"$project": {
"total": 0
}
}
])
Example here

Related

MongoDB query on specific child

I've got a document that contains an array of elements. I'd like to find specific entries in that document.
Example entry 1:
{
"c": [
{
"k": "1",
"v": "1",
},
{
"k": "2",
"v": "2",
},
]
}
Example entry 2:
{
"c": [
{
"k": "1",
"v": "2",
},
{
"k": "2",
"v": "1",
},
]
}
I'd like to find all entries that have a k that is 1, and a matching v that is 1 (here, the first entry matches, but the second one doesn't, because the v that has the value of 1 is not the same object as the k valued 1).
So far, I found the query:
{
"$and": [
{"c.k": "1"},
{"c.v": "1"}
]
}
However, that returns both entries, not just the first one. Is there a way to tell MongoDB that both constraints should apply to the same item in the array, and not just to any item?
As suggested by #turiviashal, make use of the $elemMatch operator which matches documents with will match all the key conditions in at least one object.
db.collection.aggregate([
{
"$match": {
"c": {
"$elemMatch": {
"k": "1",
"v": "1",
}
}
}
}
])
Mongo Playground Example

How to add calculated fields inside subdocuments without using unwind?

I'm looking for a simple solution to add a field to a subdocument without using $unwind and $group.
I need only to calculate the sum and the size of a nested subdocuments and show it in a new field.
This is my starting collection:
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3]
},
{
"b": 2
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6]
},
{
"b": 2
},
{
"c": 3,
"subArray": [8,8,8]
}
]
}
And this is my desired result, where I've added sum (sum of subArray) and size (number of elements in subArray):
{
"_id": ObjectId("5a934e000102030405000000"),
"subDoc": [
{
"a": 1,
"subArray": [1,2,3],
"sum": 6,
"size": 3
},
{
"b": 2
"sum": 0,
"size": 0
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"subDoc": [
{
"a": 1,
"subArray": [4,5,6],
"sum": 15,
"size": 3
},
{
"b": 2,
"sum": 0,
"size": 0
},
{
"c": 3,
"subArray": [8,8],
"sum": 16,
"size": 2
}
]
}
I know how to obtain this result using $unwind and then $group, but I'd like to know if there is any other way (or a better way!) to achieve the same result. I've tried using $addFields and $map without success.
Working playground example: https://mongoplayground.net/p/fK8t6SLlOHa
$map to iterate loop of subDoc array
$sum to get total of subArray array of numbers
$ifNull to check if field is not present or null then return empty array because $size operator only allows array input
$size to get total elements in subArray array
$mergeObjects to merge current object with new added fields
db.collection.aggregate([
{
$addFields: {
subDoc: {
$map: {
input: "$subDoc",
in: {
$mergeObjects: [
"$$this",
{
sum: { $sum: "$$this.subArray" },
size: {
$size: { $ifNull: ["$$this.subArray", []] }
}
}
]
}
}
}
}
}
])
Playground

Find documents matching a condition depending on another document value

My collection contains the following documents:
{
"_id": "a",
"index": 1
},
{
"_id": "b",
"index": 2
},
{
"_id": "c",
"index": 3
},
{
"_id": "c",
"index": 4
}
Given an id, I would like to find all the documents having a greater index that the index corresponding to the id.
For example, if id="b", then index=2 and result would be
{
"_id": "c",
"index": 3
},
{
"_id": "c",
"index": 4
}
I thougt I could use an aggregation pipeline and use $add_field to add searched index into each document and the use a $match, but cannot find how to do it. I mean, my problem would be solved if I cold produce this result:
{
"_id": "a",
"index": 1,
"ref_index": 2
},
{
"_id": "b",
"index": 2,
"ref_index": 2
},
{
"_id": "c",
"index": 3,
"ref_index": 2
},
{
"_id": "c",
"index": 4,
"ref_index": 2
}
I am not sure is there any straight way to handle this operation, you need to do 2 queries or you can try below aggregation pipeline,
$facet to separate results, getIndex to get matching document of _id: "b", allDocs to get all documents
$filter to iterate loop of allDocs and filter document by index greater than condition
$unwind deconstruct allDocs array
$replaceRoot to replace allDocs object to root
db.collection.aggregate([
{
$facet: {
getIndex: [{ $match: { _id: "b" } }],
allDocs: [{ $match: {} }]
}
},
{
$project: {
allDocs: {
$filter: {
input: "$allDocs",
cond: {
$gt: [
"$$this.index",
{ $first: "$getIndex.index" }
]
}
}
}
}
},
{ $unwind: "$allDocs" },
{ $replaceRoot: { newRoot: "$allDocs" } }
])
Playground

Return array length instead of elements in MongoDB query

I want to be able to specify in my MongoDB query to, for each document, return the number of elements in a specific sub field (var_2 in this case), instead of the whole array. Example document:
{
"_id": "abc123",
"var_1": "A",
"var_2": [
"A",
"B",
"C"
]
}
I have tried this but it returns the whole array:
db.collection.find({var_1: "A"}, {var_1: 1, var_2: 1})
Desired output:
{
"_id": "abc123",
"var_1": "A",
"var_2": 3
}
Thanks in advance!
You can retrieve the size of your array by using $size but with an aggregation since it's not supported in find' projection:
db.your_collection.aggregate([
{
"$match": {
var_1: "A"
}
},
{
"$project": {
"var_1": 1,
"var_2": {
"$size": "$var_2"
}
}
}
])
Result
[
{
"_id": "abc123",
"var_1": "A",
"var_2": 3
}
]

Using two date comparisons instead of date range query gives different results in MongoDB

With objects like this:
{
"status" : 1,
"date" : ISODate("2014-02-13T13:00:31.233Z")
}
Why does this range query
db.collection.find({
"status": 1,
"date": {
$lte: ISODate("2015-06-16T20:05:03.000Z"),
$gte: ISODate("2015-06-15T20:05:03.000Z")
}
});
give the correct result while these two date comparisons give a different result:
db.collection.find({
"status": 1,
"date": {
$lte: ISODate("2015-06-16T20:05:03.000Z")
},
"date": {
$gte: ISODate("2015-06-15T20:05:03.000Z")
}
});
It looks like the last query actually end up as an OR between the date comparisons, not AND as I would expect.
The reason this is not the same is because you are breaking a general rule of "hash/map" structures that applies to JavaScript Objects as well. That rule says that a "key" can only be specified once.
As such when you write:
db.collection.find({
"status": 1,
"date": {
"$lte": ISODate("2015-06-16T20:05:03.000Z")
},
// The next same key overwrites the other one
"date": {
"$gte": ISODate("2015-06-15T20:05:03.000Z")
}
});
One of the "date" items there is "ignored" because there can only be one at each level. So effectively either the "less than" or "greater than" is being thrown away as a condition ( likely the "less than" since it is first ) and all documents are only being tested for a single condition.
Here's the solid example. Consider these documents:
{ "a": 1 },
{ "a": 2 },
{ "a": 3 },
{ "a": 4 }
When you issue this query:
db.collection.find({ "a": { "$gte": 2 }, "a": { "$lte": 3 } },{ "_id": 0 })
The results come as only matching the "second" condition as that key overwrote the "first". It clearly violates the "first" condition, so that shows you it was "discarded":
{ "a": 1 }
{ "a": 2 },
{ "a": 3 },
So in order to get both conditions applied you specify like you did earlier:
db.collection.find({
"status": 1,
"date": {
"$lte": ISODate("2015-06-16T20:05:03.000Z"),
"$gte": ISODate("2015-06-15T20:05:03.000Z")
}
});
Which is essentially "shorthand" for writing this:
db.collection.find({
"status": 1,
"$and": [
{ "date": {
"$lte": ISODate("2015-06-16T20:05:03.000Z")
}},
{ "date": {
"$gte": ISODate("2015-06-15T20:05:03.000Z")
}}
]
});
Which is allowed since each element is in it's own document and a member of an array. The separation there allows you to use the same key names in each element.
find({"status" : 1, "date":{ $lte:ISODate("2015-06-16T20:05:03.000Z")},"date":{$gte:ISODate("2015-06-15T20:05:03.000Z")}});
Here You are trying to find date(key) lessthan 2015-06-16T20:05:03.000Z and in same query you are ading date(key) with 2015-06-15T20:05:03.000Z.
You have only one key as date.
Above query behaves like select * from table where date >=2015-06-15T20:05:03.000Z and date <= 2015-06-15T20:05:03.000Z
But first query takes one date as key and takes multiple values for comparasion.