Phrase Searching in ElasticSearch with English Analyzer - javascript

I'm currently using elastic search and have several type of queries, among them I use the match_phrase query. The index I'm using this on uses an english analyzer for text messages. When I search for phrases I'm expecting exact results, but if my search term has an english word - like remove - it also marks words like "removed", "removing" "removes".
How do I prevent this with my phrase matching? Is there a better option other than match_phrase for queries like this?
Is this possible without changing the analyzer? Below is my query (structured so it can do other things):
query: {
fields : ['_id', 'ownerId'],
from: 0,
size: 20,
query: {
filtered: {
filter: {
and: [group ids]
},
query: {
bool: {
must: {
match_phrase: {
text: "remove"
}
}
}
}
}
}
}
And here is my index:
[MappingTypes.MESSAGE]: {
properties: {
text: {
type: 'string',
index: 'analyzed',
analyzer: 'english',
term_vector: 'with_positions_offsets'
},
ownerId: {
type: 'string',
index: 'not_analyzed',
store: true
},
groupId: {
type: 'string',
index: 'not_analyzed',
store: true
},
itemId: {
type: 'string',
index: 'not_analyzed',
store: true
},
createdAt: {
type: 'date'
},
editedAt: {
type: 'date'
},
type: {
type: 'string',
index: 'not_analyzed'
}
}
}

You can use multi-fields to use a field in different ways(one for exact match and one for partial match etc).
You can get rid of stemming with standard analyzer which is also a default analyzer. You could create your index with following mapping
POST test_index
{
"mappings": {
"test_type": {
"properties": {
"text": {
"type": "string",
"index": "analyzed",
"analyzer": "english",
"term_vector": "with_positions_offsets",
"fields": {
"standard": {
"type": "string"
}
}
},
"ownerId": {
"type": "string",
"index": "not_analyzed",
"store": true
},
"groupId": {
"type": "string",
"index": "not_analyzed",
"store": true
},
"itemId": {
"type": "string",
"index": "not_analyzed",
"store": true
},
"createdAt": {
"type": "date"
},
"editedAt": {
"type": "date"
},
"type": {
"type": "string",
"index": "not_analyzed"
}
}
}
}
}
After that whenever you want exact match you need to use text.standard and when you want to perform stemming(want to match removed removes) you could revert to text
You could also update the current mapping but you would have to reindex your data in both cases.
PUT test_index/_mapping/test_type
{
"properties": {
"text": {
"type": "string",
"index": "analyzed",
"analyzer": "english",
"term_vector": "with_positions_offsets",
"fields": {
"standard": {
"type": "string"
}
}
}
}
}
Does this help?

Related

Mongoose update subdocument by key

for the following collection:
id: Number
name:[
new Schema({
language: { type: String, enum: ['en-US', 'fr-CA'] },
text: String,
},{ _id: false }
);
],
isActive: Boolean
and sample data like the following:
{
id:1,
"name": [
{"language": "en-US", "text": "Book"},
{"language": "fr-CA", "text": "livre"}
],
isActive:true
// and so many other fields
},
{
id:2,
"name": [
{"language": "en-US", "text": "Pen"}
],
isActive:true
// and so many other fields
}
I would like to update the document by Id:1 and change the text only for french, I tried by:
const input={ "id":"1", "isActive": false}
const name= { "name": [{"language": "fr-CA", "text": "Nouvel article"}]}
await langs.findByIdAndUpdate(
{ _id: input.id },
{ ...input, $addToSet: { name } },
{ new: true, upsert: true }
);
but the result is: (added another french item)
{
id:1,
"name": [
{"language": "en-US", "text": "Book"},
{"language": "fr-CA", "text": "livre"},
{"language": "fr-CA", "text": "Nouvel article"}
],
isActive:false
},
This is based on Brit comment:
https://mongoplayground.net/p/atlw5ZKoYiI
Please advise.
As long as that attribute already exists on the document, you can do something like:
Collection.findOneAndUpdate({id: 1}, {
$set: {
"name.$[elem]": name
}
},
{
arrayFilters: [ { "elem.language": name.language } ],
upsert: true,
new: true
})

How to group an array of objects in javascript?

I'm trying to group an array of objects. The array should be grouped following this quote:
Group by type respecting the sequence.
Array I wish to group
var arrayObj = [
{ "type": "user", "text": "user1" },
{ "type": "user", "text": "user2" },
{ "type": "user", "text": "user3" },
{ "type": "clerk", "text": "clerk1" },
{ "type": "user", "text": "user4" },
{ "type": "clerk", "text": "clerk2" },
{ "type": "clerk", "text": "clerk3" },
{ "type": "user", "text": "user5" },
{ "type": "user", "text": "user6" }
];
The way I want it to be grouped:
var newArray = [
[
{type: "user", text: "user1"},
{type: "user", text: "user2"},
{type: "user", text: "user3"}
],
[
{type: "clerk", text: "clerk1"}
],
[
{type: "user", text: "user4"}
],
[
{type: "clerk", text: "clerk2"},
{type: "clerk", text: "clerk3"}
],
[
{type: "user", text: "user5"},
{type: "user", text: "user6"}
]
];
What i tried:
I'm trying to use a filter, but without success since it even groups, but it groups all that are of the same type without respecting the sequence I want (from the array above);
var arrayObj = [
{ "type": "user", "text": "user1" },
{ "type": "user", "text": "user2" },
{ "type": "user", "text": "user3" },
{ "type": "clerk", "text": "clerk1" },
{ "type": "user", "text": "user4" },
{ "type": "clerk", "text": "clerk2" },
{ "type": "clerk", "text": "clerk3" },
{ "type": "user", "text": "user5" },
{ "type": "user", "text": "user6" }
];
var newArray = [];
newArray.push(filtrarArray(arrayObj, 'clerk'));
newArray.push(filtrarArray(arrayObj, 'user'));
console.log(newArray);
function filtrarArray(array, type) {
return array.filter(function (val) {
return val.type === type;
});
}
The snippet below first sorts the array by type, which then makes it easy to loop through and group. Let me know if this solves your problem :)
EDIT
Just realized you didn't need sorting, so I commented the sort function out, but it's always there to be uncommented if you change your mind :)
const arrayObj = [
{ type: 'user', text: 'user1' },
{ type: 'user', text: 'user2' },
{ type: 'user', text: 'user3' },
{ type: 'clerk', text: 'clerk1' },
{ type: 'user', text: 'user4' },
{ type: 'clerk', text: 'clerk2' },
{ type: 'clerk', text: 'clerk3' },
{ type: 'user', text: 'user5' },
{ type: 'user', text: 'user6' },
];
const group = ar =>
ar
// .sort((a, b) => (a.type < b.type ? -1 : 1))
.reduce((newAr, obj, i) => {
if (0 === i) return [[obj]];
if (obj.type === newAr[newAr.length - 1][0].type)
return newAr[newAr.length - 1].push(obj), newAr;
return [...newAr, [obj]];
}, []);
const groupedAr = group(arrayObj);
console.log(groupedAr);
function groupConsecutive(arrayObj) {
if (arrayObj.length === 0) {
return [];
}
let matchedTypesIndex = 0;
let newArray = [
[
arrayObj[0]
]
];
let currentType = arrayObj[0]["type"];
let i = 1;
while (i < arrayObj.length) {
if (arrayObj[i]["type"] === currentType) {
newArray[matchedTypesIndex].push(arrayObj[i]);
} else {
currentType = arrayObj[i]["type"];
newArray.push([]);
matchedTypesIndex++;
newArray[matchedTypesIndex].push(arrayObj[i]);
}
i++;
}
return newArray;
}
This is probably not best pure solution but works as you need.
var arrayObj = [
{ "type": "user", "text": "user1" },
{ "type": "user", "text": "user2" },
{ "type": "user", "text": "user3" },
{ "type": "clerk", "text": "clerk1" },
{ "type": "user", "text": "user4" },
{ "type": "clerk", "text": "clerk2" },
{ "type": "clerk", "text": "clerk3" },
{ "type": "user", "text": "user5" },
{ "type": "user", "text": "user6" }
];
let lastType;
let arr = [];
let arrIndex = -1;
arrayObj.forEach(obj => {
if(obj.type == lastType) { // add item into last group array by index
arr[arrIndex].push(obj);
}
else { // or add new group array
lastType = obj.type;
arrIndex++;
arr.push([obj]);
}
})
console.log(arr);
See This solution it will work
var arrayObj = [
{ type: "user", text: "user1" },
{ type: "user", text: "user2" },
{ type: "user", text: "user3" },
{ type: "clerk", text: "clerk1" },
{ type: "user", text: "user4" },
{ type: "clerk", text: "clerk2" },
{ type: "clerk", text: "clerk3" },
{ type: "user", text: "user5" },
{ type: "user", text: "user6" },
];
let typeNow = arrayObj[0].type;
let res = [];
let resultArray = [];
arrayObj.forEach((obj, i) => {
if (obj.type == typeNow) {
resultArray.push(obj);
} else {
resultArray = [obj];
res.push(resultArray);
typeNow = obj.type;
}
if (i == arrayObj.length - 1) res.push(resultArray);
});
console.log(res);
This is best solution can i have
It seem unordred because of Browser auto order but if you try in js file it will work and ordred
I'm responding with another way I found to solve my problem. This is just one more way I decided to comment.
Let's go:
I'm traversing the arrayObjects array using the appropriate loop for arrays for...of and then checking if the variable I set for arrayObjects (loopArrObj) has a different value of type of the variable (typeValue), if it is inserted at the end of the new array (grouping) using the array method push an empty array and then assign the value of the loopArrObj(Ie, there will be in the array grouping array empty only for the values ​​that are different).
So far so good, we have the first empty array. Next I'm defining this empty array with the push method the loopArrObj object in question, then we get the value through the console. I'm removing 1 from grouping.lengh so the loop assigns from 0 and not 1.
var arrayObjects = [
{ "type": "user", "text": "user1" },
{ "type": "user", "text": "user2" },
{ "type": "user", "text": "user3" },
{ "type": "clerk", "text": "clerk1" },
{ "type": "user", "text": "user4" },
{ "type": "clerk", "text": "clerk2" },
{ "type": "clerk", "text": "clerk3" },
{ "type": "user", "text": "user5" },
{ "type": "user", "text": "user6" }
];
let typeValue,
grouping = [],
loopArrObj;
for (loopArrObj of arrayObjects) {
if (loopArrObj.type !== typeValue) {
grouping.push([]);
typeValue = loopArrObj.type;
}
grouping[grouping.length - 1].push(loopArrObj);
}
console.log(grouping);

How to sort an array of object depending on value of a subfield?

I have an array of objects called posts with a nested field answer. I have different conditions depending on which I need to sort my array.
Example array
let posts = [
{
"_id": "610b806ec6386ffa276b6d1c",
"post": "CDat the difference between Equity and News channel?",
"answers": [
{
"_id": "613724604dd4b6f39b344b4c",
"type": "text",
},
{
"_id": "6113c826a64da80fe48ab543",
"type": "video",
},
{
"_id": "6113c740a64da80fe48ab534",
"type": "audio",
},
{
"_id": "611135cf7497d70b6cc2c5e0",
"type": "video",
}
]
},
{
"_id": "611e14329ff0c343540ae10e",
"post": "How forex affects investment 6",
"answers": [
{
"_id": "61371b3a9bf14a39207fff8a",
"type": "video",
},
{
"_id": "613719369507fd12f93e3c62",
"type": "text",
},
{
"_id": "6135f28e29aeae3de45fc8c2",
"type": "text",
},
{
"_id": "6135f07c831da33c28fc1cf6",
"type": "audio",
},
{
"_id": "6135eb2d51a8830698d65cf3",
"type": "text",
},
]
}
]
What I need to do is...I want to sort all the answers with type "video" to start and after that type "audio" and then type "text" answers like so
let posts = [
{
"_id": "610b806ec6386ffa276b6d1c",
"post": "CDat the difference between Equity and News channel?",
"answers": [
{
"_id": "613724604dd4b6f39b344b4c",
"type": "video",
},
{
"_id": "6113c826a64da80fe48ab543",
"type": "video",
},
{
"_id": "6113c740a64da80fe48ab534",
"type": "audio",
},
{
"_id": "611135cf7497d70b6cc2c5e0",
"type": "text",
}
]
},
{
"_id": "611e14329ff0c343540ae10e",
"post": "How forex affects investment 6",
"answers": [
{
"_id": "61371b3a9bf14a39207fff8a",
"type": "video",
},
{
"_id": "613719369507fd12f93e3c62",
"type": "audio",
},
{
"_id": "6135f28e29aeae3de45fc8c2",
"type": "text",
},
{
"_id": "6135f07c831da33c28fc1cf6",
"type": "text",
},
{
"_id": "6135eb2d51a8830698d65cf3",
"type": "text",
},
]
}
]
Any help would be appreciated. Thank you
You will need to loop through each entry in your posts array and sort the answers array inside each entry using a custom function.
The return value of the custom function (ele1, ele2) => {} should return a value > 0 if ele1 should be placed after ele2, a value < 0 if ele1 should be placed before ele2, and a value = 0 if they are considered equal.
const getRanking = (ele) => {
if (ele.type == "video") return 1;
if (ele.type == "audio") return 2;
if (ele.type == "text") return 3;
};
posts = posts.map(post => {
post.answers.sort((ele1, ele2) => {
return getRanking(ele1) - getRanking(ele2);
});
return post;
});
You should use lodash for this. Check out _.orderBy() method. Here is a helpful link: https://www.geeksforgeeks.org/lodash-_-orderby-method/
You need to add custom compare function.
This is the simplest solution for your question
let posts = [
{
"_id": "610b806ec6386ffa276b6d1c",
"post": "CDat the difference between Equity and News channel?",
"answers": [
{
"_id": "613724604dd4b6f39b344b4c",
"type": "text",
},
{
"_id": "6113c826a64da80fe48ab543",
"type": "video",
},
{
"_id": "6113c740a64da80fe48ab534",
"type": "audio",
},
{
"_id": "611135cf7497d70b6cc2c5e0",
"type": "video",
}
]
},
{
"_id": "611e14329ff0c343540ae10e",
"post": "How forex affects investment 6",
"answers": [
{
"_id": "61371b3a9bf14a39207fff8a",
"type": "video",
},
{
"_id": "613719369507fd12f93e3c62",
"type": "text",
},
{
"_id": "6135f28e29aeae3de45fc8c2",
"type": "text",
},
{
"_id": "6135f07c831da33c28fc1cf6",
"type": "audio",
},
{
"_id": "6135eb2d51a8830698d65cf3",
"type": "text",
},
]
}
]
function type (t){
if(t==="video") return 1;
if(t==="audio") return 2;
if(t==="text")return 3;
}
function comparotor(a, b){
return type(a.type) - type(b.type)
}
posts.map(ele=>{
ele.answers.sort(comparotor);
})
console.log(posts);
You can easily achieve the result by using JS only by passing the comparator function to sort function
"video" => "v"
"audio" => "a"
"text" => "t"
You can use indexOf to check the first letter in a list of sorted letters(vat)
posts.map((o) =>o.answers.sort((a, b) => "vat".indexOf(a.type[0]) - "vat".indexOf(b.type[0])))
let posts = [
{
_id: "610b806ec6386ffa276b6d1c",
post: "CDat the difference between Equity and News channel?",
answers: [
{
_id: "613724604dd4b6f39b344b4c",
type: "text",
},
{
_id: "6113c826a64da80fe48ab543",
type: "video",
},
{
_id: "6113c740a64da80fe48ab534",
type: "audio",
},
{
_id: "611135cf7497d70b6cc2c5e0",
type: "video",
},
],
},
{
_id: "611e14329ff0c343540ae10e",
post: "How forex affects investment 6",
answers: [
{
_id: "61371b3a9bf14a39207fff8a",
type: "video",
},
{
_id: "613719369507fd12f93e3c62",
type: "text",
},
{
_id: "6135f28e29aeae3de45fc8c2",
type: "text",
},
{
_id: "6135f07c831da33c28fc1cf6",
type: "audio",
},
{
_id: "6135eb2d51a8830698d65cf3",
type: "text",
},
],
},
];
const result = posts.map((o) =>o.answers.sort((a, b) => "vat".indexOf(a.type[0]) - "vat".indexOf(b.type[0])));
console.log(result);

Mongoose fetch localization multilingual

I'm using nodejs with mongoose (mongodb) and I want to filter inside a subdocument array the language selected.
User schema:
var localUserSchema = new mongoose.Schema({
firstName: {
type: String
},
moreInformation: {
experience: {
type: Number
},
specializations: [{
...
sports:[{
type: Schema.Types.ObjectId,
ref: 'sport'
}]
}]
});
User data:
[{
"_id": {
"$oid": "5fc6a379b1d5ff2c42a9a536"
},
"moreInformation": {
"experience" : 2,
"specializations": [{
"sports": [{
"$oid": "5fc6aa91b1db6cd15702241c"
}, {
"$oid": "5fcb741e786f0703646befe2"
}]
}]
}
}]
Sport schema:
var sportSchema = new Schema({
name: {
en: {
type: String
},
it: {
type: String
}
},
icon: {
type: Schema.Types.ObjectId, ref: 'file'
}
});
Sport data:
[{
"_id": {
"$oid": "5fc6aa91b1db6cd15702241c"
},
"name": {
"en": "Football",
"it": "Calcio"
},
"icon": {
"$oid": "5fc9598a0955177dee8a3bc4"
}
},{
"_id": {
"$oid": "5fcb741e786f0703646befe2"
},
"name": {
"en": "Swimming",
"it": "Nuoto"
},
"icon": {
"$oid": "5fc9598a0955177dee8a3bc5"
}
}
I want all users joined with sports by sport id, but filtered by language key like below and replace the sports name languages with the name selected without the language key.
So, if I wanted to select and filter the english language 'en', i would like to get this result:
[
{
"_id": "5fc6a379b1d5ff2c42a9a536",
"moreInformation": {
"specializations": [{
...
"sports": [
{
"_id": "5fc6aa91b1db6cd15702241c",
"name": "Football",
"icon": "5fc9598a0955177dee8a3bc4"
},
{
"_id": "5fcb741e786f0703646befe2",
"name": "Swimming",
"icon": "5fc9598a0955177dee8a3bc5"
}]
}]
}
}
}
How I can do it?
You will need to use aggregation methods like $unwind, $lookup, $group last but not least $project.
They define a sequence of steps to help you retrieve your data as you expect
The get the response that you want take a look into the following code and the running example in here
Ps: I suggest you to look into the example in the mongo playground ( link above ) and separate the aggregation pipelines to understand better the proccess
db.users.aggregate([
{
// move specializations array to separated objects
"$unwind": "$moreInformation.specializations"
},
{
// move specialization.sports to separated objects
"$unwind": "$moreInformation.specializations.sports"
},
{
// search into sports collection based on the temporary "sports" attribute
$lookup: {
from: "sports",
localField: "moreInformation.specializations.sports",
foreignField: "_id",
as: "fullSport"
}
},
{
// as the lookup resolves an array of a single result we move it to be an object
"$unwind": "$fullSport"
},
{
// here we select only the attributes that we need and the selected language
"$project": {
"moreInformation.experience": 1,
"moreInformation.specializations.sports._id": "$fullSport._id",
"moreInformation.specializations.sports.icon": "$fullSport.icon",
"moreInformation.specializations.sports.name": "$fullSport.name.en"
}
},
{
// then we group it to make the separated objects an array again
"$group": {
"_id": "$_id",
// we group by the $_id and move it to temporary "sports" attribute
"sports": {
$push: "$moreInformation.specializations.sports"
},
"moreInformation": {
$first: "$moreInformation"
}
}
},
{
$project: {
"moreInformation.experience": 1,
// move back the temporary "sports" attribute to its previous path
"moreInformation.specializations.sports": "$sports"
}
}
])

jsonschema mongodb not validate from .js file

I have mongo.js commands to create collection, here is in my .js file:
let organizationSchema = {
bsonType: "object",
properties: {
name: {
bsonType: "string",
description: "must be a string and required"
},
active: {
bsonType: "bool",
},
location: {
bsonType: "string",
},
contactInfo: {
// this one doest working for validate
pattern: "/^\+?\d+(?:-\d+)*(?:\(\d+\)-\d+)*$/"
},
createdBy: {
bsonType: "string",
},
createdAt: {
bsonType: "date",
},
updatedAt: {
bsonType: "date",
},
deletedAt: {
bsonType: "date",
},
}
}
db.runCommand( { collMod: "organizations", validator:{ $jsonSchema: organizationSchema }, validationLevel: "strict"} )
db.organizations.createIndex( { "name": 1, "createdBy": 1, "location": 1, "active": 1} )
then I run mongo "mongodb+srv://url" --username dev -p 'dev' < mongo.js
on the contactInfo I do pattern for validation phone number, but when I insert data on my MongoDB Client, with invalid phone number, it was inserted, is that something wrong on that syntax on my js should I use strings on each keys there ??
const r = /^\+?\d+(?:-\d+)*(?:\(\d+\)-\d+)*$/;
console.log(r.test("+1110988888"))
console.log(r.test("zzzzuuuu"))

Categories