Terry
Terry

Reputation: 785

Missing Children

Using Mongoose with MongoDB, my Schema is as follows:

var PartSchema = new Schema({
    partcode: String,
    children: [String]
});

And the data looks like the following:

[{"partcode":"A1","children":["B1","B2","B3","B4"]},
 {"partcode":"B1","children":["C11","C21","C31","C41"]},
 {"partcode":"B3","children":["C13","C23","C33","C43"]},

I can query for A1's children field by using the following static call:

PartSchema.static('getChildren', function (partcode, callback) {
    var self = this;
    self.findOne({ partcode: partcode }, childrenOnly)
        .exec(function (err, doc) {
        return self.find({"partcode": {"$in": doc.children} }, exclId, callback);
    });
});

This returns (via express)

 [{"partcode":"B1","children":["C11","C21","C31","C41"]},
 {"partcode":"B3","children":["C13","C23","C33","C43"]}]

What I need is to return all children not found, for example:

[{"children":["B2","B4"}]

Upvotes: 2

Views: 99

Answers (1)

chridam
chridam

Reputation: 103375

You could use the _.difference() method from the lodash library to calculate the array set difference:

var _ = require("lodash");
PartSchema.static('getChildren', function (partcode, callback) {
    var self = this;
    self.findOne({ partcode: partcode }, childrenOnly)
        .exec(function (err, doc) {
        var promise = self.find({"partcode": {"$in": doc.children} }, exclId).lean().exec();
        promise.then(function (res){
             var codes = res.map(function (m) {
                 return m.children;
             }),
                 obj = {"children": _.difference(doc.children, codes)},
                 result = [];
             result.push(obj);
             return result;
        });
    });
});

-- UPDATE --

With MongoDB's aggregation framework, you can achieve the desired result. Let's demonstrate this in mongoshell first.

Suppose you insert the following test documents in the parts collection:

db.part.insert([
    {"partcode":"A1","children":["B1","B2","B3","B4"]},
    {"partcode":"B1","children":["C11","C21","C31","C41"]},
    {"partcode":"B3","children":["C13","C23","C33","C43"]}
])

The aggregation can be useful here given that you have an array of the children partcodes for a given partcode, say "A1", which is ["B1","B2","B3","B4"]. In this instance, your aggregation pipeline would consist of the following aggregation pipeline stages:

  1. $match - You need this to filter those documents whose children partcodes are not in the ["B1","B2","B3","B4"] array. This is achieved using the $nin operator.

  2. $group - This groups all the documents from the previous stream and creates an additional array field that has the parent partcodes. Made possible by using the $addToSet accumulator operator.

  3. $project - Reshapes each document in the stream by adding a new field partcode (which will eventually become part of the result object) and suppresses the _id field. This is where you can get the array difference between the parent partcode in the criteria and those not in the pipeline documents, made possible using the $setDifference set operator.

Your final aggregation operator would look like this (using mongoshell):

var children = ["B1","B2","B3","B4"];
db.part.aggregate([        
    {
        "$match": {
            "children": { "$nin": children }
        }
    },
    {
        "$group": {
            "_id": null,
            "parents": {
                "$addToSet": "$partcode"
            }
        }
    },
    {
        "$project": {
            "_id": 0,
            "partcode": {
                "$setDifference": [ children, "$parents" ]
            }
        }
    }
]).pretty()

Output:

/* 0 */
{
    "result" : [ 
        {
            "partcode" : ["B2","B4"]
        }
    ],
    "ok" : 1
}

Using the same concept in your Mongoose schema method:

PartSchema.static('getChildren', function (partcode, callback) {
    var self = this;
    self.findOne({ partcode: partcode }, childrenOnly)
        .exec(function (err, doc) {
            var pipeline = [        
                {
                    "$match": {
                        "children": { "$nin": doc.children }
                    }
                },
                {
                    "$group": {
                        "_id": null,
                        "parents": {
                            "$addToSet": "$partcode"
                        }
                    }
                },
                {
                    "$project": {
                        "_id": 0,
                        "partcode": {
                            "$setDifference": [ doc.children, "$parents" ]
                        }
                    }
                }
            ],
            self.aggregate(pipeline).exec(callback);                
    });
});

Or using Mongoose aggregation pipeline builder for a fluent call:

PartSchema.static('getMissedChildren', function (partcode, callback) {
    var self = this;
    self.findOne({ partcode: partcode }, childrenOnly)
        .exec(function (err, doc) {
            var promise = self.aggregate()
                .match({"children": { "$nin": doc.children }})
                .group({"_id": null,"parents": {"$addToSet": "$partcode"}})
                .project({"_id": 0,"partcode": {"$setDifference": [ doc.children, "$parents" ]}})
                .exec(callback);
            });
    });

Upvotes: 1

Related Questions