standard way to $set a field to "missing" in mongoDB, i.e. $unset it - mongodb

{
$set: {
cheese: {
$cond: [
{ $eq: [{ $type: "$cheese" }, "missing"] },
"$cheese",
7,
],
},
},
}
If I set a field's value to undefined, it will be converted to null. That's not the goal. The goal is to remove the field. I cannot use $unset because it is conditional.
So the query above does work, i.e. setting the field to another field, via $a_field_that_does_not_exist, will remove the field.
However, is this the standard way? Remember, it is conditional, so we cannot use $unset. We have to $set to field to something, and that something must have the effect of removing the field.

Related

MongoDB Aggregation - $lookup without grouping 'as'

what I want to do is to join bookmarks with events that its document's status is activated. and also I want to replace roots with joined data list. hence, I'm currently running query like this:
db.bookmarks.aggregate(
[
{
"$lookup": {
"from": "events",
"let": {"eventId": "event_id"},
"pipeline": [
{
"$match": {"$expr": {"$and": [{"$eq": ["$_id", "$$eventId"]}, {"$eq": ["$status", "activated"]}]}
}
],
"as": "events"
}
},
{
"$unwind": {"path": "$events", "preserveNullAndEmptyArrays": True}
},
{
"$replaceWith": "$events"
},
{
"$facet": {
"page_info": [{"$count": "total_count"}],
"data_list": {
[{"$skip": 0}, {"$limit": 10}]
}
}
}
]
)
but here's problem, when event's status doesn't match with activated, it causes error like this:
PlanExecutor error during aggregation :: caused by :: 'replacement document' must evaluate to an object, but resulting value was: MISSING. Type of resulting value: 'missing'
my query only works when all the documents are fully joined.
what is the best way of replacing root with joined data list?
Based on this comment:
My exact requirement is to exclude empty events and not count excluded documents.
I think the solution here is to use the default $unwind settings. Currently you are specifically setting preserveNullAndEmptyArrays to true in your $unwind stage. Per the documentation, the description of this parameter is:
Optional.
If true, if the path is null, missing, or an empty array, $unwind outputs the document.
If false, if path is null, missing, or an empty array, $unwind does not output a document.
The default value is false.
So the trailing $facet in your pipeline is receiving (and therefor counting) the empty documents precisely because you are jumping through hoops (via that preservation setting in $unwind and the $ifNull in $replaceRoot) to get them there. To get the behavior that you are looking for, change the value of that setting to false:
{
"$unwind": {"path": "$events", "preserveNullAndEmptyArrays": False}
},
Or, since that's the default value, you can use the simpler syntax:
{
"$unwind": "$events"
},
You can play around with the preserveNullAndEmptyArrays setting in this playground sandbox. Simple syntax variant is here.

Project fleld by value in another collection

Record collection contains an array of.
Field1: 'some values'
Field2: 'some values'
Project collection contains project condition
Field1: 1
Field2: 0
The above scenario needs a query that only shows Field1.
because project collection says to show only Field1
I came up to this.
db.Record.aggregate([
{
$lookup: {
from: 'Project',
localField: '',
foreignField: '',
as: 'Project',
pipeline:[]
},
},
{
$unwind: {
path: '$Project',
preserveNullAndEmptyArrays: true,
},
},
{
$project: {
_id: 0,
Field1: {"$Project.Field1":{$eq:1}},
Field2: {"$Project.Field2":{$eq:1}},
},
},
]);
this will show like an array of {Field1:1, Field2:2} It can recognize. the value is present. I just want the value to be resolved.
Record Collection will have multiple records but Project collection will only have one collection.
It sounds like you are looking to Conditionally Exclude Fields.
In general, if field x is the optional field to include, and field y is the one indicating whether or not it should be included, then the portion of the $project definition to do so will use the $cond operator and look similar to the following:
"x": {
$cond: {
if: "$y",
then: "x",
else: "$$REMOVE"
}
}
In your particular case, x is represented by Field1 and y is represented by Project.Field1 (and similarly for 2). So your $project stage should look similar to the following:
{
$project: {
_id: 0,
Field1: {
$cond: {
if: "$Project.Field1",
then: "$Field1",
else: "$$REMOVE"
}
},
Field2: {
$cond: {
if: "$Project.Field2",
then: "$Field2",
else: "$$REMOVE"
}
}
}
}
A working playground example is here.
There were two general things wrong with this approach that you originally tried:
Field1: {"$Project.Field1":{$eq:1}}
The first is that the $eq operator in aggregation has a different structure. It takes the form { $eq: [ <expression1>, <expression2> ] }.
The second, and bigger, problem is that apart from true/false values, the other input you can provide when expressing field names in projection are expressions via <field>: <expression>. This syntax "Adds a new field or resets the value of an existing field." Using $eq alone would resolve to just true or false, which would then be set as the value of the field.
Helpfully, the rest of the description for that syntax is what points us to the approach using $cond outlined above: "If the the expression evaluates to $$REMOVE, the field is excluded in the output. For details, see
Exclude Fields Conditionally."

Mongodb | Check if any other key exist in object field other than given key in object field | length of object field type

We have one use case. Let's suppose I have two documents as given below.
{
"_id": ObjectID('123'),
"test": {
"a":1,
"b":1
}
},
{
"_id": ObjectID('456'),
"test": {
"a":1
}
Now I want those result whose "test" field has property other than "a" or in another way, I want those objects which have multiple keys/properties in a "test" field or to check the size of an object greater than 1
So, the result will be:
{
"_id": ObjectID('123'),
"test": {
"a":1,
"b":1
}
}
I tried to make a query for the above output as shown below and it working as expected
db.col.find({"test": {"$gt": {"a": 1} }})
So, is that the right thing to do? Any downside of doing it? We want to lever-age indexes as well
Please let me know your inputs on this.
Thanks.
Demo - https://mongoplayground.net/p/bluMAU_0Dre
Use $objectToArray { "$objectToArray": "$test" } convert to array
get the size $size os array
$gt check if it's more than 1
db.collection.find({
$expr: {
$gt: [ { $size: { "$objectToArray": "$test" } }, 1 ]
}
})
$expr
Allows the use of aggregation expressions within the query language.

How to use $addFields (aggregation) only when a certain condition is met

I'm trying to add field only when a certain condition is met, for example, if an array size > 0 then use $addFields to add field "date", otherwise don't add it.
when using $cond its a boolean so I have to add a field with either date value or false
When using if I have to provide else, then conditions
Any ideas on how to achieve this?
Starting in MongoDB 3.6, you can use the variable $$REMOVE in aggregation expressions to conditionally suppress a field. For an example, see Conditionally Exclude Fields,
{
$addFields: {
date: {
$cond: [
{ $gt: [{ $size: "$array" }, 0] },
"date field", // your date field
"$$REMOVE"
]
}
}
}
Playground

Accessing a value in an embedded document in an array

Consider a document containing an array of embedded documents:
{'array': [{'key1': 120.0, 'key2': 69.0}, {'key1': 100.0, 'key2': 50.0}]}
I want to project key2 for the first element of the array.
I naively tried
'$project':
{
'item': '$array.0.key2'
}
which fails (but explains what I want to do better than many words).
Using $arrayElemAt and $let
Since MongoDB 3.2, it is possible to get an item from the list using $arrayElemAt:
'$project':
{
'item1': {'$arrayElemAt': ['$array', 0] }
}
will return item1 as {'key1': 120.0, 'key2': 69.0}.
What I want key2 in there.
I managed to get it using $let:
'$project':
{
'item': {
'$let': {
'vars': {
'tmp': {'$arrayElemAt': ['$array', 0] },
},
'in': '$$tmp.key2'
}
},
}
Is there a simpler way?
This seems painfully verbose. Especially considering I'd like to use this construction in several expressions for the same value (test not zero, then use in division) in the same projection.
 The context
The array stores the successive states of the object represented in the document, sorted by date, reverse order. The 1st element of the array is the last (therefore current) state. I want to sort the documents using a ratio of two values in the current state.
It is possible that the only reasonable solution would be to get the last state out of the array. Or even to pre-calculate the ratio and sort on the pre-calculated value.
In an aggregation pipeline you can repeat steps to keep things cleaner so after:
'$project': {'item1': {'$arrayElemAt': ['$array', 0] }}
you can add:
{$project: {"item1.key2": 1}}
Actually you don't need to use the $let operator to do this. Simply use the $arrayElemAt operator in your $project stage to return the first item in your "array" and the $$ROOT system variable to return the current document. From there you can easily specify the field to $sort by using the dot notation. Additionally you can add another $project stage to the pipeline to discard the "item" field and the "_id" field from the query result.
db.collection.aggregate([
{ "$project": {
"item": { "$arrayElemAt": [ "$array", 0 ] },
"doc": "$$ROOT"
}},
{ "$sort": { "item.key2": 1 } },
{ "$project": { "doc": 1, "_id": 0 } }
])