MongoDB Aggregation project field to be conditional value - mongodb

I try to project my latest aggregation artifact, into a new one, with an additional field, whose value should be conditional.
My latest artifact of the aggregation pipeline is:
[
server: {
_id: 6108d87b9255993b26796ca9,
version: '2.0.366',
name: 'aaaaaa',
variables: [
{
key: 'aKey',
value: 'aVal',
},
{
key: 'bKey',
value: 'bVal',
},
{
key: 'cKey',
value: 'cVal',
}
],
__v: 0
}
]
So basically I want to project the additional artifact with an additional field whose name is bucketName.
I want to check in the variables array, whether there is an item, whose key is pre-known of value aKey. If so, the result of the projected bucketName would be the value field: aVal. If could not find, value would be NOT_FOUND.
So the result of the sample above would be:
[
server: {
_id: 6108d87b9255993b26796ca9,
version: '2.0.366',
name: 'aaaaaa',
variables: [
{
key: 'aKey',
value: 'aVal',
},
{
key: 'bKey',
value: 'bVal',
},
{
key: 'cKey',
value: 'cVal',
}
],
bucketName: 'aVal',
__v: 0
}
]
My try was:
{
$project: {
bucketName: {
$cond: {
if: {
$eq : ['$server.variables.key', 'aKey'],
},
then: '$server.variables.value',
else: 'NOT_FOUND',
},
},
},
},
But that just didn't do the query correctly.

You should use $in operator because this variables key is array:
Then, use also $filter within the then to extract that value.
db.collection.aggregate([
{
"$set": {
"bucketName": {
"$cond": {
"if": {
"$in": [
"aKey",
"$server.variables.key"
]
},
"then": {
"$filter": {
"input": "$server.variables",
"as": "variable",
"cond": {
"$eq": [
"$$variable.key",
"aKey"
]
}
},
},
"else": "NOT_FOUND"
}
}
},
},
{
"$unwind": "$bucketName",
},
{
"$project": {
"bucketName": "$bucketName.value",
"server": 1
}
}
])

you can check $in condition because server.variables.key will return an array of string
$indexOfArray to get index of matching key element from array server.variables.key
$arrayElemAt to select value from server.variables.value by about returned index
db.collection.aggregate([
{
$project: {
bucketName: {
$cond: {
if: { $in: ["aKey", "$server.variables.key"] },
then: {
$arrayElemAt: [
"$server.variables.value",
{ $indexOfArray: ["$server.variables.key", "aKey"] }
]
},
else: "NOT_FOUND"
}
}
}
}
])
Playground

Your $server.variables.key is an array, is that why $eq fails, you are comparing an string with an array. You need to use $in. Check this example.
But also there is a problem, as $server.variables.key is an array with all keys values, the bucketName variable will be an array too.
You have to use the string aKey into then stage like this
db.collection.aggregate({
"$set": {
"bucketName": {
"$cond": {
"if": {
"$in": [
"aKey",
"$server.variables.key"
]
},
"then": "aKey",
"else": "NOT_FOUND"
}
}
}
})

Related

MongoDB - $filter an array of objects with $exists

I have an array of objects and in the $project pipeline of MongoDB I have to choose the one element whose metadata does not exist. So, for example below are a few documents after my $group pipeline -
{
_id: {
genre: "suspense",
},
price: 10210.6,
data: [
{
subGenre: "Thriller",
flag: true,
},
{
subGenre: "jumpScare",
},
{
subGenre: "horror",
flag: true,
}
]
}
After this, I need to run a $project pipeline where I have to only project that element of the data array where the flag does not exist. My syntax for that is -
db.collection.aggregate([
{
"$project": {
"_id": 0,
"price": 1,
"data": {
"$getField": {
"field": "subGenre",
"input": {
"$first": {
"$filter": { input: "$data", cond: { "$exists": [ "$$this.flag", false ] } }
}
}
}
}
}
}
])
But this is throwing an error -
Invalid $project :: caused by :: Unrecognized expression '$exists'
The output should be -
{
price: 10210.6,
subGenre: "jumpScare"
}
Instead of using $exists which is invalid, you can compare the value with:
$eq: [
"$$this.flag",
undefined
]
This aims to check whether the flag field does not exist.
Demo # Mongo Playground
You may also work with checking the $type value is matched with "missing". (Tested this works in MongoDB Compass)
$eq: [
{ $type: "$$this.flag" },
"missing"
]

MongoDB Aggregation Pipeline - get oldest document, but debounced

I have documents that look like:
[
{
value: 'Apple',
createdAt: '2021-12-09T20:15:26.421+00:00',
},
{
value: 'Blueberry',
createdAt: '2021-12-09T20:45:26.421+00:00',
},
{
value: 'Cranberry',
createdAt: '2021-12-09T21:30:26.421+00:00',
},
{
value: 'Durian',
createdAt: '2022-01-24T20:15:26.421+00:00',
},
{
value: 'Elderberry',
createdAt: '2022-01-24T20:45:26.421+00:00',
},
]
I'd like to do an aggregation where I get the oldest document, with the caveat that if another document was created within an hour, it invalidates the first document and I actually want to grab that one instead. For example, in the above I would like to return Cranberry. Initially pick Apple, but since Blueberry comes within an hour, move to that one, and since Cranberry comes within an hour of Blueberry, select Cranberry.
You can do the followings in an aggregation pipeline:
$sort by createdAt
$limit to get the oldest document
$lookup to get all the documents with createdAt behind the current document
$reduce to loop the result array; update the accumulator/result only if the current entry is within 1 hour
db.collection.aggregate([
{
$sort: {
createdAt: 1
}
},
{
$limit: 1
},
{
"$lookup": {
"from": "collection",
let: {
current: "$createdAt"
},
pipeline: [
{
$match: {
$expr: {
$gte: [
"$createdAt",
"$$current"
]
}
}
}
],
"as": "within"
}
},
{
"$addFields": {
"within": {
"$reduce": {
"input": "$within",
"initialValue": null,
"in": {
"$cond": {
"if": {
$or: [
{
$eq: [
"$$value",
null
]
},
{
$lte: [
{
"$subtract": [
"$$this.createdAt",
"$$value.createdAt"
]
},
3600000
]
}
]
},
"then": "$$this",
"else": "$$value"
}
}
}
}
}
}
])
Here is the Mongo playground for your reference.

Mongo DB Join on Primary/Foreign Key

I have two collections, viz: clib and mp.
The schema for clib is : {name: String, type: Number} and that for mp is: {clibId: String}.
Sample Document for clib:
{_id: ObjectId("6178008397be0747443a2a92"), name: "c1", type: 1}
{_id: ObjectId("6178008397be0747443a2a91"), name: "c2", type: 0}
Sample Document for mp:
{clibId: "6178008397be0747443a2a92"}
{clibId:"6178008397be0747443a2a91"}
While Querying mp, I want those clibId's that have type = 0 in clib collection.
Any ideas how this can be achieved?
One approach that I can think of was to use $lookUp, but that doesnt seem to be working. Also, I m not sure if this is anti-pattern for mongodb, another approach is to copy the type from clib to mp while saving mp document.
If I've understood correctly you can use a pipeline like this:
This query get the values from clib where its _id is the same as clibId and also has type = 0. Also I've added a $match stage to not output values where there is not any coincidence.
db.mp.aggregate([
{
"$lookup": {
"from": "clib",
"let": {
"id": "$clibId"
},
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{
"$eq": [
{
"$toObjectId": "$$id"
},
"$_id"
]
},
{
"$eq": [
"$type",
0
]
}
]
}
}
}
],
"as": "result"
}
},
{
"$match": {
"result": {
"$ne": []
}
}
}
])
Example here
db.mp.aggregate([
{
$lookup: {
from: "clib",
let: {
clibId: "$clibId"
},
pipeline: [
{
$match: {
$expr: {
$and: [
{
$eq: [ "$_id", "$$clibId" ],
}
]
}
}
},
{
$project: { type: 1, _id: 0 }
}
],
as: "clib"
}
},
{
"$unwind": "$clib"
},
{
"$match": {
"clib.type": 0
}
}
])
Test Here

How to use $addFields in mongo to add elements to just existing documents?

I am trying to add a new field to an existing document by using a combination of both $ifnull and $cond but an empty document is always added at the end.
Configuration:
[
{
line: "car",
number: "1",
category: {
FERRARI: {
color: "blue"
},
LAMBORGHINI: {
color: "red"
}
}
},
{
line: "car",
number: "2",
category: {
FERRARI: {
color: "blue"
}
}
}
]
Query approach:
db.collection.aggregate([
{
$match: {
$and: [
{ line: "car" },
{ number: { $in: ["1", "2"] } }
]
}
},
{
"$addFields": {
"category.LAMBORGHINI.number": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
"$number",
"$$REMOVE"
]
}
}
},
{
$group: {
_id: null,
CATEGORIES: {
$addToSet: "$category.LAMBORGHINI"
}
}
}
])
Here is the link to the mongo play ground:
https://mongoplayground.net/p/RUnu5BNdnrR
I tried the mentioned query but I still get that ugly empty set added at the end.
$$REMOVE will remove last field/key, from your field category.LAMBORGHINI.number the last field is number that is why it is removing number from the end, you can try another approach,
specify just category.LAMBORGHINI, if condition match then it will return object of current category.LAMBORGHINI and number object after merging using $mergeObjects
{
"$addFields": {
"category.LAMBORGHINI": {
$cond: [
{ "$ifNull": ["$category.LAMBORGHINI", false] },
{
$mergeObjects: [
"$category.LAMBORGHINI",
{ number: "$number" }
]
},
"$$REMOVE"
]
}
}
}
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 ] }
}}
])