Lee Morgan
Lee Morgan

Reputation: 696

Run a specific $match query only if string is not empty

I am trying to implement a search of documents in a collection. One of the queries in the $match stage checks if anything in a list matches another list from my JS (not within the document).

However, what I want to do is only run that query if the list has contents. If it is empty then just ignore it.

Essentially like choosing options in a filter. If you choose something, then it will search for that, if you choose nothing then it doesn't limit based on that.

Person documents:

{
    name: String,
    dob: Date,
    gender: String,
    favoriteColors: [{
        color: String,
        otherInfo: String
    }]
}

My pipeline:

let colors = ["red", "blue", "green"];

collection.aggregate([
    {$match: {
        gender: "male",
        //run below only if colors array is not empty
        favoriteColors: {
            $elemMatch: {
                color: {
                    $in: colors
                }
            }
        }
    }}
])

How can I basically just run that part of the aggregate only if the array is not empty? I really don't want to use an if statement and write that same aggregate twice based on the two different conditions. Is it even possible to do this?

Upvotes: 1

Views: 609

Answers (1)

Tom Slabbaert
Tom Slabbaert

Reputation: 22296

You can use something like this using $cond like so:

let colors = [ ... ]
db.collection.aggregate([
    {
        $match: {
            $expr: {
                $and: [
                    {
                        $eq: [
                            "$gender",
                            "male"
                        ]
                    },
                    {
                        $cond: [
                            {
                                $gt: [
                                    colors.length,
                                    0
                                ]
                            },
                            {
                                $gt: [
                                    {
                                        $size: {
                                            $setIntersection: [
                                                {
                                                    $map: {
                                                        input: "$favoriteColors",
                                                        as: "color",
                                                        in: "$$color.color"
                                                    }
                                                },
                                                colors
                                            ]
                                        }
                                    },
                                    0
                                ]
                            },
                            {}
                        ]
                    }
                ]
            }
        }
    }
])

But if you're already using code why not move the same logic into js?:

let colors = [ ... ];

let conds = [
    {
        "gender": "Male"
    }
];

if (colors.length) {
    conds.push({
        "favoriteColors.color": {$in: colors}
    })
}

db.collection.aggregate([
    {
        $match: {
            $and: conds
        }
    }
])

Upvotes: 1

Related Questions