Liatz
Liatz

Reputation: 5207

Mongodb find best match

I would like to build a query that returns the best matching document in the DB.

i.e I would like to find a document with the following fields - "animal":"dog" "color":"brown" "pattern":"dots" "size":"small"

If there is no document that has all the above fiels and their corresponding values I would like the query to return the best match. f.e {"animal":"dog", "color":"brown", "size":"small"}

Can I implement a recursive query in mongo db? If so, how? Should I use mapReduce, if so, how? One can think of this problem more as a recommendation system problem in the way I would like to recommend on the item(document) that fits best to the given values, I would appreciate if someone knows a good way to implement the above

I'm using mongodb mongoose and nodejs.

Thank you.

Upvotes: 3

Views: 2423

Answers (2)

Stennie
Stennie

Reputation: 65363

Given that you appear to only be searching text fields, one possibility is to use the new (and currently "experimental") text search feature in MongoDB 2.4. This allows you to create a full text index on one or more fields, including field weighting for relevance.

For example:

db.collection.ensureIndex(
    // Fields to index
    {
        animal:  "text",
        color:   "text",
        pattern: "text",
        size:    "text"
    },

    // Options
    {
        name: "best_match_index",

        // Adjust field weights (default is 1)
        weights: {
            animal: 5,  // Most relevant search field
            size:   4   // Also relevant
       }
    }
)

Results will be returned in order of relevance with scoring based on the associated field weights.

Note that search keywords are also stemmed, so this may have some unexpected results if you are expecting exact matches. You can put your terms in double quotes to take advantage of phrase matching for more specific matches.

Upvotes: 1

mr.freeze
mr.freeze

Reputation: 14060

I'm not sure about the performance implications of a recursive query but something like this should work. It removes the last property from the query and tries again if no match is found:

var AnimalSchema = new mongoose.Schema({
    atype: { type: String },
    color: {type:String},
    pattern: {type:String},
    size : {type:String}
});

var Animal = mongoose.model('Animal',AnimalSchema);

//test data
//var dalmation = new Animal({
//    atype:'dog',
//    color: 'black and white',
//    pattern: 'spots',
//    size: 'big'
//});

//dalmation.save(function(err){
//    var yorkie = new Animal({
//        atype:'dog',
//        color:'brown',
//        pattern:'mixed',
//        size:'small'
//    });
//    yorkie.save(function(yer){
//         bestMatch({'atype':'dog','size':'big','color':'brown'},function(err,animal){
//             console.log(err,animal);
//         })
//    });
//});

function bestMatch(params,cb){
    Animal.findOne(params,function(err,animal){
        if (err || !animal){
           var keys = Object.keys(params);
           if (keys.length){
               delete params[keys.pop()];
               bestMatch(params,cb);
           }else{
               cb('No matches',null);
           }
        }else{
            cb(null,animal);
        }
    });
}

bestMatch({'atype':'dog','size':'big','color':'brown'},function(err,animal){
    console.log(err,animal);
});

Upvotes: 2

Related Questions