Elasticsearch full-text query issue from inner array - node.js

This is my object. and I want find text inside of task i.e task.name.
{
"_id" : ObjectId("55e2acd77e5e4ddc037b3ef5"),
"userId" : "123",
"username" : "user12",
"address" : "abc",
"number" : 928228828282.0,
"task" : [{
"name" : "metac",
"productCode" : "1234",
"_id" : ObjectId("55e2acd77e5e4ddc037b3ef7")
}, {
"name" : "alfa33",
"productCode" : "1234",
"_id" : ObjectId("55e2acd77e5e4ddc037b3ef6")
}],
"__v" : 0
}
so when I query for that it will return all task.
curl -XPOST 'localhost:9200/userprofiles/_search?pretty' -d '
{
"query": { "match": { "name": "alfa33" } }
}
Output:
{
"took": 51,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 0.19178301,
"hits": [
{
"_index": "userprofiles",
"_type": "userprofile",
"_id": "55e2acd77e5e4ddc037b3ef5",
"_score": 0.19178301,
"_source": {
"userId": "123",
"username": "user12",
"address": "abc",
"number": 928228828282,
"task": [
{
"name": "metac"
},
{
"name": "alfa33"
}
]
}
}
]
}
}
As you can see task return full array I want only 1 task which is selected.
I am using mongoosastic inside node it will give me problem so i tried to use request directly to Elasticsearch.
mongoosastic config - >elasticsearch search text return full array issue i tried this solution but not working.
Currently I am search my result using curl command in git bush not using search function
EDIT
FILE:- mongoose and mongoosastic.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var medicineSchema = require('./search')
var mongoosastic = require("mongoosastic");
var UserProfileSchema = new Schema({
userId: String,
username: String,
address: String,
number: Number,
task: [{
name: {
type: String,
es_boost: 2.0 // or es_indexed:true
},
taskCode: String,
}]
});
UserProfileSchema.plugin(mongoosastic);
UserProfileSchema.plugin(mongoosastic, {
host: "localhost",
port: 9200,
// ,curlDebug: true
});
UserProfile = module.exports = mongoose.model('UserProfile', UserProfileSchema);
UserProfile.createMapping(function(err, mapping) {
if (err) {
console.log('error creating mapping (you can safely ignore this)');
console.log(err);
} else {
console.log('mapping created!');
console.log(mapping);
}
});
And my search Query:
var UserProfileSchema = require('../../app/models/user');
UserProfileSchema.search({
query_string: {
query: name
}
}, function(err, result) {
if (err) {
callback({
RESULT_CODE: '-1',
MESSAGE: 'System error'
});
} else {
callback({
RESULT_CODE: '1',
DATA: result
});
}
});

In order to treat these as separate objects, you will need to use a nested type
for the "task" field, so setting up your mappings as follows:
{
"mappings": {
"doc": {
"... all the other fields ...": {},
"task": {
"properties": {
"_id": {
"type": "string"
},
"name": {
"type": "string"
},
"productCode": {
"type": "string"
}
},
"type": "nested"
}
}
}
}
After reindexing your document. You'll need to use a nested query with an
inner-hits query to return which task matched the query:
{
"query": {
"nested": {
"path": "task",
"query": {
"match": {
"name": "alfa33"
}
},
"inner_hits": {}
}
}
}
The inner_hits portion will return the specific task that matched the query by
itself.
I've posted an example with the
full output here since it's a
little long to post in entirety here.

Related

How to filter sub values from the object using ExpressJS and MongoDB?

I have a data from MongoDB where I'm getting all the data of the agent, and I want to get the testimonials with isDisplayed is true only. Is there a way in ExpressJS & MongoDB where I can filter the testimonial key?
Here's what I tried
const getSingleAgent = expressAsync(async (req, res) => {
const agent = await Agents.findOne({
_id: req.params.id,
});
res.json(agent);
} else {
res.status(404);
throw new Error("Agent not found.");
}
});
Actual Result
{
"_id": "63ea901a85d4fbd62fb887b3",
"name": "test namee",
"isDeclined": false,
"testimonials": [
{
"isDisplayed": true,
"name": "test123123123123123",
},
{
"isDisplayed": false,
"name": "test123123123123123",
},
{
"isDisplayed": false,
"name": "test#gmail.com",
},
],
}
Expected Result
{
"_id": "63ea901a85d4fbd62fb887b3",
"name": "test namee",
"isDeclined": false,
"testimonials": [
{
"isDisplayed": true,
"name": "test123123123123123",
},
],
}
You can use $elemMatch into a projection stage like this:
db.collection.find({
"_id": "63ea901a85d4fbd62fb887b3",
"testimonials.isDisplayed": true
},
{
"name": 1,
"isDeclined": 1,
"testimonials": {
"$elemMatch": {
"isDisplayed": true
}
}
})
Example here
But be careful. using $elemMatch will only return the first match. Look at this example where there are two true but only one is returned.
As explained into docs:
The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition.
So if you have or want more than one value you can use and aggregation pipelina and $filter into a $project stage:
db.collection.aggregate([
{
"$match": {
"_id": "63ea901a85d4fbd62fb887b3",
"testimonials.isDisplayed": true
}
},
{
"$project": {
"name": 1,
"isDeclined": 1,
"testimonials": {
"$filter": {
"input": "$testimonials",
"cond": {
"$eq": [
"$$this.isDisplayed",
true
]
}
}
}
}
}
])
Example here
Note how in this example if exists two true values, both are displayed.

Update String inside object inside array mongodb

I have a document that looks something like this
{
"_id":{
"$oid":"id"
},
"side1":[
{
"username":"test1",
"id":"id1",
},
{
"username":"test2",
"id":"id2",
},
{
"username":"test3",
"id":"id3",
}
],
"side2":[
{
"username":"test4",
"id":"id4",
},
{
"username":"test5",
"id":"id5",
},
{
"username":"test6",
"id":"id6",
}
],
}
I want to be able to search and update one of the sides, for example, if I searched with username for side1 and that username would be there then I would be able to $set other fields for the object with this username. Something like: Search side1 username test1: $set result.id: "43242342" this would set the id of the object with the username of test1 to 43242342. I am not sure on how I would go about doing this, I have tried using $elemMatch but that didn't bring any results.
Test.findOne({ id: id },
{ side1: { $elemMatch: {username: username} } }, function (err, result) {
if (err) {
console.log(err);
} else {
console.log(result)
}
});
I'm not exactly sure how you want to update the document, but perhaps this is what you are looking for?
db.collection.update({
"_id": ObjectId("000000000000000000000001"),
"side1.username": "test1"
},
{
"$set": {
"side1.$.id": "43242342"
}
})
Try it on mongoplayground.net.
Example updated document:
[
{
"_id": ObjectId("000000000000000000000001"),
"side1": [
{
"id": "43242342",
"username": "test1"
},
{
"id": "id2",
"username": "test2"
},
{
"id": "id3",
"username": "test3"
}
],
"side2": [
{
"id": "id4",
"username": "test4"
},
{
"id": "id5",
"username": "test5"
},
{
"id": "id6",
"username": "test6"
}
]
}
]
Could you do something like this?
function addProperty(id, side, username, property, value) {
const query = {
_id: {
$oid: id,
},
};
const update = {
$push: {
[side]: {
username: username,
[property]: value,
},
},
};
const options = {
upsert: true,
};
db.collection("Test").updateOne(query, update, options);
}

Autocomplete filtered with mongodb

I would like to perform autocompletion on the name but filtered on a specific city with mongoose and nodejs.
I have a mongodb collection like this :
{
"_id" : ObjectId("6007fd9d984e2507ad452cf3"),
"name" : "John",
"city" : "A",
},
{
"_id" : ObjectId("6007ff6844d9e517e1ec0976"),
"name" : "Jack",
"city" : "B",
}
What i have done so far :
I have setup MongoDB Atlas with a Search Index (with the help of search doc)
And set up the autocomplete like that :
router.get('/search', async (request, response) => {
try {
let result = await Client.aggregate([
{
"$search": {
"autocomplete": {
"query": `${request.query.term}`,
"path": "name",
"fuzzy": {
"maxEdits": 2,
"prefixLength": 3,
},
},
},
},
{
$limit: 3
},
{
$project: {
"_id": 0,
}
}
]);
response.send(result);
} catch (e) {
response.status(500).send({message: e.message});
}
});
In front-end, with autocompleteJs :
const autoCompleteJS = new autoComplete({
data: {
src: async () => {
const query = document.querySelector("#autoComplete").value;
const source = await fetch(`${window.location.origin}/search?term=${query}`);
const data = await source.json();
return data;
},
key: ["name"],
},
trigger: {
event: ["input", "focus"],
},
searchEngine: "strict",
highlight: true,
});
So far it is working well. But I don't know how to make the autocomplete result filtered based on city. It seems that the documentation does not mention this. Do you have any leads.
Use the $where pipeline stage from the aggregation pipeline after performing your search to filter out unwanted documents. So for example,
Client.aggregate([
{
"$search": {
"autocomplete": {
"query": `${request.query.term}`,
"path": "name",
"fuzzy": {
"maxEdits": 2,
"prefixLength": 3,
},
},
},
},
{
$match: { city: 'city-name' }
},
{
$limit: 3
},
{
$project: {
"_id": 0,
}
}
]);
Use a compound operator like so which lets you have more control over your results in a performant fashion:
"$search": {
"compound" : {
"filter" : [{
"text" : { path: "city", query: "New York" }
}],
"must": [{
"autocomplete": {
"query": `${request.query.term}`,
"path": "name",
"fuzzy": {
"maxEdits": 2,
"prefixLength": 3,
},
},}
}] }
using filter will filter your results, without impacting the score. must will require that the name field will also match. Also take a look at should and mustNot in the compound docs for more options.

array update in mongoDB

I've got mongoose schema like this:
var mySchema = new mongoose.Schema({
user: String,
photos: [{
url: String,
thumbnail: String,
time: Date,
mainPhoto: Boolean
}]
});
now, I'd like to make a "setMainPhoto" function - for given id - set mainPhoto flag to true, and set it to false for other photos.
There is a document like this:
{
"_id": {
"$oid": "56269dea07a455920a21bca7"
},
"user": "someUser",
"photos": [
{
"time": {
"$date": "2015-05-25T08:37:56.000Z"
},
"thumbnail": "https://s3-eu-west-1.amazonaws.com/...jpg",
"url": "https://s3-eu-west-1.amazonaws.com/...jpg",
"_id": {
"$oid": "56269e2e07a455920a21bcab"
},
"mainPhoto": false
},
{
"time": {
"$date": "2015-05-25T09:27:17.000Z"
},
"thumbnail": "https://s3-eu-west-1.amazonaws.com/...jpg",
"url": "https://s3-eu-west-1.amazonaws.com/...jpg",
"_id": {
"$oid": "56269e3507a455920a21bcae"
},
"mainPhoto": true
},
{
"time": {
"$date": "2015-05-25T09:27:17.000Z"
},
"thumbnail": "https://s3-eu-west-1.amazonaws.com/...jpg",
"url": "https://s3-eu-west-1.amazonaws.com/...jpg",
"_id": {
"$oid": "56269e3507a455920a21bcaf"
},
"mainPhoto": false
}
]}
I'm trying to do this with mongoose save function:
var myModel = mongoose.model('myCollection', mySchema);
myModel.find({user:params.user,_id:params.documentId}, function(err,data){
var newMyModel = new myModel(data[0]);
for(var i=0; i<newMyModel.photos.length; i++){
if(newMyModel.photos[i]._id == params.photoId){
newMyModel.photos[i].mainPhoto = true;
}else{
newMyModel.photos[i].mainPhoto = false;
}
}
return newTrack.save(function(err,results){
console.log(err,results);
return cb(err,results);
});
});
but I'm getting an error:
'E11000 duplicate key error index: xx.$_id_ dup key: { : ObjectId(\'56269dea07a455920a21bca7\') }',
apart from the fact I feel there is a better way to do this
You may want to try a conditional type of update operation that first updates the documents based on the given criteria using the .update() method with a callback, and then do the other update in the callback for documents that do not satisfy the given criteria. For example, you could restructure this as:
var Model = mongoose.model('myCollection', mySchema);
Model.update(
{
"_id": params.documentId,
"user": params.user,
"photos.mainPhoto": true
},
{
"$set": {
"photos.$.mainPhoto": false
}
},
function(err, numAffected) {
Model.update(
{
"_id": params.documentId,
"user": params.user,
"photos._id": params.photoId /* criteria for setting the updating the documents to true */
},
{
"$set": {
"photos.$.mainPhoto": true
}
},
function(err, numAffected) {
/* Update complete. */
}
)
}
)

MongoDB: Update property of subarray just updates the first element

The matching element looks like that:
{
"_id": {
"$oid": "519ebd1cef1fce06f90e3157"
},
"from": "Tester2",
"to": "Tester",
"messages": [
{
"username": "Tester2",
"message": "heeey",
"read": false
},
{
"username": "Tester",
"message": "hi!",
"read": false
},
{
"username": "Tester2",
"message": "test",
"read": false
}
],
}
Now I try to set the read property to the current date just of the subelements where the username is not equal to Tester:
var messages = db.collection('messages');
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
messages: {
$elemMatch: { username: { $ne: "Tester" } }
}
},
{ $set: { 'messages.$.read': new Date() } },
{ multi: true }, function(error, result) {
console.log(error);
console.log(result);
});
But just the first messages subelement read property updates:
{
"_id": {
"$oid": "519ebd1cef1fce06f90e3157"
},
"from": "Tester2",
"to": "Tester",
"messages": [
{
"username": "Tester2",
"message": "heeey",
"read": {
"$date": "2013-01-01T00:00:00.000Z"
}
},
{
"username": "Tester",
"message": "hi!",
"read": false
},
{
"username": "Tester2",
"message": "test",
"read": false
}
],
}
What's wrong with the code?
I'm using node.js v0.10.8 and MongoDB v2.4.3 together with node-mongodb-native.
There's nothing wrong with your code; the $ operator contains the index of the first matching array element. The {multi: true} option only makes the update apply to multiple documents, not array elements. To update multiple array elements in a single update you must specify them by numeric index.
So you'd have to do something like this:
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157")
},
{ $set: {
'messages.0.read': new Date(),
'messages.2.read': new Date()
} },
function (err, result) { ... }
);
This is similar to question: How to Update Multiple Array Elements in mongodb
var set = {}, i, l;
for(i=0,l=messages.length;i<l;i++) {
if(messages[i].username != 'tester') {
set['messages.' + i + '.read'] = new Date();
}
}
.update(objId, {$set:set});
I think ArrayFilters can be used in this case
I know it's a little bit late to answer this question but if you use the $[] operator, it will do the trick.
more details here
https://www.mongodb.com/docs/manual/reference/operator/update/positional-all/
I was able to use it inside an updateMany operation, but it should work also for update
var messages = db.collection('messages');
messages.update(
{
_id: new BSON.ObjectID("519ebd1cef1fce06f90e3157"),
messages: {
$elemMatch: { username: { $ne: "Tester" } }
}
},
{ $set: { 'messages.$[].read': new Date() } },
{ multi: true }, function(error, result) {
console.log(error);
console.log(result);
});

Resources