Condition in aggregation mongodb - mongodb

I'm new in Mongodb and I try to learn aggregation.
My database look like this:
{
_id : 1,
a: 5,
b: 6,
c : {
ca: 3
cb: 5
}
},
{
_id : 1,
a: 3,
b: 4,
c : {
ca: 3
cb: 7
}
}
Is there anyway to :
- First: find max in c
- Second: create a new object like this:
{
a : [ 5, 3],
b : [6, 4],
c : [5, 7]
}
My apologies if this is a repeated question but I wasn't able to find a similar one.

Try this,
db.collection.aggregate([
{
$addFields: {
c: {
$objectToArray: "$c"
}
}
},
{
$group: {
_id: null,
a: {
$push: "$a"
},
b: {
$push: "$b"
},
c: {
$push: {
$max: "$c.v"
}
}
}
}
])
Mongo Playground

Related

How to compare two objects in MongoDB (ignoring order of keys)?

What is the best way to find all the documents where objA is the same as objB (order of keys is not important)?
Inspired by another question by #Digvijay, I was looking for a way to compare two objects on MongoDB query and could not find a relevant solution on SO.
Sample data:
[
{
objA: {a: 1, b: 2},
objB: {a: 1, b: 2}
},
{
objA: {m: "g", c: 5},
objB: {c: 5, m: "g"}
},
{
objA: {m: "g", c: 7},
objB: {c: 5, m: "g"}
},
{
objA: {m: "g", c: 7},
objB: {b: "g", c: 7}
}
]
Expected results:
[
{
objA: {a: 1, b: 2},
objB: {a: 1, b: 2}
},
{
objA: {m: "g", c: 5},
objB: {c: 5, m: "g"}
},
]
You can do it like this:
$objectToArray - to transform objA and objB to arrays.
$setEquals - to compare if above arrays have the same distinct elements.
db.collection.aggregate([
{
$match: {
$expr: {
$setEquals: [
{ $objectToArray: "$objA" },
{ $objectToArray: "$objB" }
]
}
}
}
])
Working example
Maybe use the old-school trick of converting them into an array by $objectToArray. Use $sortArray to sort by key. Compare by the sorted array to get the matches.
db.collection.aggregate([
{
$addFields: {
"sortedA": {
$sortArray: {
input: {
"$objectToArray": "$objA"
},
sortBy: {
k: 1
}
}
},
"sortedB": {
$sortArray: {
input: {
"$objectToArray": "$objB"
},
sortBy: {
k: 1
}
}
}
}
},
{
"$match": {
$expr: {
$eq: [
"$sortedA",
"$sortedB"
]
}
}
},
{
"$unset": [
"sortedA",
"sortedB"
]
}
])
Mongo Playground

mongodb average arrays across many documents

Using mongodb, I have a collection of documents where each document has a fixed length vector of floating point values such as below:
items = [
{"id": "1", "vec": [1, 2, 0]},
{"id": "2", "vec": [6, 4, 1]},
{"id": "3", "vec": [3, 2, 2]},
]
I would like to take the row wise average of these vectors. In this example I would expect the result to return
[ (1 + 6 + 3) / 3, (2 + 4 + 2) / 3, (0 + 1 + 2) / 3 ]
This answer is very close to what I am looking for, but as far as I can tell it will only work on vectors of size 2. mongoDB - average on array values
An answer has been provided that is not very performant for large arrays. For context I am using ~700 dimension vectors.
This should work: https://mongoplayground.net/p/PKXqmmW31nW
[
{
$group: {
_id: null,
a: {
$push: {
$arrayElemAt: ["$vec", 0]
}
},
b: {
$push: {
$arrayElemAt: ["$vec", 1]
}
},
c: {
$push: {
$arrayElemAt: ["$vec", 2]
}
}
}
},
{
$project: {
a: {
$avg: "$a"
},
b: {
$avg: "$b"
},
c: {
$avg: "$c"
}
}
}
]
Which outputs:
[
{
"_id": null,
"a": 3.3333333333333335,
"b": 2.6666666666666665,
"c": 1
}
]
Here's a more efficient without $avg operator. I'll leave other answer up for reference.
https://mongoplayground.net/p/rVERc8YjKZv
db.collection.aggregate([
{
$group: {
_id: null,
a: {
$sum: {
$arrayElemAt: ["$vec", 0]
}
},
b: {
$sum: {
$arrayElemAt: ["$vec", 1]
}
},
c: {
$sum: {
$arrayElemAt: ["$vec", 2]
}
},
totalDocuments: {
$sum: 1
}
}
},
{
$project: {
a: {
$divide: ["$a", "$totalDocuments"]
},
b: {
$divide: ["$b", "$totalDocuments"]
},
c: {
$divide: ["$c", "$totalDocuments"]
}
}
}
])
You can use $unwind to get values into separate documents, the key is to keep the index of the values. Then you can use $group by the index and calculate the average using the $avg operator.
db.collection.aggregate([
{
$unwind: {
path: "$vec",
includeArrayIndex: "i" // unwind and keep index
}
},
{
$group: {
_id: "$i", // group by index
avg: { $avg: "$vec" }
}
}, // at this stage, you already get all the values you need, in separate documents. The following stages will put all the values in an array
{
$sort: { _id: 1 }
},
{
$group: {
_id: null,
avg: { $push: "$avg" }
}
}
])
Mongo Playground

Get max from unwound arrays

I have a collection of documents where I want to find the maximum values of each of the ratios of every possible pair of fields in the data object. For example:
Documents:
[
{ data: { a: 1, b: 5, c: 2 } },
{ data: { a: 4, b: 1, c: 1 } },
{ data: { a: 2, b: 4, c: 3 } }
]
Desired output:
{
a: { a: 1, b: 4, c: 4 },
b: { a: 5, b: 1, c: 2.5 },
c: { a: 2, b: 1, c: 1 }
}
So the output a.b is the largest of the a:b ratios 1/5, 4/1, and 2/4.
So I figure I first use $objectToArray to convert data, then $unwind on the result, but I'm having a hard time figuring out how to group everything together. The number of documents I have won't be too large, but the number of keys in data can be in the low thousands, so I'm not sure how well Mongo will be able to handle doing a bunch of $lookup's and comparing the values like that.
You can try following aggregation:
db.col.aggregate([
{
$addFields: { data: { $objectToArray: "$data" } }
},
{
$project: {
pairs: {
$map: {
input: { $range: [ 0, { $multiply: [ { $size: "$data" }, { $size: "$data" } ] } ] },
as: "index",
in: {
$let: {
vars: {
leftIndex: { $floor: { $divide: [ "$$index", { $size: "$data" } ] } },
rightIndex: { $mod: [ "$$index", { $size: "$data" } ] }
},
in: {
l: { $arrayElemAt: [ "$data", "$$leftIndex" ] },
r: { $arrayElemAt: [ "$data", "$$rightIndex" ] }
}
}
}
}
}
}
},
{ $unwind: "$pairs" },
{
$group: {
_id: { l: "$pairs.l.k", r: "$pairs.r.k" },
value: { $max: { $divide: [ "$pairs.l.v", "$pairs.r.v" ] } }
}
},
{
$sort: {
"_id.l": 1, "_id.r": 1
}
},
{
$group: {
_id: "$_id.l",
values: { $push: { k: "$_id.r", v: "$value" } }
}
},
{
$addFields: { values: { $arrayToObject: "$values" } }
},
{
$project: {
root: [ { k: "$_id", v: "$values" } ]
}
},
{
$sort: { "root.k": 1 }
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$root"
}
}
}
])
Basically you need $objectToArray and $arrayToObject to transform between arrays and objects. Basically the point is that for each object you need to generate nxn pairs (3x3=9 in this case). You can perform such iteration using $range operator. Then using $mod and $divide with $floor you can get index pairs like (0,0)...(2,2). Then you just need $group with $max to get max values for each pair type (like a with b and so on). To get final shape you also need $replaceRoot.
Outputs:
{ "a" : { "a" : 1, "b" : 4, "c" : 4 } }
{ "b" : { "a" : 5, "b" : 1, "c" : 2.5 } }
{ "c" : { "a" : 2, "b" : 1, "c" : 1 } }

How to use elemMatch to match nested array

I have some data structure like this
{
a: 1,
array1: [
{
b: 2
array2: [
{
// this is my target
c: 3,
d: 3
},
{
c: 4,
d: 4
}
]
},
{
b: 3
array2: [
{
c: 5,
d: 5
},
{
c: 6,
d: 6
}
]
}
]
}
I know use {"array1" : {"$elemMatch" : {"b" : 2} } } to match element of first level array1. But I don't know how to match element {c: 3, d: 3} of array2 of array1.
$elemMatch is used to state association among multiple fields within the same nested structure.
For eg. If you are looking to query c and d and need them to belong to the same sub-document, the you can query it like.
{"array1.array2" : {"$elemMatch" : {"c" : 3, "d":3} } }
Note: if you are querying on a single field, you dont really need to use $elemMatch (since there is no association)
For instance, in your query example, you can instead do
{"array1.b" : 2}
try this,it help me a lot.
{
"array1": {
"$elemMatch": {
"array2": {
"$elemMatch": {
"c": 3
}
}
}
}
}
let feed = await Doc.findOneAndUpdate(
{
$and: [
{
_id: req.params.id,
},
{
'feeds.$[].locations': {
$elemMatch: {
uniqueName,
},
},
},
],
},
{
$pull: {
//#ts-ignore
'feeds.$[].locations': { uniqueName },
},
},
{ new: true }
);

Mongo Aggregation grouping subdocument

I'm kinda stuck doing something seemingly simple with MongoDB's aggregation framework.
Imagine you have documents that would look like this :
[
{ a: 1, b: 2 },
{ a: 1, b: 3 },
{ a: 5, b: 6 }
]
How can you group documents by the field a and then regroup sub-documents by another field, say b while still calculating the total number of documents at each step ?
For our example, the results would look be the following output document :
{
results: [
{
_id: {
a: 1
},
sum_a: 2,
doc_a: [
{
_id: {
b: 2
},
sum_b: 1
},
{
_id: {
b: 3
},
sum_b: 1
}
]
},
{
_id: {
a: 5
},
sum_a: 1,
doc_a: [
{
_id: {
b: 6
},
sum_b: 1
}
]
}
]
}
I tried things like this :
printjson(db.getSiblingDB('mydb').mycollection.aggregate([
{
$project: {
a: 1,
b: 1
}
},
{
$group: {
_id: {
a: '$a'
},
sum_a: {
$sum: 1
},
b: {
$first: '$b'
}
}
},
{
$group: {
_id: {
b: '$b'
},
sum_b: {
$sum: 1
}
}
},
{
$sort: {
sum_a: 1
}
}
]));
But in the different tests I made, it keeps overwriting previous group stage results, wrongly calculating sums, ...etc.
So I'm not really sure how to approach this problem.
If you group by main field ('a') and sub-field ('b') together and then group by only 'a' (summing the counts from the first step) and push 'b's into an array (copying counts from the first step), it should produce what you need:
{
$group : {
_id : {
a : '$a',
b : '$b'
},
count : {
$sum : 1
}
}
},{
$group : {
_id : {
a : '$_id.a'
},
count_a : {$sum: '$count'},
doc_a : {
$push : {
b : '$_id.b',
count_b : '$count'
}
}
}
}