Update or Insert object in array in MongoDB - 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

Related

How to remove field conditionally mongoodb

I have a collection and its documents look like:
{
_id: ObjectId('111111111122222222223333'),
my_array: [
{
id: ObjectId('777777777788888888889999')
name: 'foo'
},
{
id: ObjectId('77777777778888888888555')
name: 'foo2'
}
//...
]
//more attributes
}
However, some documents have my_array: [{}] (with one element which is an empty array).
How can I add conditionally a projection or remove it?
I have to add it to a mongo pipeline at the end of the query, and I want to get my_array only when it has at least one element which is not an empty object. If there's an empty object remove it.
I tried with $cond and $eq in a projection stage but it is not supported. Any suggestion to solve this?
Suppose you have documents like this with my_array field:
{ "my_array" : [ ] }
{ "my_array" : [ { "a" : 1 } ] } // #(1)
{ "my_array" : null }
{ "some_fld" : "some value" }
{ "my_array" : [ { } ] }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] } // #(2)
And, the following aggregation will filter and the result will have the two documents (1) and (2):
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
}
}
])
This also works with a find method:
db.collection.find({
$expr: {
$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]
}
})
To remove the my_array field, from a document when its empty, then you try this aggregation:
db.collection.aggregate([
{
$addFields: {
my_array: {
$cond: [
{$and: [
{ $eq: [ { $type: "$my_array" }, "array" ] },
{ $gt: [ { $size: "$my_array" }, 0 ] },
{ $ne: [ [{}], "$my_array" ] }
]},
"$my_array",
"$$REMOVE"
]
}
}
}
])
The result:
{ }
{ "my_array" : [ { "a" : 1 } ] }
{ }
{ "a" : 1 }
{ }
{ "my_array" : [ { "a" : 2 }, { "a" : 3 } ] }
You can't do that in a query, however in an aggregations you can add $filter to you pipeline, like so:
db.collection.aggregate([
{
$project: {
my_array: {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
}
])
Mongo Playground
However unless this is "correct" behavior I suggest you clean up your database, it's much simpler to maintain "proper" structure than to update all your queries everywhere.
You can use this update to remove these objects:
db.collection.update({
"myarray": {}
},
[
{
"$set": {
"my_array": {
$filter: {
input: "$my_array",
as: "elem",
cond: {
$ne: [
{},
"$$elem"
]
}
}
}
}
},
],
{
"multi": false,
"upsert": false
})
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.

how to project field in array with mongodb

my collection in mongo db like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:[2,3,4],
c:3,
d:4,
e:5
},
{
a:11,
b:[22,33,44],
c:33,
d:44,
e:55
}
]
}
I want to get a result with project op to give me a specific field in an array like this:
{
name:"mehdi",
grades:
[
{
a:1,
b:2
},
{
a:11,
b:22
}
]
}
how can I do this?
You can use $map to select a,b fields using $type to determine whether it's an array or number:
db.collection.aggregate([
{
$project: {
grades: {
$map: {
input: "$grades",
in: {
a: { $cond: [ { $eq: [ { $type: "$$this.a" }, "array" ] }, { $arrayElemAt: [ "$$this.a", 0 ] }, "$$this.a" ] },
b: { $cond: [ { $eq: [ { $type: "$$this.b" }, "array" ] }, { $arrayElemAt: [ "$$this.b", 0 ] }, "$$this.b" ] },
}
}
}
}
}
])
Mongo Playground

mongodb aggregate apply a function to a field

As part of an aggregate I need to run this transformation:
let inheritances = await db.collection('inheritance').aggregate([
{ $match: { status: 1 }}, // inheritance active
{ $project: { "_id":1, "name": 1, "time_trigger": 1, "signers": 1, "tree": 1, "creatorId": 1, "redeem": 1, "p2sh": 1 } },
{ $lookup:
{
from: "user",
let: { creatorId: { $concat: [ "secretkey", { $toString: "$creatorId" } ] }, time_trigger: "$time_trigger"},
pipeline: [
{ $match:
{ $expr:
{ $and:
[
{ $eq: [ "$_id", sha256( { $toString: "$$creatorId" } ) ] },
{ $gt: [ new Date(), { $add: [ { $multiply: [ "$$time_trigger", 24*60*60*1000 ] }, "$last_access" ] } ] },
]
}
}
},
],
as: "user"
},
},
{ $unwind: "$user" }
]).toArray()
creatorId comes from a lookup, and in order to compare it to _id I first need to do a sha256.
How can I do it?
Thanks.
External functions will not work with the aggregation framework. Everything is parsed to BSON by default. It is all basically processed from BSON operators to native C++ code implementation, This is by design for performance.
Basically in short, you can't do this. I recommend just storing the hashed value on every document as a new field, otherwise you'll have to do it in code just before the pipeline.

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 }
}
}
])