MongoDB aggregation - Match input parameter if provided else do not match - mongodb

I have a MongoDB aggregation query in which I have the following:
{ $match: { version: versionNumber }
The 'versionNumber' is an optional input parameter to the aggreagation. If this versionNumber is not provided, then I do not want this match to be performed.
Currently, if the versionNumber is not supplied, the match still happens and I get a blank query output.
Is there a way in Mongo to do this? Thanks!

There is a way to do that, yes, but it should be done in the application code. When building the pipeline array to pass to the query, only include the $match stage if the necessary information is provided.
var pipeline=[]
if (versionNumber) pipeline.push( {$match: {version: versionNumber }} )
pipeline.push( ... Other Stages ... )
db.collection.aggregate(pipeline)

I am not sure what will be the value in versionNumber when its not provided (optional), lets assume versionNumber will be any from "" or undefined or null,
if versionNumebr is not available then it will skip and when its available then it will match $eq condition
you can add more values in array [null, "", "undefined"], 0 zero or anything you wanted to skip
{
$match: {
$expr: {
$cond: [
{ $in: [versionNumber, [null, "", "undefined"]] },
true,
{ $eq: ["$version", versionNumber] }
]
}
}
}
if versionNumebr will be always single possible value "" then you can use $eq instead of $in,
{
$match: {
$expr: {
$cond: [
{ $eq: [versionNumber, ""] },
true,
{ $eq: ["$version", versionNumber] }
]
}
}
}
Playground

Related

Find in nested array with compare on last field

I have a collection with documents like this one:
{
f1: {
firstArray: [
{
secondArray: [{status: "foo1"}, {status: "foo2"}, {status: "foo3"}]
}
]
}
}
My expected result includes documents that have at least one item in firstArray, which is last object status on the secondArray is included in an input array of values (eg. ["foo3"]).
I don't must use aggregate.
I tried:
{
"f1.firstArray": {
$elemMatch: {
"secondArray.status": {
$in: ["foo3"],
},
otherField: "bar",
},
},
}
You can use an aggregation pipeline with $match and $filter, to keep only documents that their size of matching last items are greater than zero:
db.collection.aggregate([
{$match: {
$expr: {
$gt: [
{$size: {
$filter: {
input: "$f1.firstArray",
cond: {$in: [{$last: "$$this.secondArray.status"}, ["foo3"]]}
}
}
},
0
]
}
}
}
])
See how it works on the playground example
If you know that the secondArray have always 3 items you can do:
db.collection.find({
"f1.firstArray": {
$elemMatch: {
"secondArray.2.status": {
$in: ["foo3"]
}
}
}
})
But otherwise I don't think you can check only the last item without an aggregaation. The idea is that a regular find allows you to write a query that do not use values that are specific for each document. If the size of the array can be different on each document or even on different items on the same document, you need to use an aggregation pipeline

pipeline function in MongoDB, how to make the join

I am looking at a query in MongoDB.
Essentially, I want to join records, but only when the records in collection mongo2 meet certain conditions (those in the and statement).
I have 2 questions about this
Where can I put the local and foreign field setting. It says I cannot define them when using pipeline.
Its says that my GT and LT statements are wrong. They work in single find statements, but I am getting the error
Expression $gt takes exactly 2 arguments. 1 were passed in.
Any help will be massivel appreciated :)
Thanks guys
db.mongo.aggregate([
{ $lookup:
{
from: "mongo2",
pipeline: [
{ $match:
{ $expr:
{
$and:[{Age : {$gt:50}}, {Age : {$lt:100}}]
}
}
}
],
as: "filters"
}
}
])
The only way to access fields coming from mongo collection inside of pipeline is to define them as variables using let statement. For instance:
db.mongo.aggregate([
{
$lookup: {
from: "mongo2",
let: { "mongo_collection_id": "$_id" },
pipeline: [
{
$match: { $expr: { $eq: [ "$$mongo_collection_id", "$_id" ] } }
}
],
as: "filters"
}
}
])
Please note that you need double dollar sign ($$) to refer to that variable within pipeline. Single dollar references fields from mongo2 collection documents.
Answering second question: there are two $gt and $lt pairs in MongoDB (which might be confusing). Since you probably have to use $expr the only way is to use $gt (aggregation) so the syntax is a bit different:
{ $expr:
{
$and:[{ $gt: [ "$Age", 50 ] }, { $lt: [ "$Age", 100 ] }]
}
}

Lookup with concat followed by match, doesn't return the result of the latter

I'm new to MongoDB and I'm trying to perform aggregate of multiple collections in order to validate a HTTP request. Right now I need to lookup a collection, concat a value of the collection I've looked up with a given string and use the result of the concat on a match equals. The code below is what i currently have:
{
from: "Patient",
let: {
subject: "$subject.reference"
},
pipeline: [
{
$project: {
concatTest: {
$concat: [
"Patient/",
"$id"
]
}
}
},
{
$match: {
$expr: {
$eq: [
"$concatTest",
"$$subject"
]
}
}
}
],
as: "result"
}
The problem:
The result array does not outputs the collection after filtering with match and instead outputs the results of the concat as shown:
result:Array
0:Object
_id:5d6d13175def3532dd905767
concatTest:"Patient/5d6d13175def3532dd905767"
I'm guessing this is a pretty simple problem of placing the right output, however I can't find a solution to it. Maybe i shouldn't be doing concat inside the pipeline? Or I completely misunderstood out the pipeline works?
Thanks in advance.
Ye, was just a simple problem of having the concat has part of the pipeline and not within the match. Solution was to write the match as follows:
$match: {
$expr: {
$eq: [
"$$subject",
{$concat:["Patient/","$id"]}
]
}
}
This way the concat is resolved into the match and not into the pipeline.

MongoDB aggregation project different fields based on the matched condition?

I am writing a query in MongoDB using aggregation such that if condition 1 matches then I want to project some fields and when condition 2 matches I want to project some different fields and when the third condition 3 reaches I want to project some other different fields.
My Query is like below
{
$match: {
$and: [
{
{field_a: "henesa"}
},
{
$expr: {
$or: [
{ Condition 1}, {Condition 2}, {condition 3}
]
}
}
]
}
},
{$project: { /* Here How To Decide which params to send */}}
Can anyone please tell me how can I do that.
You can use <field>: <expression> syntax of $projection at the projection stage.
In <expression> part you can use conditional operators to project values based on your criteria. E.g.
{ $project: {
field: { $switch: {
branches: [
{ case: Condition 1, then: "$field1" },
{ case: Condition 2, then: "$field2" },
...
]
} }
} }
Or more complex combination of $cond if you need to handle cases when more than one $or conditions met.

MongoDB projections and fields subset

I would like to use mongo projections in order to return less data to my application. I would like to know if it's possible.
Example:
user: {
id: 123,
some_list: [{x:1, y:2}, {x:3, y:4}],
other_list: [{x:5, y:2}, {x:3, y:4}]
}
Given a query for user_id = 123 and some 'projection filter' like user.some_list.x = 1 and user.other_list.x = 1 is it possible to achieve the given result?
user: {
id: 123,
some_list: [{x:1, y:2}],
other_list: []
}
The ideia is to make mongo work a little more and retrieve less data to the application. In some cases, we are discarding 80% of the elements of the collections at the application's side. So, it would be better not returning then at all.
Questions:
Is it possible?
How can I achieve this. $elemMatch doesn't seem to help me. I'm trying something with unwind, but not getting there
If it's possible, can this projection filtering benefit from a index on user.some_list.x for example? Or not at all once the user was already found by its id?
Thank you.
What you can do in MongoDB v3.0 is this:
db.collection.aggregate({
$match: {
"user.id": 123
}
}, {
$redact: {
$cond: {
if: {
$or: [ // those are the conditions for when to include a (sub-)document
"$user", // if it contains a "user" field (as is the case when we're on the top level
"$some_list", // if it contains a "some_list" field (would be the case for the "user" sub-document)
"$other_list", // the same here for the "other_list" field
{ $eq: [ "$x", 1 ] } // and lastly, when we're looking at the innermost sub-documents, we only want to include items where "x" is equal to 1
]
},
then: "$$DESCEND", // descend into sub-document
else: "$$PRUNE" // drop sub-document
}
}
})
Depending on your data setup what you could also do to simplify this query a little is to say: Include everything that does not have a "x" field or if it is present that it needs to be equal to 1 like so:
$redact: {
$cond: {
if: {
$eq: [ { "$ifNull": [ "$x", 1 ] }, 1 ] // we only want to include items where "x" is equal to 1 or where "x" does not exist
},
then: "$$DESCEND", // descend into sub-document
else: "$$PRUNE" // drop sub-document
}
}
The index you suggested won't do anything for the $redact stage. You can benefit from it, however, if you change the $match stage at the start to get rid of all documents which don't match anyway like so:
$match: {
"user.id": 123,
"user.some_list.x": 1 // this will use your index
}
Very possible.
With findOne, the query is the first argument and the projection is the second. In Node/Javascript (similar to bash):
db.collections('users').findOne( {
id = 123
}, {
other_list: 0
} )
Will return the who'll object without the other_list field. OR you could specify { some_list: 1 } as the projection and returned will be ONLY the _id and some_list
$filter is your friend here. Below produces the output you seek. Experiment with changing the $eq fields and target values to see more or less items in the array get picked up. Note how we $project the new fields (some_list and other_list) "on top of" the old ones, essentially replacing them with the filtered versions.
db.foo.aggregate([
{$match: {"user.id": 123}}
,{$project: { "user.some_list": { $filter: {
input: "$user.some_list",
as: "z",
cond: {$eq: [ "$$z.x", 1 ]}
}},
"user.other_list": { $filter: {
input: "$user.other_list",
as: "z",
cond: {$eq: [ "$$z.x", 1 ]}
}}
}}
]);