Search code examples
mongodbmongooseaggregation-frameworksubdocument

Subtract subdocuments from collection in aggregate pipeline


I'm trying to retrieve a document from a mongodb and remove some objects from an array based on a condition (deleted != null). Here is a simplified example of the item I'm targeting:

{
    "_id" : ObjectId("54ec9cac83a214491d2110f4"),
    "name" : "my_images", 
    "images" : [
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2311026b0cb289ed04188"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:20:16.961Z"),
        },
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2314a26b0cb289ed04189"),
            "deleted" : ISODate("2015-02-24T15:38:14.826Z"),
            "date_added" : ISODate("2015-02-28T21:21:14.910Z"),
        },
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2315526b0cb289ed0418a"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:21:25.042Z"),
        },
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2315d26b0cb289ed0418b"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:21:33.081Z"),
        }
    ]
}

The successful query will return the same but with one of the image objects removed as it has an ISODate rather than null. Like this:

{
    "_id" : ObjectId("54ec9cac83a214491d2110f4"),
    "name" : "my_images", 
    "images" : [
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2311026b0cb289ed04188"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:20:16.961Z"),
        },
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2315526b0cb289ed0418a"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:21:25.042Z"),
        },
        {
            "ext" : "jpeg",
            "type" : "image/jpeg",
            "_id" : ObjectId("54f2315d26b0cb289ed0418b"),
            "deleted" : null,
            "date_added" : ISODate("2015-02-28T21:21:33.081Z"),
        }
    ]
}

I have tried using aggregate to $unwind the images then $match only the relevant images but I now need to recreate the document other wise it returns the remaining 3 'unwound' documents.

Here is where I am so far:

Collection.aggregate([
        { $match:
            { _id: ObjectID(collection_id) }
        },
        { $unwind: "$images" },
        { $match:
            { "images.deleted": null }
        }

        // Next step in the pipeline to
        // reconfigure into one document
        // goes here

    ], function (err, result) {
    if (err) {
        console.log(err);
        return;
    }
    console.log(result);
});

Is there a way to create one document from what remains or am I going about this in entirely the wrong way?

Thanks.


Solution

  • As you alluded to, you need to re-group the unwound, filtered docs back into their original shape. You can do this with $group:

    Collection.aggregate([
            { $match:
                { _id: ObjectID(collection_id) }
            },
            { $unwind: "$images" },
            { $match:
                { "images.deleted": null }
            },
    
            // Regroup the docs by _id to reassemble the images array
            {$group: {
                _id: '$_id',
                name: {$first: '$name'},
                images: {$push: '$images'}
            }}
    
        ], function (err, result) {
        if (err) {
            console.log(err);
            return;
        }
        console.log(result);
    });