How to use $cond in mongoDB without an else expression? - mongodb

I want to add the values of $site to a variable if site!= 'undefined', else I have to skip that document and move on to the next.
I used
{$addToSet: { $cond: { if: { $ne: [ "$site", 'undefined' ] }, then: "$site"}}}
But it returns "Missing 'else' parameter to $cond"
and if I add an else statement
1) {$addToSet: { $cond: { if: { $ne: [ "$site", 'undefined' ] }, then: "$site", else: {} }}}
it returns the value to the addset {Object Object}
2) {$addToSet: { $cond: { if: { $ne: [ "$site", 'undefined' ] }, then: "$site", else: null }}}
it returns null to the set like ["sample1", "sample2", ]
3) {$addToSet: { $cond: { if: { $ne: [ "$site", 'undefined' ] }, then: "$site", else: "" }}}
it returns null to the set like ["sample1", "sample2", "" ]
I don't want anything to be added to the set if it does not satisfy the condition.

Starting in MongoDB 3.6, you can use the $$REMOVE variable as the ELSE argument of a $cond operator to prevent anything from being added to the set if the condition fails.
See here for an example from Mongo Docs.
In your case, we can get it done with,
{
$group: {
//_id: <group-by-expression>
// other fields (if any)..
sites: {
$addToSet: {
$cond: {
if: { $ne: ["$site", 'undefined'] },
then: "$site",
else: "$$REMOVE"
}
}
}
}
}
OR
{
$group: {
//_id: <group-by-expression>
// other fields (if any)..
sites: {
$addToSet: {
$cond: [
{ $ne: ["$site", 'undefined'] },
"$site",
"$$REMOVE"
]
}
}
}
}

You can add null on else brach(I used the simpliefied cond see here):
{
$group: {
_id: null,
A: {
$addToSet: {
$cond: [{ $ne: ["$site", 'undefined'] }, "$site", null]
}
}
}
}
and then:
{
"$project": {
"A": {
"$setDifferrence": ["$A", [null]]
},
}
}

Related

Using $exists in MongoDB $cond

For background, I have to create a field in a collection, but ONLY if another known field (an array) is not empty. I'm trying to make a boolean filter with the $and and some conditions:
$set: {
clientsAllowed: {
$cond: {
if: {
$and: [
{
businessAllowed: { $exists: true },
},
{
businessAllowed: { $type: "array" },
},
{
businessAllowed: { $ne: [] },
},
],
},
then: [...],
else: [...],
},
}
}
But I get this error:
Invalid $set :: caused by :: Unrecognized expression '$exists'
Is the $exists wrongly formatted? Or is the $and stage? Any ideas?
Type cond for businessAllowed is enough. You don't need to check exists or not.
db.collection.aggregate([
{
$set: {
clientsAllowed: {
$cond: {
if: {
$and: [
{
"$eq": [
{
$type: "$businessAllowed"
},
"array"
]
},
{
$ne: [
"$businessAllowed",
[]
]
}
]
},
then: [],
else: []
}
}
}
}
])
mongoplayground

MongoDB conditional if else if with exists

I am stuck in a query,
my case is
if user id exists then user id
else if user name exists then user name
else if user email exists then user email
else ''
Here, is my code.
$project: {
..... other fields,
'userDetail': {
$cond: {
if: {
$exists: ['$user.id']
},
then: '$user.id',
else: {
$cond: {
if: {
$exists: ['$user.name']
},
then: '$user.name',
else: {
$cond: {
if: {
$exists: ['$user.email']
},
then: '$user.email',
else: '',
}
},
}
}
}
}
}
Since, $exists don't work on this. Can anybody, help me finding the solution to this problem?
Also, if user.id is '' (empty string) & user.name is "Marcus". I want the result to return user.name.
Query
if id is false or empty string, try the name, else if name false or empty string, try the email, else ""
false are all not-exists/null/false
the types of those fields are not booleans, so you are safe to use this way
*in case you really needed the $exist for example you wanted to keep the null or the false values, you could use this {"$eq":[{"$type":"$user.id"}, "missing"]}
Test code here
aggregate(
[{"$set":
{"userInfo":
{"$switch":
{"branches":
[{"case":{"$and":["$user.id", {"$ne":["$user.id", ""]}]},
"then":"$user.id"},
{"case":{"$and":["$user.name", {"$ne":["$user.name", ""]}]},
"then":"$user.name"},
{"case":{"$and":["$user.email", {"$ne":["$user.email", ""]}]},
"then":"$user.email"}],
"default":""}}}}])
If I've understood correctly (and based on your example) you can use a query like this:
The trick here is to use ifNull instead of $exists.
db.collection.aggregate([
{
"$project": {
"userDetail": {
"$cond": [
{
"$ifNull": [
"$user.id",
false
]
},
// user id exists
"$user.id",
// user id no exists
{
"$cond": [
{
"$ifNull": [
"$user.name",
false
]
},
// user name exists
"$user.name",
// user name no exists
{
"$cond": [
{
"$ifNull": [
"$user.email",
false
]
},
// user email exists
"$user.email",
// user email no exists
""
]
}
]
}
]
}
}
}
])
Example here
You can use the nested $ifNull operator, by checking nested condition,
{
$project: {
userDetail: {
$ifNull: [
"$user.id",
{
$ifNull: [
"$user.name",
{ $ifNull: ["$user.email", ""] }
]
}
]
}
}
}
Playground
nested if else
tasks: {
$addToSet: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.id', ''],
},
'',
],
},
then: '$$REMOVE',
else: {
id: '$tasks.id',
description: '$tasks.description',
assignee: {
$cond: {
if: {
$eq: [
{
$ifNull: ['$tasks.assignee._id', ''],
},
'',
],
},
then: undefined,
else: {
id: '$tasks.assignee._id',
name: '$tasks.assignee.name',
thumbnail: '$tasks.assignee.thumbnail',
status: '$tasks.assignee.status',
},
},
},
},
},
},
}

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

Mongo project test if value is in array

In a project step of a mongo aggregation, I'd like to create a boolean field like:
{
$project: {
isInArray: { $cond: [
{ $in: ['$_id', ids] },
{ $const: true },
{ $const: false },
] },
},
},
but this is failing with
invalid operator $in
I could not find documentation on the correct syntax
You can use $setIsSubset operator
db.people.aggregate([
{ $project: {
isInArray: { $cond: [ {$setIsSubset: [['$_id'], ids]}, true, false ] }
}}
])

Nested conditions in $cond aggregate

I'm trying to create a computed status field in my Mongo query (statuses: created, payment received, shipped, received, finished).
db.orders.aggregate( [
{ $project: { status: {
$cond: { if: { $ne: ["$feedback", null] },
then: 'finished', else: {
$cond: { if: { $ne: ["$received", null] },
then: 'received', else: {
$cond: { if: { $ne: ["$shipped", null] },
then: 'shipped', else: {
$cond: { if: { $ne: ["$payment", null] },
then: 'payment received', else: 'created' }
} }
} }
} }
} } },
{ $match: { } }
] )
Example data:
{
"_id" : "xxxxxx0",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z"),
"received" : ISODate("2016-02-03T12:45:00.011Z"),
"feedback" : ISODate("2016-02-03T14:34:00.011Z")
},
{
"_id" : "xxxxxx1",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z"),
"received" : ISODate("2016-02-03T12:45:00.011Z")
},
{
"_id" : "xxxxxx2",
"payment" : ISODate("2016-02-03T10:45:00.011Z"),
"shipped" : ISODate("2016-02-03T11:55:00.011Z")
},
{
"_id" : "xxxxxx3",
"payment" : ISODate("2016-02-03T10:45:00.011Z")
},
{
"_id" : "xxxxxx4"
}
For some reason all my results show up as 'finished', am I using $cond wrong? Does it support nested $cond ?
You can't use $eq null if you want to check if the field exists or not, it will always return true
There is a trick to do that with $gt. You can check full explanation here (https://docs.mongodb.com/manual/reference/bson-types/#bson-types-comparison-order)
db.orders.aggregate( [
{ $project: { status: {
$cond: { if: { $gt: ["$feedback", null] },
then: 'finished', else: {
$cond: { if: { $gt: ["$received", null] },
then: 'received', else: {
$cond: { if: { $gt: ["$shipped", null] },
then: 'shipped', else: {
$cond: { if: { $gt: ["$payment", null] },
then: 'payment received', else: 'created' }
} }
} }
} }
} } },
{ $match: { } }
] )