Gaurav Gupta
Gaurav Gupta

Reputation: 4701

How to assign weights to searched documents in MongoDb?

This might sounds like simple question for you but i have spend over 3 hours to achieve it but i got stuck in mid way.

Inputs:

  1. List of keywords
  2. List of tags

Problem Statement: I need to find all the documents from the database which satisfy following conditions:

  1. List documents that has 1 or many matching keywords. (achieved)
  2. List documents that has 1 or many matching tags. (achieved)
  3. Sort the found documents on the basis of weights: Each keyword matching carry 2 points and each tag matching carry 1 point.

Query: How can i achieve requirement#3.

My Attempt: In my attempt i am able to list only on the basis of keyword match (that too without multiplying weight with 2 ).

tags are array of documents. Structure of each tag is like

{
   "id" : "ICC",
   "some Other Key" : "some Other value"
}

keywords are array of string:

["women", "cricket"]

Query:

var predicate = [
    {
        "$match": {
            "$or": [
                {
                    "keywords" : {
                        "$in" : ["cricket", "women"]
                    }
                },
                {
                    "tags.id" : {
                        "$in" : ["ICC"]
                    }
                }
            ]
        }
    },
    {
        "$project": {
            "title":1,
            "_id": 0,
            "keywords": 1,
            "weight" : {
                "$size": {
                    "$setIntersection" : [
                        "$keywords" , ["cricket","women"]
                    ]
                }
            },
            "tags.id": 1
        }   
    },
    {
        "$sort": {
            "weight": -1
        }
    }
]; 

Upvotes: 1

Views: 1419

Answers (1)

Blakes Seven
Blakes Seven

Reputation: 50416

It seems that you were close in your attempt, but of course you need to implement something to "match your logic" in order to get the final "score" value you want.

It's just a matter of changing your projection logic a little, and assuming that both "keywords" and "tags" are arrays in your documents:

db.collection.aggregate([
    // Match your required documents
    { "$match": {
        "$or": [
            {
                "keywords" : {
                    "$in" : ["cricket", "women"]
                }
            },
            {
                "tags.id" : {
                    "$in" : ["ICC"]
                }
            }
        ]
    }},

    // Inspect elements and create a "weight"
    { "$project": {
        "title": 1,
        "keywords": 1,
        "tags": 1,
        "weight": {
            "$add": [
                { "$multiply": [
                    {"$size": {
                        "$setIntersection": [
                            "$keywords",
                            [ "cricket", "women" ]             
                        ]
                    }}
                ,2] },
                { "$size": {
                    "$setIntersection": [
                        { "$map": {
                            "input": "$tags",
                            "as": "t",
                            "in": "$$t.id"
                        }},
                        ["ICC"]
                    ]
                }}
            ]
        }
    }},

    // Then sort by that "weight"
    { "$sort": { "weight": -1 } }
])

So it is basicallt the $map logic here that "transforms" the other array to just give the id values for comparison against the "set" solution that you want.

The $add operator provides the additional "weight" to the member you want to "weight" your responses by.

Upvotes: 5

Related Questions