MongoDB If condition as the second field in gt - mongodb

I want to select the $user_a_seen_at field if $user_a_id == socket.user_id otherwise select the $user_b_seen_at field. But my query isn't working.
$gt: ["$$this.created_at", IF CONDITION TO SELECT A FIELD ]
$project: {
unread_messages: {
$size: {
$filter: {
input: "$messages",
cond: {
$and: [
{ $eq: ["$$this.to_id", socket.user_id] },
{
$gt: [
"$$this.created_at", {
if: { $eq: ["$user_a_id", socket.user_id] },
then: "$user_a_seen_at",
else: "$user_b_seen_at"
}
]
}
]
}
}
}
}
}
Sample data
{
"_id" : ObjectId("5e4c57649fad2e2cac9f8cd5"),
"user_a_id" : 1,
"user_b_id" : 2,
"user_a_seen_at" : ISODate("2020-02-18T21:30:12.418Z"),
"user_b_seen_at" : ISODate("2020-02-18T15:30:12.418Z"),
"messages" : [
{
"text" : "Hello",
"_id" : ObjectId("5e4c57649fad2e2cac9f8cd4"),
"from_id" : 1,
"to_id" : 2,
"created_at" : ISODate("2020-02-18T21:30:12.409Z")
}
],
"created_at" : ISODate("2020-02-18T21:30:12.418Z"),
"last_activity" : ISODate("2020-02-18T21:30:12.418Z"),
"__v" : 0
}

You need to use $cond operator to evaluate a boolean expression to return one of the two specified return expressions
{ $cond: { if: <boolean-expression>, then: <true-case>, else: <false-case> } }
//or simplified
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
db.collection.aggregate([
{
$project: {
unread_messages: {
$size: {
$filter: {
input: "$messages",
cond: {
$and: [
{
$eq: [
"$$this.to_id",
socket.user_id
]
},
{
$gt: [
"$$this.created_at",
{
$cond: [
{
$eq: [
"$user_a_id",
socket.user_id
]
},
"$user_a_seen_at",
"$user_b_seen_at"
]
}
]
}
]
}
}
}
}
}
}
])
MongoPlayground

Related

MongoDB: add element to an inner array of array with an object that contains field calculated on another field

I have this document:
{
"_id" : ObjectId("626c0440e1b4f9bb5568f542"),
"ap" : [
{
"ap_id" : ObjectId("000000000000000000000001"),
"shop_prices" : [
{
"shop_id" : ObjectId("000000000000000000000097"),
"price" : 102
}
]
}
],
"bc" : [
{
"bc_id" : ObjectId("000000000000000000000003"),
"price" : 102
},
{
"bc_id" : ObjectId("000000000000000000000004"),
"price" : 104
}
],
"stock_price" : 70
}
My need is to eventually add to ap.shop_prices an element if not exists with this structure:
{
"shop_id" : ObjectId("000000000000000000000096"),
"price" : 104
}
where the price is bc.price where bc.bc_id = ObjectId("000000000000000000000004")
This is my first (unsuccesfull) try:
updateMany(
{
"_id": {"$eq": ObjectId("626c0421e1b4f9bb5568f531")},
"ap":{
$elemMatch:{
"ap_id":{$in:[ObjectId("000000000000000000000001")]},
"shop_prices.shop_id":{$ne:ObjectId("000000000000000000000096")}
}
},
"bc.bc_id": ObjectId("000000000000000000000003")
},
[
{"$set":
{"ap.$.shop_prices":
{"$cond":
[{"$in": [ObjectId("000000000000000000000096"), "$ap.$.shop_prices.shop_id"]}, "$ap.$.shop_prices",
{"$concatArrays":
["$ap.$.shop_prices",
[{"shop_id": ObjectId("000000000000000000000096"), "price": ???}]
]
}
]
}
}
}
]
)
thanks in advance
You can do that:
finding the bc related to your request using the $project
using $map in the $set operator
This should be the solution:
db.getCollection('test').update({
"ap": {
$elemMatch: {
"ap_id":{$in:[ObjectId("000000000000000000000001")]},
"shop_prices.shop_id":{$ne:ObjectId("000000000000000000000096")}
}
},
"bc.bc_id": ObjectId("000000000000000000000004")
},
[
{
$project: {
ap: 1,
bc: 1,
stock_price: 1,
current_bc: {
$arrayElemAt: [ {
$filter: {
input: "$bc",
as: "curr_bc",
cond: {$eq: ["$$curr_bc.bc_id", ObjectId("000000000000000000000004")]}
}
}, 0 ]
}
}
},
{
$set: {
"ap": {
"$map": {
input: "$ap",
as: "current_ap",
in: {
$cond: [
{$eq: [ObjectId("000000000000000000000001"), "$$current_ap.ap_id"]},
{
"$mergeObjects": [
"$$current_ap",
{"shop_prices": {$concatArrays: ["$$current_ap.shop_prices", [{"shop_id": ObjectId("000000000000000000000096"), "price": "$current_bc.price"}]]}}
]
},
"$$current_ap"
]
}
}
}
}
}
])

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

Aggregate and project with multiples conditions

I have a collection myCollection with array of members :
{
name : String,
members: [{status : Number, memberId : {type: Schema.Types.ObjectId, ref: 'members'}]
}
and i have this data
"_id" : ObjectId("5e83791eb49ab07a48e0282b")
"members" : [
{
"status" : 1,
"_id" : ObjectId("5e83791eb49ab07a48e0282c"),
"memberId" : ObjectId("5e7dbf5b257e6b18a62f2da9")
},
{
"status" : 2,
"_id" : ObjectId("5e837944b49ab07a48e0282d"),
"memberId" : ObjectId("5e7de2dbe027f43adf678db8")
}
],
I want to check by aggregate query if member 5e7dbf5b257e6b18a62f2da9 exists with status 1 but it didn't return true
db.getCollection('myCollection').aggregate([
{$match: {_id: ObjectId("5e83791eb49ab07a48e0282b")}},
{
$project: {
isMember: {
$cond: [
{ $and: [ {$in: [ObjectId("5e7dbf5b257e6b18a62f2da9"), '$members.memberId']}, {$eq: ['$members.status', 1]} ] },
// if
true, // then
false // else
]
}
}
}
])
Thank you for your responses.
If you want to get just true/false you can shortcut like this:
db.collection.aggregate([
{ $match: { _id: ObjectId("5e83791eb49ab07a48e0282b") } },
{
$project: {
isMember: {
$map: {
input: "$members",
in: {
$and: [
{ $eq: [ObjectId("5e7dbf5b257e6b18a62f2da9"), '$$this.memberId'] },
{ $eq: [1, '$$this.status'] }
]
}
}
}
}
},
{ $set: { isMember: { $anyElementTrue: "$isMember" } } }
])
A different style would be this:
db.collection.aggregate([
{ $match: { _id: ObjectId("5e83791eb49ab07a48e0282b") } },
{
$project: {
isMember: {
$map: {
input: "$members",
in: {
$eq: [
{ memberId: ("5e7dbf5b257e6b18a62f2da9"), status: 1 },
{ memberId: "$$this.memberId", status: "$$this.status" }
]
}
}
}
}
},
{ $set: { isMember: { $anyElementTrue: "$isMember" } } }
])

Document transformation based type

Lets say I have a mongodb document
{
"id" : 1,
"types" : ["online" ,"offline"],
"applications" : [ ["online", "online"], ["offline"] ]
}
I would like to transformed into
{
"id" : 1,
"types" : [
{
"type" : "online",
"count" : 2,
"applications" : [ "online", "online"]
},
{
"type" : "offline",
"count" : 1,
"applications" : [ "offline" ]
}
]
}
here types and applications arrays can be of any size.
You can do that in two steps, use $map with $filter to match type with applications and then run $reduce to get count:
db.collection.aggregate([
{
$project: {
id: 1,
types: {
$map: {
input: "$types",
as: "type",
in: {
type: "$$type",
applications: {
$filter: {
input: "$applications",
as: "application",
cond: {
$allElementsTrue: {
$map: { input: "$$application", in: { $eq: [ "$$this", "$$type" ] } }
}
}
}
}
}
}
}
}
},
{
$addFields: {
types: {
$map: {
input: "$types",
in: {
$mergeObjects: [
"$$this",
{
count: {
$reduce: {
input: "$$this.applications",
initialValue: 0,
in: { $add: [ "$$value", { $size: "$$this" } ] }
}
}
}
]
}
}
}
}
}
])
You can decide whether it's better to use $allElementsTrue or $anyElementTrue when building your filtering criteria.
Mongo Playground

MongoDB , getting the minimum & maximum of array subset

Am trying to find a way to get the minimum number of orders between
2019-03-17 and 2019-03-19 excluding 2019-03-15 from the results ..
{
"_id" : ObjectId("5c8ffdadde62bf097d54ec47"),
"productId" : "32886845998",
"orders" : [
{
"date" : ISODate("2019-03-15T00:00:00.000+0000"),
"orders" : NumberInt(9)
},
{
"date" : ISODate("2019-03-17T00:00:00.000+0000"),
"orders" : NumberInt(21)
},
{
"date" : ISODate("2019-03-18T00:00:00.000+0000"),
"orders" : NumberInt(20)
},
{
"date" : ISODate("2019-03-19T00:00:00.000+0000"),
"orders" : NumberInt(30)
}
]
}
I tried using $min and $max operator but that didn't help because it iterated through the full array to find maximum & minimum
db.products.aggregate([
{
$project: {
maximum: {
$reduce: {
input: "$orders",
initialValue: 0,
in: {
$max: [
"$$value",
{
$cond: [
{ $gte: [ "$$this.date", ISODate("2019-03-17T00:00:00.000+0000") ] },
"$$this.orders",
0
]
}
]
}
}
}
}
}
])
You can use $filter to apply filtering by orders.date and then you can apply $min and $max on filtered set:
db.col.aggregate([
{
$project: {
filteredOrders: {
$filter: {
input: "$orders",
cond: {
$and: [
{ $gte: [ "$$this.date", ISODate("2019-03-17T00:00:00.000+0000") ] },
{ $lte: [ "$$this.date", ISODate("2019-03-19T00:00:00.000+0000") ] },
]
}
}
}
}
},
{
$project: {
min: { $min: "$filteredOrders.orders" },
max: { $max: "$filteredOrders.orders" },
}
}
])