text search returning empty array - node.js

my query is returning me an empty array when I try to text search in mongodb I already created an index into my database.
For example:
I have 2 data type of String declared in my model status and mac_address both of them already included in the text index. When I search for a mac_address it gives me the correct data but when I tried to search for the status it returns an empty array.
--Model--
const PhoneSchema = new mongoose.Schema({
status: {
type: String,
default: "DOWN"
},
mac_address: {
type: String,
unique: true
},
});
--Index--
db.phones.createIndex({
status: "text",
mac_address: "text"
});
--Route--
router.get('/search/:searchForData',
async function (req, res) {
try {
const searchPhone = await Phone.find({
$text: {
$search: req.params.searchForData
}
}, {
score: {
$meta: "textScore"
}
}).sort({
score: {
$meta: "textScore"
}
})
res.status(200).json(searchPhone);
} catch (err) {
return res.status(404).json({
error: err.message
});
}
});
db.phones.getIndexes()
[
{
"v" : 2,
"key" : {
"_id" : 1
},
"name" : "_id_",
"ns" : "pingphony.phones"
},
{
"v" : 2,
"unique" : true,
"key" : {
"ip" : 1
},
"name" : "ip_1",
"ns" : "pingphony.phones",
"background" : true
},
{
"v" : 2,
"unique" : true,
"key" : {
"mac" : 1
},
"name" : "mac_1",
"ns" : "pingphony.phones",
"background" : true
},
{
"v" : 2,
"key" : {
"_fts" : "text",
"_ftsx" : 1
},
"name" : "$**_text",
"ns" : "pingphony.phones",
"weights" : {
"$**" : 1
},
"default_language" : "english",
"language_override" : "language",
"textIndexVersion" : 3
}
]
I expect the output of /phone/search/DOWN to be the data consisting of DOWN status but the actual output I get is []

Try making your query directly from mongo console:
db.phones.find({ $text: { $search: "DOWN" }})
Try using aggregation pipeline:
const searchPhone = await Phone.aggregate(
[
{ $match: { $text: { $search: "DOWN" } } },
{ $sort: { score: { $meta: "textScore" } } },
]
);
If you tried everything and it just didn't go well try using regexp:
const searchQuery = req.params.searchForData.replace(/[.*+?^${}()|[]\]/g, '\$&'); // escape regexp symbols
const searchPhone = await Phone.find({ status: new RegExp(${searchQuery}, 'i') });

Related

MongoDB sort / weight $or aggregated query

I am doing a query with $or aggregation on two fields in the database. The first condition I want to be more relevant than the second, so I want to sort those that matches by first condition first and then return the latter after.
Here is some code:
const regexp = new RegExp('^' + req.query.search, 'i')
const query = {
$or: [{ word: regexp }, { 'lang.english': regexp }],
}
const words = await collection
.find(query, {
collation: {
locale: 'sv',
strength: 1,
},
projection: {
'-_id': 1,
'word': 1,
'lang.english': 1,
'lang.definitionEnglish': 1,
'lang.definition': 1,
},
})
.skip(skips)
.limit(page_size)
.toArray()
So basicly, those results that matches on the word regexp should come first, then lang.english
To start with let's throw some data in to a MongoDB test collection to play around with.
db.test.insertMany([
{ word: 'TEST123', lang: { english: 'wibble1' } },
{ word: 'wibble2', lang: { english: 'Test123' } },
{ word: 'test345', lang: { english: 'wibble3' } },
{ word: 'wibble4', lang: { english: 'testy101' } }
]);
{
"acknowledged" : true,
"insertedIds" : [
ObjectId("5f6924b271e546940acc10f7"),
ObjectId("5f6924b271e546940acc10f8"),
ObjectId("5f6924b271e546940acc10f9"),
ObjectId("5f6924b271e546940acc10fa")
]
}
This has the same structure as mentioned in the question.
Next we can build up an aggregation query that will match the same as your find but this time append an extra field that we can sort on. We'll make this 1 if word field matches then 2 for anything else.
db.test.aggregate([
{ $match: { $or: [{ word: regexp }, { 'lang.english': regexp }] } },
{ $addFields: { sortingValue: { $cond: { if: { $regexMatch: { input: "$word" , regex: regexp } }, then: 1, else: 2 } } } }
]);
{
"_id" : ObjectId("5f6924b271e546940acc10f7"),
"word" : "TEST123",
"lang" : {
"english" : "wibble1"
},
"sortingValue" : 1
}
{
"_id" : ObjectId("5f6924b271e546940acc10f8"),
"word" : "wibble2",
"lang" : {
"english" : "Test123"
},
"sortingValue" : 2
}
{
"_id" : ObjectId("5f6924b271e546940acc10f9"),
"word" : "test345",
"lang" : {
"english" : "wibble3"
},
"sortingValue" : 1
}
{
"_id" : ObjectId("5f6924b271e546940acc10fa"),
"word" : "wibble4",
"lang" : {
"english" : "testy101"
},
"sortingValue" : 2
}
Now we can add the normal sort, skip and limting.
var regexp = new RegExp('^' + 'test', 'i')
var skip = 0;
var limit = 3;
db.test.aggregate([
{ $match: { $or: [{ word: regexp }, { 'lang.english': regexp }] } },
{ $addFields: { sortingValue: { $cond: { if: { $regexMatch: { input: "$word" , regex: regexp } }, then: 1, else: 2 } } } },
{ $sort: { sortingValue: 1 } },
{ $skip: skip },
{ $limit: limit }
]);
{
"_id" : ObjectId("5f6924b271e546940acc10f9"),
"word" : "test345",
"lang" : {
"english" : "wibble3"
},
"sortingValue" : 1
}
{
"_id" : ObjectId("5f6924b271e546940acc10f7"),
"word" : "TEST123",
"lang" : {
"english" : "wibble1"
},
"sortingValue" : 1
}
{
"_id" : ObjectId("5f6924b271e546940acc10f8"),
"word" : "wibble2",
"lang" : {
"english" : "Test123"
},
"sortingValue" : 2
}

Mongoose update object inside a nested array

I know there are hundreds of same questions, but I've read them and can't get this to work.
What I want to do is to update the given object inside tasks array with given req.body params
I tried most of the solutions but probably wrongly implemented them. This is closest to what I can imagine working.
Example document
{
"_id" : ObjectId("5f421bf98bc1d33d7c646535"),
"todoSections" : [
{
"_id" : ObjectId("5f42202b9c6c3040ea0d5326"),
"name" : "first section",
"tasks" : [
{
"status" : 4,
"priority" : 2,
"_id" : ObjectId("5f42274952e6864b19252e37"),
"name" : "task 1",
"assignedUsers" : [],
"comments" : [],
"subTasks" : []
},
{
"status" : 4,
"priority" : 2,
"_id" : ObjectId("5f422a6377eaa74f2e403940"),
"name" : "task 2",
"creationDate" : ISODate("2020-08-23T08:35:47.497Z"),
"assignedUsers" : [],
"comments" : [],
"subTasks" : []
}
]
},
{
"_id" : ObjectId("5f42202f9c6c3040ea0d5327"),
"name" : "another section..",
"tasks" : []
},
],
"__v" : 0
}
My code
const mongodb = require("mongodb");
const { ObjectId } = require("mongodb");
Todo.updateOne(
{
_id: req.body.todoId,
},
{
$set: {
"todoSections.$[outer].tasks$[inner]": {
name: req.body.name,
status: req.body.status
},
},
},
{
arrayFilters: [
{ "outer._id": ObjectId(req.body.sectionId) },
{ "inner._id": ObjectId(req.body.taskId) },
],
},
(err, result) => {
if (!err) {
if (result.nModified === 0) {
res.status(400).send(result);
console.log(result);
return;
} else {
res.status(200).send("ok");
}
} else {
res.status(400).send(err);
console.log(err);
return;
}
}
);
Result returns:
{
"ok": 0,
"n": 0,
"nModified": 0
}

Mongoose update value in Array of Array in NodeJS

my Test Schema:
var TestSchema = new Schema({
testName: String,
topic: {
topicTitle: String,
topicQuestion: [
{
questionTitle: String,
choice: [
{
name: String
age: Number
}
]
}
]
}
}, { collection: 'test' });
var Test = mongoose.model('test', TestSchema);
I want to update one age ($inc)value which I have the choice id.
I can have test id, topicQuestion id and choice id.
How to write this query in mongoose in NodeJS?
Normally I use the below query to update a value:
Test.findOneAndUpdate({ _id: testId }, { $inc: { ... } }, function (err, response) {
...
});
but it is so difficult to get in array and one more array. Thanks
You can use the $[] positional operator to update nested arrays.
router.put("/tests/:testId/:topicQuestionId/:choiceId", async (req, res) => {
const { testId, topicQuestionId, choiceId } = req.params;
const result = await Test.findByIdAndUpdate(
testId,
{
$inc: {
"topic.topicQuestion.$[i].choice.$[j].age": 1
}
},
{
arrayFilters: [{ "i._id": topicQuestionId }, { "j._id": choiceId }],
new: true
}
);
res.send(result);
});
Let's say we have this existing document:
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2116"),
"testName" : "Test 1",
"topic" : {
"topicTitle" : "Title",
"topicQuestion" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211a"),
"questionTitle" : "Question 1 Title",
"choice" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211c"),
"name" : "A",
"age" : 1
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf211b"),
"name" : "B",
"age" : 2
}
]
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2117"),
"questionTitle" : "Question 2 Title",
"choice" : [
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2119"),
"name" : "C",
"age" : 3
},
{
"_id" : ObjectId("5e53e7d9bf65ac4f5cbf2118"),
"name" : "D",
"age" : 4
}
]
}
]
},
"__v" : 0
}
If we want to increment age value of a given choice, we send a PUT request using endpoint like this http://.../tests/5e53e7d9bf65ac4f5cbf2116/5e53e7d9bf65ac4f5cbf211a/5e53e7d9bf65ac4f5cbf211b where
"testId": "5e53e7d9bf65ac4f5cbf2116"
"topicQuestionId": "5e53e7d9bf65ac4f5cbf211a"
"choiceId": "5e53e7d9bf65ac4f5cbf211b"
You need to inform what choice you want and, on the update section, you need change the way you do increment.
Example:
Test.findOneAndUpdate({ _id: testId, topicQuestion.choice._id: choiceId}, { 'topicQuestion.$.choice': {$inc: { age: <numberToIncrement> }}}, {new: true}, function (err, response) {
...
});

Mongoose create a subobject in a subobject

I want to create a subdocument in a subobject field, not to update.
My Schema:
var DemandeSchema = new Schema({
titre: {
type: String,
required: true
},
description: {
type: String,
required: true
},
type: {
type: String,
required: true
},
answer: {}
});
My code:
demande.update(
{ name: 'answer' },
{ $push: req.body.answer },
{ upsert: true },
function(error, user) {
if (error) return next(error);
else {
return true;
}
}
)
req.body.answer = {
"id": "57f512f4360d8818a4e5ea3d",
"answer": {
"122547eee99" : {
"review" : "1.3",
"login" : "new"
}
}
}
But this code doesn't create a new field in my DB, it just updates the field answer when I just want to create a new object field in the answer field.
Actual Result:
{
"_id" : ObjectId("57f512f4360d8818a4e5ea3d"),
"titre" : "TEST",
"description" : "ee",
"type" : "ee",
"__v" : 0,
"answer" : {
"122547eee98" : {
"review" : "8.8",
"login" : "x"
}
}
}
Expected Result:
{
"_id" : ObjectId("57f512f4360d8818a4e5ea3d"),
"titre" : "TEST",
"description" : "ee",
"type" : "ee",
"__v" : 0,
"answer" : {
"122547eee98" : {
"review" : "8.8",
"login" : "x"
},
"122547eee99" : {
"review" : "1.3",
"login" : "new"
}
}
}
var DemandeSchema = new Schema({
titre: {
type: String,
required: true
},
description: {
type: String,
required: true
},
type: {
type: String,
required: true
},
answer: []
});
Answer field curly braces would convert to square brackets for pushing all new answers.
Conclusion: It creates an array.
Instead of the $push operator which works on arrays, use the $set operator together with the dot notation to set the subdocument in the embedded answer document.
You would need to preprocess the document to use in your update so that it will have the dot notation. The following mongo shell example demonstrates this:
var obj = {
"id": "57f512f4360d8818a4e5ea3d",
"answer": {
"122547eee99" : {
"review" : "1.3",
"login" : "new"
}
}
},
update = {};
var key = Object.keys(obj.answer)[0]; // get the dynamic key "122547eee99"
update["answer."+key] = obj.answer[key]; // create the update object with dot notation
/*
update = {
"answer.122547eee99": {
"review" : "1.3",
"login" : "new"
}
}
*/
db.demandes.update(
{ "_id" : ObjectId(obj.id)},
{ "$set": update },
{ "upsert": true }
)
Using the same concept as above, you can create the documents to use in your update as follows:
var update = {},
key = Object.keys(req.body.answer.answer)[0]; // get the dynamic key "122547eee99"
// create the update object with dot notation
update["answer."+key] = req.body.answer.answer[key];
demande.update(
{ "_id": req.body.answer.id },
{ $set: update },
{ upsert: true },
function(error, user) {
if (error) return next(error);
else {
return true;
}
}
);
Try this, and in schema answer: [],
demande.findOne( { name: 'answer' }, function(err, result){
result.answer.push({ans:req.body.answer})
var dem = new Demande(result); // Demande is ur mongoose schema model,
dem.save(function(err, result){
console.log(result);
});
})

Mongoose: aggregation with $match by "_id.property"

In a mongo (2.4.x) collection with documents with this structure:
db.col.insert({ "_id" : { "owner" : "john" }, "value" : 10 });
db.col.insert({ "_id" : { "owner" : "mary" }, "value" : 20 });
I am using mongoose (3.5.4) and trying to run an aggregation pipeline without success. This works fine from the mongo command line client:
> db.col.aggregate({ $match : { "_id.owner": "john" } });
{ "result" : [ { "_id" : { "owner" : "john" }, "value" : 10 } ], "ok" : 1 }
But it does not work as expected when I use mongoose:
Col.aggregate(
{ $match : { "_id.owner": "john" } },
function(err, result) {
console.log("ERR : " + err + " RES : " + JSON.stringify(result));
}
);
ERR : null RES : []
This is the mongoose Schema I am using:
var Col = new Schema({
_id: {
owner: String
},
value: Number
});
I have to say that a simple Col.find({ "_id.owner": "john" }) works as expected.
How could I make it work? I can not modify the structure of my documents.
As I workaround I included a $project before the match, but I am still looking for a cleaner solution:
{
$project: {
"_id": 0,
"owner": "$_id.owner",
"value": 1
}
}

Resources