MongoDB get best match - Is this possible? - node.js

I have fulltext enabled with MongoDB. Here is my script:
function bestMatch(name){
ChebiEntry.find({$text: {$search: escapeString(name)}},
{score: {$meta: 'textScore'}}, {lean:true},(e,results)=>{
if (results.length > 0){
let oneScores = []
results.forEach((obj)=>{
if(obj.score===1){
oneScores.push(obj);
}
});
console.log(oneScores);
}
})
.sort({score: {$meta: 'textScore'}})
}
//prepareDatabase();
bestMatch('K+');
And here is my output:
[ { _id: 596d55fd2c446456a0cebc1d,
id: 8345,
name: 'K+',
__v: 0,
score: 1 },
{ _id: 596d55ff2c446456a0ceccb2,
id: 8345,
name: 'K(+)',
__v: 0,
score: 1 },
{ _id: 596d56072c446456a0ceee6b,
id: 29103,
name: 'K(+)',
__v: 0,
score: 1 },
{ _id: 596d56072c446456a0cef092,
id: 29104,
name: 'K(-)',
__v: 0,
score: 1 } ]
I have a hit that is verbatim what I searched. However it has the same score as several other hits that are not exactly what I searched. This 'score' system that mongoDB is not very helpful like this.
Is there a way to change this score system to give verbatim matches a higher score? There are also results that are over 1, which are even worse hits for my query.

Related

Array of Mongodb Document only show ObjectId

So I want to update a document in Mongo.
I have a document and I want to modify it and send it back. But for some reason, when I do, it changes the value into ObjectId. And I figured out an alternative, but I don't like it...
async updateDogs() {
const cats = await this.catModel.find().exec();
const dog: DogDocument = await this.dogModel
.findOne({ _id: '62b57f65fa0e953b3e0494eb' })
.populate('cats')
.exec();
console.log('All cats:', cats);
console.log('Dingo Dog in find', dog);
dog.cats = cats;
console.log('Dingo dog in find but with Cat ObjectId()', dog.cats);
console.log(dog.cats[0].name);
dog.cats = [];
dog.cats.push(...cats);
console.log('Dingo dog in find and with cat', dog.cats);
console.log(dog.cats[0].name);
}
And this is the output:
All cats: [
{
_id: new ObjectId("62af2508025adb0b7e6e446f"),
name: 'Mikey',
age: 0,
breed: 'string',
__v: 0
},
{
_id: new ObjectId("62b57fd0fa0e953b3e0494f5"),
name: 'Mini',
age: 0,
breed: 'string',
__v: 0
}
]
Dingo Dog in find {
_id: new ObjectId("62b57f65fa0e953b3e0494eb"),
name: 'Dingo',
age: 0,
__v: 0,
cats: [
{
_id: new ObjectId("62af2508025adb0b7e6e446f"),
name: 'Mikey',
age: 0,
breed: 'string',
__v: 0
}
]
}
Dingo dog in find but with Cat ObjectId() [
new ObjectId("62af2508025adb0b7e6e446f"),
new ObjectId("62b57fd0fa0e953b3e0494f5")
]
First cat name is undefined
Dingo dog in find and with cat [
{
name: 'Mikey',
age: 0,
breed: 'string',
_id: new ObjectId("62af2508025adb0b7e6e446f"),
__v: 0
},
{
name: 'Mini',
age: 0,
breed: 'string',
_id: new ObjectId("62b57fd0fa0e953b3e0494f5"),
__v: 0
}
]
First cat name is Mikey
The way i add cats to dog.cats it change the result.
Do you have any idea why ? I Need to have CatDocument and not ObjectId.
And i can do another populate but i don't want to make another request because i already have it. And use push is ugly i think...

Mongoose .populate not showing in JSON

I have some code which populates a section called "user". When I console.log the result I get a nicely filled document like this:
[ { _id: 60f92778ceefc423d834d9c0,
title: 'Vaatwasser',
day: 0,
house: '60f455b1e0394e12a226045a',
user: { _id: 60f4142ff55f81251a7224ff, name: 'Lucas Blommers' },
createdAt: 2021-07-22T08:08:24.477Z,
updatedAt: 2021-07-22T08:08:24.477Z,
__v: 0 },
{ _id: 60f92788ceefc423d834d9c3,
title: 'Koken',
day: 0,
house: '60f455b1e0394e12a226045a',
user: { _id: 60f4142ff55f81251a7224ff, name: 'Lucas Blommers' },
createdAt: 2021-07-22T08:08:40.712Z,
updatedAt: 2021-07-22T08:08:40.712Z,
__v: 0 } ]
But when I request the data using Android or Postman I get the following result:
{
"_id": "60f92778ceefc423d834d9c0",
"title": "Vaatwasser",
"day": 0,
"house": "60f455b1e0394e12a226045a",
"user": {},
"createdAt": "2021-07-22T08:08:24.477Z",
"updatedAt": "2021-07-22T08:08:24.477Z",
"__v": 0
},
As you can see the user is an empty object after it gets send.
Is this a bug in the JSON library or am I doing something wrong on the server.
-Edit-
Here's the server side code:
const housekeepings = await Housekeeping.find({day:day, house:
house}).populate("user", "name")
console.log(housekeepings)
return res.send(housekeepings)
"You can try await Housekeeping.find({day:day, house: house}).populate("user", "name").lean() – Cuong Le Ngoc"
This is the answer. Thank you very much Cuong.

How to use aggregate in mongoosejs with this example

I am here to ask a question about mongo aggregate function to achieve this example.
Scenario
I have 3 Mongo Schema i.e House, family and educations which as :
House: {
_id: ObjectId,
state: Number,
houseNumber: Number
}
Family: {
_id: ObjectId,
houseId: ObjectId,//ref: house
name: String,
gender: String
}
Education: {
_id: ObjectId,
familyId: ObjectId,//ref: Family
level: String, //might be one of ["primary","secondary","higher_secondary"]
}
Expected Output:
{
state1: {
primary: {
male: 3,
female: 4
},
secondary: {
male: 4,
female: 8
}
},
state2: {
primary: {
male: 5,
female: 4
},
secondary: {
male: 4,
female: 6
}
}
}
I want to group all the education level by gender and then ward.
What I did:
I am newbie in mongo world and recently shifted from sql to no-sql. I had done this:
let edu = await Education.find({level: "primary"}).populate({
path: "family",
match: {gender: "male"},
select: "house",
populate: {
path: "house",
match: {state: 1},
select: "_id"
}
});
let count = (await edu.filter(each => !isEmpty(each.family) && !isEmpty(each.family.house)).length) || 0;
By doing this I get count of male member who has studied primary from state 1. but I cannot afford to call this function one by one for each data.
As requestd the sample data are:
house = [
{
_id: AA1,
state: 1,
houseNumber: 101
},
{
_id: AA2,
state: 1,
houseNumber: 102
},
{
_id: AA3,
state: 2,
houseNumber: 201
}
];
family = [
{
_id: BB1,
houseId: AA1, //ref: house
name: "John",
gender: "male"
},
{
_id: BB2,
houseId: AA1, //ref: house
name: "Solena",
gender: "female"
},
{
_id: BB3,
houseId: AA2, //ref: house
name: "Efrain",
gender: "male"
},
{
_id: BB4,
houseId: AA3, //ref: house
name: "Naruto",
gender: "male"
}
];
education = [
{
_id: CC1,
familyId: AA1, //ref: Family
level: "primary"
},
{
_id: CC2,
familyId: AA2, //ref: Family
level: "secondary"
},
{
_id: CC3,
familyId: AA3, //ref: Family
level: "primary"
},
{
_id: CC4,
familyId: AA4, //ref: Family
level: "secondary"
}
];
P.S expected output is not relevant output to the sample data. And ObjectId has been replaced with some unique reference.
Any lead from here guyz?
You can use below aggregation query in 4.x version.
Query the family collection and join to education collection to get the level followed by join to house collection to get the state.
Once you have all the data group by state, level and gender to count all the matches followed by other groups for formatting result. Last stage to promote the aggregated result into its own document.
Last two groups formatting the results from previous stage into named key value document. First group to format the results into gender and count grouped by state. Second group to format the previously combined gender and count with education key.
Finally replace root stage to format the combined gender, count and education doc with state key.
Also added output after each stage for clarity.
db.family.aggregate(
[
{"$lookup":{
"from":"education",
"localField":"_id",
"foreignField":"familyId",
"as":"education"
}},
{"$unwind":"$education"},
{"$lookup":{
"from":"house",
"localField":"houseId",
"foreignField":"_id",
"as":"state"
}},
{"$unwind":"$state"},
{"$group":{
"_id":{
"state":"$state.state",
"education":"$education.level",
"gender":"$gender"
},
"count":{"$sum":1}
}},//{"_id":{"state" :1,"education" :"secondary","gender":"female"},"count":1}
{"$group":{
"_id":{"state":"$_id.state","education":"$_id.education"},
"gandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.gender","$count"]]]}}
}},//{"_id":{"state":1,"education":"primary"},"gandc":{"male":2}}
{"$group":{
"_id":"$_id.state",
"egandc":{"$mergeObjects":{"$arrayToObject":[[["$_id.education","$gandc"]]]}}
}},//{"_id":1,"egandc":{"primary":{"male":2},"secondary":{"female":1}}}
{"$replaceRoot":{"newRoot":{"$arrayToObject":[[[{"$toString":"$_id"},"$egandc"]]]}}} ])
])
//{"1":{"primary":{"male" : 2 },"secondary":{"female":1}}}

How can I select document with conditional subdocs?

I need to find documents with Mongoose that contains at least one subdocument, respecting the $elemMatch condition.
My main doc sample is like this:
{
desc: 'Sample',
user: 10,
prices: [{ code: 1, price: 10 }, { code: 2, price: 0 }]
}
I need the documents that contains at least one subdoc with price bigger than 0. If doc no have price bigger than 0, the main doc may be discarded.
With $elemMatch, I can filter the subdocs, but not the main doc:
Prod.find({ user: 10}, { prices: { $elemMatch: { price: { $gt: 0 } } } })
With this, I have the document:
[{
desc: 'Sample',
user: 10,
prices: []
}]
When should it be:
[]
How can I do this?

Mongo Aggregation Framework with Mongoose raising Document Limit exception on simple Projection

I have a collection links (schema below) with almost 500k entries.
{
url,
title,
owner,
stars: { users: [{ name }]},
createdAt
}
and I really do not understand why the simple aggregation projection
var projection = { $project: { _id: 1, url: 1, title: 1, createdAt: 1 } }
Link.aggregate([projection]).exec(resultCallback);
raises an
MongoError: exception: aggregation result exceeds maximum document size (16MB)
could you explain me ?
I'm using Mongoose (3.8.8) and Mongodb (2.6.0)
Not sure if the options available from MongoDB 2.6 and on-wards are fully available in the .aggregate() method implementation in mongoose. But there should be an options "hash/object" available after the pipeline argument. So basically:
var pipeline = [{ $project: { _id: 1, url: 1, title: 1, createdAt: 1 } }];
Link.aggregate(pipeline,{ cursor: true}, function(err,cursor) {
});
Or if mongoose doesn't like that for some reason then just get the raw node driver collection:
var pipeline = [{ $project: { _id: 1, url: 1, title: 1, createdAt: 1 } }];
Link.collection.aggregate(pipeline,{ cursor: true}, function(err,cursor) {
if (err)
throw err;
// Do something with the cursor which is actually more akin to a node
// stream interface with a basic .next() method and other helpers.
});
Otherwise since you output is blowing up the 16MB BSON limit then you can always output to a collection:
var pipeline = [
{ $project: { _id: 1, url: 1, title: 1, createdAt: 1 } },
{ $out: "newcollection" }
];
But since you are probably just really testing, why not just use the $limit pipeline stage until you work out the rest of your aggregation:
var pipeline = [
{ $project: { _id: 1, url: 1, title: 1, createdAt: 1 } },
{ $limit: 50 }
];
So there are a few different ways to handle things.

Resources