Johan
Johan

Reputation: 40500

Insert documents to MongoDB when count is equal to expected value?

Is it possible to insert/upsert multiple documents in MongoDB 4.2 only if the the number of documents matching a particular query is of a particular size?

Example:

Let's say I have an items collection with the following 2 documents:

{ item: "ZZZ137", type="type1"}
{ item: "ZZZ138", type="type1"}

Now I want to insert these two documents:

{ item: "ZZZ139", type="type1"}
{ item: "ZZZ140", type="type1"}

but only of there are currently 2 items of type type1 in the collection (i.e. count of type1 is equal to 2).

Is it possible to somehow do this in MongoDB with a single command?

Update

To further illustrate my question let's imagine that insertMany had support for conditions. Then I'd like to do something like this (pseudo code that doesn't work):

db.items.insertMany({ { $count: { type: "type" } } : { $eq : 2 } } , [{ item: "ZZZ139", type="type1"}, { item: "ZZZ140", type="type1"}])

Where { { $count: { type: "type" } } : { $eq : 2 } } would be the query that must be fulfilled in order to insert item ZZZ139 and ZZZ140.

Upvotes: 1

Views: 303

Answers (1)

Tom Slabbaert
Tom Slabbaert

Reputation: 22276

This can be achieved using $out or $merge if you insist on doing this in 1 call, however it's very inefficient due to the logic and restriction of these 2 operators. I personally recommend splitting it into 2 calls:

let typeTwoCount = await db.collection.countDocuments({type: "2"})
if (typeTwoCount === 2) {
   await db.collection.insertMany(newItems)
}

Now we can use $out but due to the fact that it re-writes the collection we'll have to carry the entire collection through the pipeline and into the $out stage, which is ridiculous:

db.collection.aggregate([
    {
        $facet: {
            typeTwo: [
                {
                    $match: {
                        type: "2"
                    }
                },
                {
                    $count: "doc_count"
                },
                {
                    $addFields: {
                        newDocs: {
                            $cond: [
                                {$eq: ["$doc_count", 2]},
                                items,
                                []
                            ]
                        }
                    }
                },
                {
                    $unwind: "$newDocs"
                },
                {
                    $replaceRoot: {
                        newRoot: "$newDocs"
                    }
                },
            ],
            all: [
                {
                    $match: {}
                }
            ]
        }
    },
    {
        $addFields: {
            merged: { $concatArrays: ["$all", "$typeTwo"]}
        }
    },
    {
        $unwind: "$merged"
    },
    {
        $replaceRoot: {
            newRoot: "$merged"
        }
    },
    {
        $out: "collection"
    }
])

Now the issue with $merge is the following restriction:

The output collection cannot be the same collection as the collection being aggregated.

So we can employ similar tactic to the $out pipeline (with using the typeTwo pipeline for the $merge), but we'll have to start the aggregation with a different none empty dummy collection:

db.any_other_none_empty_collection.aggregate([
    {
        $limit: 1
    },
    {
        $lookup: {
            from: "collection",
            let: {},
            pipeline: [
                {
                    $match: {
                        type: "2"
                    }
                }
            ],
            as: "all"
        }
    },
    {
        $addFields: {
            doc_count: {$size: "$all"}
        }
    },
    {
        $addFields: {
            newDocs: {
                $cond: [
                    {$eq: ["$doc_count", 2]},
                    items,
                    []
                ]
            }
        }
    },
    {
        $unwind: "$newDocs"
    },
    {
        $replaceRoot: {
            newRoot: "$newDocs"
        }
    },
    {
        $merge: {
            into: "collection"
        }
    }
])

Upvotes: 2

Related Questions