How to use Mongo Aggregation Pipeline to format array? - mongodb

I have this document as part of an Aggregation Pipeline:
[
{
"mfe_average": [
[
true,
5.352702824879613
],
[
false,
3.2361364317753383
],
[
null,
2.675027181819201
]
]
}
]
How to convert to this format? Only the true values are Valid.
[
{
"mfe_average": [
[
"Valid",
5.352702824879613
],
[
"Invalid",
3.2361364317753383
],
[
"Invalid",
2.675027181819201
]
]
}
]

The query may look complex.
$map - Iterates the element in mfe_average and returns a new array.
1.1. $concatArrays - Combine arrays into one.
1.1.1. $cond - Set the value ("Valid"/"Invalid") by getting the first item of the nested array and comparing the value. It results in an array.
1.1.2. $slice - Take all the values in the nested array except the first item.
db.collection.aggregate([
{
$set: {
mfe_average: {
$map: {
input: "$mfe_average",
as: "avg",
in: {
$concatArrays: [
[
{
$cond: {
if: {
$eq: [
{
$arrayElemAt: [
"$$avg",
0
]
},
true
]
},
then: "Valid",
else: "Invalid"
}
}
],
{
$slice: [
"$$avg",
{
$multiply: [
{
$subtract: [
{
$size: "$$avg"
},
1
]
},
-1
]
}
]
}
]
}
}
}
}
}
])
Demo # Mongo Playground
If your nested array contains only 2 items, you may not require to write the complex $slice query as above. Instead, just provide -1 to take the last item of the nested array.
{
$slice: [
"$$avg",
-1
]
}

You can use $map operator to reconstruct the array,
$map to iterate loop of mfe_average array
$arrayElemAt to get specific position's element from array
$cond to check if the first element is true then return "Valid" otherwise "Invalid"
db.collection.aggregate([
{
$addFields: {
mfe_average: {
$map: {
input: "$mfe_average",
in: [
{
$cond: [
{ $arrayElemAt: ["$$this", 0] },
"Valid",
"Invalid"
]
},
{ $arrayElemAt: ["$$this", 1] }
]
}
}
}
}
])
Playground

Related

How to check if an array has the same elements as another one in mongodb aggregation

I need to match all documents where field arr, which is an array of object Ids, has same elements of a given one no matther the position.
I tried:
[
{
$match:{
arr: givenArr
}
}
]
or
[
{
$match:{
arr: {
$in: givenArr
}
}
}
]
The first pipeline matches all the documents that have same elements in the same position.
The second pipeline matches all the documents that has at least one element in the given array.
For example if I have a couple of documents like:
[
{
_id: ObjectId("639a0e4cc0f6595d90a84de4"),
arr: [
ObjectId("639a0e4cc0f6595d90a84de1"),
ObjectId("639a0e4cc0f6595d90a84de2"),
ObjectId("639a0e4cc0f6595d90a84de3"),
]
},
{
_id: ObjectId("639a0e4cc0f6595d90a84de5"),
arr: [
ObjectId("639a0e4cc0f6595d90a84de7"),
ObjectId("639a0e4cc0f6595d90a84de8"),
ObjectId("639a0e4cc0f6595d90a84de9"),
]
},
]
If I need to match all of those documents that have arr same as
[
ObjectId("639a0e4cc0f6595d90a84de8"),
ObjectId("639a0e4cc0f6595d90a84de9"),
ObjectId("639a0e4cc0f6595d90a84de7"),
]
I want to get only the second document.
How could I do that?
You could treat each array as a set and use "$setEquals".
db.collection.find({
$expr: {
$setEquals: [
"$arr",
[
ObjectId("639a0e4cc0f6595d90a84de8"),
ObjectId("639a0e4cc0f6595d90a84de9"),
ObjectId("639a0e4cc0f6595d90a84de7")
]
]
}
})
Try it on mongoplayground.net.
You can compare the sorted results of your 2 arrays computed by $sortArray
db.collection.find({
$expr: {
$eq: [
{
$sortArray: {
input: "$arr",
sortBy: 1
}
},
{
$sortArray: {
input: [
ObjectId("639a0e4cc0f6595d90a84de8"),
ObjectId("639a0e4cc0f6595d90a84de9"),
ObjectId("639a0e4cc0f6595d90a84de7"),
],
sortBy: 1
}
}
]
}
})
Mongo Playground

Update or Insert object in array in MongoDB

I have the following collection
{
"_id" : ObjectId("57315ba4846dd82425ca2408"),
"myarray" : [
{
userId : "8bc32153-2bea-4dd5-8487-3b65e3aa0869",
Time:2022-09-20T04:44:46.000+00:00,
point : 5
},
{
userId : "5020db46-3b99-4c2d-8637-921d6abe8b26",
Time:2022-09-20T04:44:49.000+00:00
point : 2
},
]
}
These are my questions
I want to push into myarray if userId doesn’t exist, and if userid already exists then update time and point also I have to keep only 5 elements in the array if 6th element comes then I a have to sort the array based on Time and remove oldest time entry
what is the best way to do this in mongo using aggregation
FYI we are using Mongo 4.4
You can achieve this by using the aggregation pipeline update syntax, the strategy will be first to update the array (or insert a new element to it).
then if the size exceeds 5 we just filter it based on minimum value. like so:
const userObj = {point: 5, userId: "12345"};
db.collection.updateOne(
{ ...updateCondition },
[
{
$set: {
myarray: {
$cond: [
{
$in: [
userObj.userId,
{$ifNull: ["$myarray.userId", []]}
]
},
{
$map: {
input: "$myarray",
in: {
$cond: [
{
$eq: [
"$$this.userId",
userObj.userId
]
},
{
$mergeObjects: [
"$$this",
{
Time: "$$NOW", // i used "now" time but you can swap this to your input
point: userObj.point
}
]
},
"$$this"
]
}
}
},
{
$concatArrays: [
{ $ifNull: ["$myarray", []] },
[
{
userId: userObj.userId,
point: userObj.point,
Time: "$$NOW"
}
]
]
}
]
}
}
},
{
$set: {
myarray: {
$cond: [
{
$gt: [
{
$size: "$myarray"
},
5
]
},
{
$filter: {
input: "$myarray",
cond: {
$ne: [
"$$this.Time",
{
$min: "$myarray.Time"
}
]
}
}
},
"$myarray"
]
}
}
}
])
Mongo Playground

$switch inside a $match MONGODB

Hi i am trying to use MONGODB query inside TIBCO jasperstudio to create a report
What I am trying to do is filter the data using two parameters #orderitemuid and #ordercatuid. My case is if I put a parameter using #orderitemuid, it will disregard the parameter for #ordercatuid. Vise versa, if I put a parameter using #ordercatuid, it will disregard the parameter for #orderitemuid. But there is also an option when using bot parameters in the query. I used a $switch inside the $match but I am getting an error. Below is the $match I am using
{
$match: {
$switch: {
branches: [
{
case: { $eq: [{ $IfNull: [$P{orderitemuid}, 0] }, 0] },
then: { 'ordcat._id': { '$eq': { '$oid': $P{ordercatuid} } } },
},
{
case: { $eq: [{ $IfNull: [$P{ordercatuid}, 0] }, 0] },
then: { '_id': { '$eq': { '$oid': $P{orderitemuid} } } },
},
],
default: {
$expr: {
$and: [
{ $eq: ['_id', { '$oid': $P{orderitemuid} }] },
{ $eq: ['ordcat_id', { '$oid': $P{ordercatuid} }] },
],
},
},
},
},
}
Thank you in advance
As mentioned in the $match docs
$match takes a document that specifies the query conditions. The query syntax is identical to the read operation query syntax; i.e. $match does not accept raw aggregation expressions. ...
And $switch is an aggregation expressions. this means it cannot be used in a $match stage without being wrapped with $expr.
You can however wrap it with $expr, this will also require you to restructure the return values a little bit, like so:
db.collection.aggregate([
{
$match: {
$expr: {
$switch: {
branches: [
{
case: {
$eq: [
{
$ifNull: [
$P{orderitemuid},
0
]
},
0
]
},
then: {
$eq: [
"$ordcat._id",
{"$oid":$P{ordercatuid}}
]
}
},
{
case: {
$eq: [
{
"$ifNull": [
$P{ordercatuid},
0
]
},
0
]
},
then: {
$eq: [
"$_id",
{"$oid":$P{orderitemuid}}
]
}
}
],
default: {
$and: [
{
$eq: [
"$_id",
{"$oid": $P{orderitemuid} }
]
},
{
$eq: [
"$ordcat_id",
{"$oid": $P{ordercatuid}}
]
}
]
}
}
}
}
}
])
Mongo Playground

in mongo how to match a field that is not list of empty elements?

I have field x which can be [[], [], ...] or ["", "", ....] I want to filter them out and keeps the document at least have 1 non-empty list or 1 non-empty string. for example [[], [1,2], [], ...]
This is an aggregation query which filters out collection documents with the array field x, having elements with all empty strings or all empty arrays.
db.collection.aggregate([
{
$addFields: {
filtered: {
$filter: {
input: "$x",
as: "e",
cond: {
$or: [
{ $and: [
{ $eq: [ { "$type": "$$e" }, "array" ] },
{ $gt: [ { $size: "$$e" }, 0 ] }
] },
{ $and: [
{ $eq: [ { "$type": "$$e" }, "string" ] },
{ $gt: [ { $strLenCP: "$$e" }, 0 ] }
] }
]
}
}
}
}
},
{
$match: {
$expr: { $gt: [ { $size: "$filtered" }, 0 ] }
}
},
{
$project: { filtered: 0 }
}
])
Reference: Various aggregation operators ($size, $type, $strLenCP, etc.) used.

mongodb return number of matched expression

I have many document in collection I want to run a aggregation query. Let say sample doc are like as shown below
{"A":1,"B":2, "c":"x"}
{"A":3,"B":2, "c":"play"}
{"A":1,"B":2, "c":"test"}
to these doc I want to run a OR query and also I want to return number of or condition matched.
for example if query is
{"A":1, "A":3, "c":"test"}
it should return result like
[
{"A":1,"B":2, "c":"x", "count":1}
{"A":3,"B":2, "c":"play", "count", 1}
{"A":1,"B":2, "c":"test", "count":2}
]
You can use $addFields and evaluate your value based on specified conditions:
db.col.aggregate([
{
$addFields: {
count: {
$add: [
{ $cond: [ { $eq: ["$A", 1] }, 1, 0 ] },
{ $cond: [ { $eq: ["$A", 3] }, 1, 0 ] },
{ $cond: [ { $eq: ["$c", "test"] }, 1, 0 ] },
]
}
}
},
{
$match: {
"count": { $gt: 0 }
}
}
])