node elastic search strict match - node.js

can anyone tell me how to strict match in elasticsearch-js. here is my search
client.search({{
index: 'hash_tag',
type: 'hash_tag',
lenient:false,
body: {
query: {
match: {
tag_name: 'hash tag 1'
}
}
}
}).then(function (body) {
console.log("body", JSON.stringify(body));
}, function (error) {
console.trace(error.message);
})
this query search either hash, tag ,1 i'm looking for exact whole string match.here is my example index style.
{
"_index": "hash_tag",
"_type": "hash_tag",
"_id": "3483",
"_score": 0.019691018,
"_source": {
"id": "3483",
"labels": [
"hash_tag"
],
"tag_name": "hash tag 2"
}
}

by default elasticsearch will "tokenize" your text fields to add them to the inverted index, that's why you get results for each term used.
In order to get the full match you can have different approaches, the simplest would be to use a match_frase:
GET /megacorp/employee/_search
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}
Another option would be to add that specific field with a mapping of not_analyzed, then the text wouldn't be tokenized.

Related

MongoDB query to find in nested schema

This query is returning the first object but it should not return. Because it has the BU but in different domain. Its doing fine in single objects in collaborators. When there is multiple Its not behaving as expected. How can we do this any suggestions?
My criteria is In the collaborator array
Only BU name or
Only Domain or
Both BU and Domain it should return.
In below situation first one has same domain <{"domain": "xyz.com"}> but still its not returning. Why?
[
{
name: "1",
collaborators: [
{
"domain": "xyz.com"
},
{
"buName": "Vignesh B"
},
{
"domain": "yz.com"
},
{
"domain": "xyz.com",
"buName": "Vignesh B"
}
]
},
{
name: "2",
collaborators: [
{
"domain": "xyz.com",
"buName": "Vignesh BU"
}
]
},
{
name: "3",
collaborators: [
{
"domain": "xyz.com"
}
]
},
{
name: "4",
collaborators: [
{
"buName": "Vignesh BU"
},
{
"domain": "xyz.com"
},
{
"domain": "xyz.com",
"buName": "Vignesh BU"
}
]
}
]
db.collection.find({
$or: [
{
"collaborators.domain": "xyz.com",
"collaborators.buName": {
"$exists": false
}
},
{
"collaborators.buName": "Vignesh BU",
"collaborators.domain": {
"$exists": false
}
},
{
"collaborators.buName": "Vignesh BU",
"collaborators.domain": "xyz.com"
}
]
})
It is not returning the first document because the buName values in this document are "Vignesh B" and not "Vignesh BU". Only add an U in Vignesh B and it works.
Link to mongodb playground
I think there was a comment at wone point that said that the name: "1" document was expected to return (as it matches the second "Only Domain" criteria) but it is not currently. This is because you will need to use the $elemMatch operator since you are querying an array with multiple conditions.
The query should look as follows, as demonstrated in this playground example (note that I've changed the name: 3 document so that it would not match):
db.collection.find({
$or: [
{
"collaborators": {
$elemMatch: {
"domain": "xyz.com",
"buName": {
"$exists": false
}
}
}
},
{
"collaborators": {
$elemMatch: {
"buName": "Vignesh BU",
"domain": {
"$exists": false
}
}
}
},
{
"collaborators": {
$elemMatch: {
"buName": "Vignesh BU",
"domain": "xyz.com"
}
}
}
]
})
Why is this change needed? It is because of the semantics of how querying an array works in MongoDB. When querying on multiple nested conditions without using $elemMatch you are telling the database that different entries in the array can each individually satisfy the requirements. As shown in this playground example, that means that when you run this query:
db.collection.find({
"arr.str": "abc",
"arr.int": 123
})
The following document will match:
{
_id: 1,
arr: [
{
str: "abc"
},
{
int: 123
}
]
}
This is because the first entry in the array satisfies one of the query predicates while the other entry in the array satisfies the second predicate. Changing the query to use $elemMatch changes the semantics to specify that a single entry in the array must successfully satisfy all query predicate conditions which prevents the document above from matching.
In your specific situation the same thing was happening with your first set of conditions of:
{
"collaborators.domain": "xyz.com",
"collaborators.buName": {
"$exists": false
}
}
The first array item in the name: "1" document was matching the collaborators.domain condition. The problem was the second condition. While that same first array entry did not have a buName field, two of the other entries in the array did. Since there is no $elemMatch present, the database checked those other entries, found that the buName existed there, and that caused the query predicates to fail to match and for the document to not get returned. Adding the $elemMatch forces both of those checks to happen against the single entry in the array hence resolving the issue.

search multiple field as regexp query in elasticsearch

I am trying to search by different fields such as title and description. When i type keywords, elasticseach must found something if description or title includes that i typed keywords. This is my goal. How can i reach my goal?
You can see the sample code that i used for one field.
query: {
regexp: {
title: `.*${q}.*`,
},
},
I also tried below one but it gave syntax error.
query: {
regexp: {
title: `.*${q}.*`,
},
regexp: {
description: `.*${q}.*`,
},
},
To do so, you need to use a bool query.
GET /<you index>/_search
{
"query": {
"bool": {
"should": [
{
"regexp": {
"title": ".*${q}.*"
}
},
{
"regexp": {
"description": ".*${q}.*"
}
}
]
}
}
}
You can find the documentation => [doc]

Conditionally adding term and multi_match filters to elasticsearch with nodejs client

I am using ElasticSearch 7.9 with a nodejs client. I have the following query :
{
"query": {
"bool": {
"must":[
{ "terms" : { "id" : ["5f0d06fb5112231eb89eb819", "5f0d06fb5112231eb89eb817"] } },
{"query_string": {
"query": "(News) OR (Entertainent)",
"fields": [ "topics", "subTopics", "categories"]
}
},
{
"multi_match": {
"query": "publisher",
"fields": ["text", "name", "title", "subtitle", "description"]
}
}
]
}
}
}
I want to be able to conditionally add the terms filter for id if list of ids coming in to the nodejs/js function is not empty. Similarly for the multi_match query text as well. Add the multi_filter only if the incoming text is not empty
Should all queries be pre-constructed or is it possible to have conditional blocks and add only if the empty text or array of Ids are not empty.
My current method expects both ids and text input to the method to have valid values but these could be empty. Do I separate methods to handle the empty conditions
export const searchResults = async (text, ids) => {
const response = await client.search({
index: "new_index", //customer.id
type: "_doc",
body: {
query: {
bool: {
must: [
{terms: {"id": ids}},
{query_string: {
query: "(News) OR (Entertainent)",
fields: [ "topics", "subTopics", "categories"]
}
},
{
multi_match: {
query: text,
fields: ["text", "name", "title", "subtitle", "description"],
}
}
],
}
}
},
});
return response?.hits?.hits.map(({_source}) => _source) || [];
};
any help is really appreciated.
In a production environment and managing client based applications you should use search-templates, where you can use conditional blocks of queries. Besides, if you would want to change your query you would not neet to redeploy your app, just change it on ES.

Conditionally updating items in mongoose query

I have the following code and I'm trying to do two things. First I want to have my query have one condition where it finds the 'originator' value in a doc, but the second par of that is not to update if is also finds 'owner_id' is the same as originator.
The second part of what I'm trying to do is only set/update a field is it is being passed in. Can I use a ternary statement, something like below???
Contacts.update(
{
'originator': profile.owner_id,
'owner_id': !profile.owner_id
},
{
$set: {
(phoneNumber) ? ('shared.phones.$.phone_number': phoneNumber):null,
(emailAddress) ? ('shared.emails.$.email_address': emailAddress):null
}
},
{
'multi': true
},
function(err) {
err === null ? console.log('No errors phone updated for contacts.shared') : console.log('Error: ', err);
}
)
You mean something like this:
var updateBlock = {};
if (phoneNumber)
updateBlock['shared.phones.$.phone_number'] = phoneNumber;
if (emailAddress)
updateBlock['shared.email.$.email_address'] = emailAddress;
Contacts.updateMany(
{
"originator": profile.owner_id
"owner_id": { "$ne": profile.owner_id }
},
{ "$set": updateBlock },
function(err, numAffected) {
// work with callback
}
)
That addresses your two "main" misconceptions here in that the "inequality" in the query condition requires the $ne operator and not the ! JavaScript expression. MongoDB does not use JavaScript expressions here for the query conditions.
The second "main" misconception is the construction of the "update block" with conditional keys. This is by contrast a "JavaScript Object" which you construct separately in order to specify only the keys you wish to effect.
However there is STILL A PROBLEM in that you want to use the positional $ operator. Presuming you actually have "arrays" in the document like this:
{
"originator": "Bill",
"owner_id": "Ted",
"shared": {
"phones": [ "5555 5555", "4444 4444" ],
"email": [ "bill#stalyns.org", "bill#example.com" ]
}
}
Then your "two-fold" new issue is that:
You must specify a query condition that matches the array element "in the query block" in order to obtain the "matched position" at which to update.
You can only return ONE matched array index via use of the positional $ operator and NOT TWO as would be inherent to updating such a document.
For those reasons ( and others ) it is strongly discouraged to have "multiple arrays" within a single document. The far better approach is to use a "singular" array, and use properties to denote what "type" of entry the list item actually contains:
{
"originator": "Bill",
"owner_id": "Ted",
"shared": [
{ "type": "phone", "value": "5555 5555" },
{ "type": "phone", "value": "4444 4444" },
{ "type": "email", "value": "bill#stalyns.org" },
{ "type": "email", "value": "bill#example.com" }
]
}
In this way you can actually address the "matched" element in which to update:
// phoneNumberMatch = "4444 4444";
// phoneNumber = "7777 7777";
// emailAddress = null; // don't want this one
// emailAddressMatch = null; // or this one
// profile = { owner_id: "Bill" };
var query = {
"originator": profile.owner_id,
"owner_id": { "$ne": profile.owner_id },
"shared": {
"$elemMatch": {
"type": (phoneNumber) ? "phone" : "email",
"value": (phoneNumber) ? phoneNumberMatch : emailAddressMatch
}
}
};
var updateBlock = {
"$set": {
"shared.$.value": (phoneNumber) ? phoneNumber : emailAddress
}
};
Contacts.updateMany(query, updateBlock, function(err, numAffected) {
// work with callback
})
In such a case and with a "binary" choice then you "can" use ternary conditions in construction since you are not reliant on "naming keys" within the construction.
If you want "either, or indeed both" supplied values in combination then you need a bit more advanced statement:
// phoneNumberMatch = "5555 5555";
// phoneNumber = "7777 7777";
// emailAddress = "bill#nomail.com";
// emailAddressMatch = "bill#example.com";
// profile = { owner_id: "Bill" };
var query = {
"originator": profile.owner_id,
"owner_id": { "$ne": profile.owner_id },
"$or": []
};
var updateBlock = { "$set": {} };
var arrayFilters = [];
if (phoneNumber) {
// Add $or condition for document match
query.$or.push(
{
"shared.type": "phone",
"shared.value": phoneNumberMatch
}
);
// Add update statement with named identifier
updateBlock.$set['shared.$[phone].value'] = phoneNumber;
// Add filter condition for named identifier
arrayFilters.push({
"phone.type": "phone",
"phone.value": phoneNumberMatch
})
}
if (emailAddress) {
// Add $or condition for document match
query.$or.push(
{
"shared.type": "email",
"shared.value": emailAddressMatch
}
);
// Add update statement with named identifier
updateBlock.$set['shared.$[email].value'] = emailAddress;
// Add filter condition for named identifier
arrayFilters.push({
"email.type": "email",
"email.value": emailAddressMatch
})
}
Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
// work with callback
})
Noting of course here that the positional filtered $[<identifier>] syntax from MongoDB 3.6 and upwards is required in order to effect multiple array elements within a single update statement.
Much the same applies to the "original" structure I first described using "multiple" arrays in the documents instead of named properties on a "singular" array as the above examples deal with:
var query = {
"originator": "Bill",
"owner_id": { "$ne": "Bill" },
"$or": []
};
var updateBlock = { "$set": {} };
var arrayFilters = [];
if (phoneNumber) {
query.$or.push({
"shared.phones": phoneNumberMatch
});
updateBlock.$set['shared.phones.$[phone]'] = phoneNumber;
arrayFilters.push({
"phone": phoneNumberMatch
});
}
if (emailAddress) {
query.$or.push({
"shared.email": emailAddressMatch
});
updateBlock.$set['shared.email.$[email]'] = emailAddress;
arrayFilters.push({
"email": emailAddressMatch
});
}
Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
// work with callback
})
Of course if you don't even have arrays at all ( the question posted lacks any example document ) then positional matches are not even needed in any form, but you do however still "conditionally" construct JavaScript object "keys" via construction code blocks. You cannot "conditionally" specify a "key" in JSON-like notation.
Here is a simple example with switch condition in some variation like this:
const transfоrmFunc = function(val) {
if(val){
// do whatever you want with the value here
return val;
}
return null;
};
AnyModel.updateMany({ fieldId: { $in: ["MATCH1", "MATCH2"] } }, [
{
$set: {
field2: {
$switch: {
branches: [
{
case: { $eq: ["$fieldId", "MATCH1"] },
then: transfоrmFunc("$field3")
},
{
case: { $eq: ["$fieldId", "MATCH2"] },
then: transfоrmFunc("$field4.subfield")
}
]
}
}
}
}
]);
That way you work with both record data and outside data and update conditionally. You can modify query conditions as pleased. Plus it's really fast.

mongoosastic search with two fields with and condition

I have a chatting applications developed in Angular js, Node.js, MongoDB with elastic search integration. I had provided search functionality for chats, which user can enter any combination. Options are chat message, user and date.
So i want to search in elastic search db with help of nodejs, with mutiple field combination. For example, 1) search with username='mohan' and the date='anydate', 2) username='mohan' and chatmessage='Hi there'.
So the result should come which satisfy both conditions.
How can we achieve this using mongoosastic? I tried with below query. But it is giving result with OR condition, I want with AND condition.
{
"query": {
"bool": {
"should": [{
"match": {
"feedMsg": "Hi there"
}
}, {
"match": {
"userId": 'mohan'
}
}, {
"range": {
"feedTime": {
"from": '27/10/2016',
"to": '27/10/2016'
}
}
}]
}
}
}
I found the solutions. Bool Query with must is working for me.
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
"filter": []
}
}
must- All of these clauses must match. The equivalent of AND.
must_not - All of these clauses must not match. The equivalent of
NOT.
should - At least one of these clauses must match. The equivalent of OR.
filter - Clauses that must match, but are run in non-scoring,
filtering mode.
You should use this format, it really works...
var query = {
query_string: {
filtered: {
query: {
multi_match: {
query: req.query.q, // or anything you will put here
}
},
filter: {
term: {
'field': value
}
}
}
}
}

Resources