I have a collection with thousands of users. Each user document has a few properties like name, age range, and favourites.
I can easily help users find other users that are the within the same age range but I also want to somehow allow them to choose a matching percentage OR number of the favourites they have in common with other users.
Eg.
User 1
Name: x
Age Range: 19-25
Favourites: ["Red", "Green", "Blue"]
User 2
Name: y
Age Range: 19-25
Favourites: ["Orange", "Red", "Pink"]
User 3
Name: z
Age Range: 19-25
Favourites: ["Orange", "Red", "Blue"]
Here, if user 1 searched for users with a 33% match OR at least one common match, they would get only user 2. If they searched for a 66% OR at least two common matches, they would get user 3.
I've done the easy part and matched by age range and tried to work with mongoDB $all
and $in
but it's not exactly what I'm looking for. Can someone point me in the right direction?
Well, if you want to find objects having some common attributes, I'll take a different approach. I'll create full text index on attribute. In your particular case it's Favorites
.
Full text index is much faster when search for text. It also gives you a text score showing how much a given term is matching in collection.
In your particular case, I'll be measuring text score
to see if other documents are falling with in my criteria.
You'll need to create full text
index first.
db.collection.createIndex({"Favourites":"text"})
After creation of full text index, assuming you are trying to find all documents with at least 66%
match. It means if we have three text terms, we want all documents matching at least two out of 3 terms.
var match = 2;
var terms = "Red Green Blue";
db.collection.aggregate([
{ $match: { $text: { $search: terms } } },
{ $project: {User:1, _id:0, Name:1, "Age Range":1, Favourites:1, score: {$meta: "textScore"}}},
{ $sort: { score: 1 }},
{ $match: { score: { $gte: match } } }
])
In example above, we want to find all documents with at least two matching terms. Code snippet above will return:
{
"User" : 3.0,
"Name" : "z",
"Age Range" : "19-25",
"Favourites" : [
"Orange",
"Red",
"Blue"
],
"score" : 2.2
}
{
"User" : 1.0,
"Name" : "x",
"Age Range" : "19-25",
"Favourites" : [
"Red",
"Green",
"Blue"
],
"score" : 3.3000000000000003
}
We got back two documents matching at least two terms.
Update:
OP mentioned that terms can contain multi-word phrase. MongoDB full text allows to search for phrases and requires to wrap phrases around with string quotes.
e.g. var terms = "Red \"Light Blue\"";
Using above code snippet and provided that document contains Light Blue
phrase in Favourites, will return matching document.
However there is a catch. MongoDB always perform logical and
operation on phrases with rest of terms if any. In example above, code will search a document which contains phrase Light Blue
and term Red
Please see https://docs.mongodb.com/manual/reference/operator/query/text/#phrases
This will help
Do you expect the result as this ?
For example: Searched for "favourites" at least two common matches,['orange','red', 'pink'].Hence "User3" is the expectation.Query as below:
db.test.aggregate([
{"$match":
{
favourites: {"$in" : ['orange','red', 'pink']}
}
},
{ "$unwind": "$favourites" },
{ "$match": { favourites: { "$in": ['orange','red', 'pink'] } }},
{"$group" :
{
"_id": {
"_id": {"id":"$_id", "name":"$name"},
"favourites": "$favourites"
}
}
},
{ "$group": {
"_id": "$_id._id",
"favourites": { "$push": "$_id.favourites" },
"length": { "$sum": 1 }
}},
{ "$match": { "length": 2 }}
])
Result:
{
"result" : [
{
"_id" : ObjectId("574cf11b0b3052089fe57605"),
"favourites" : [
"red",
"orange"
],
"length" : 2
}
],
"ok" : 1
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With