Nested query on ElasticSearch - javascript

I have an elastic search index which is storing documents in the following way:
{
categorisedTags:
{ urlTags: { L: [] },
commodityTags: { L: [Array] },
tags: { L: [] } },
newOptions: [],
created_at: 'Mon, 07 Oct 2019 12:55:34 GMT',
name: 'Template ',
}
I need to query the index by 'commodityTags', so given a string, it should return all documents where the string is included in the commodityTags array.
I have tried with:
service.queryTags = async (index, values) => {
const { hits } = await esClient.search({
index,
type: '_doc',
body: {
query: {
term: {
'categorisedTags.commodityTags': 'oil'
}
},
},
});
return hits.hits.map(({ _source }) => _source);
};
But no luck, always returns 0 hits. How can I do this kind of nested queries on ES ?

Nested query can be created like below
Query
"query": {
"nested": {
"path": "categorisedTags",
"query": {
"bool": {
"must": [
{
"term": {
"categorisedTags.commodityTags": {
"value": "oil"
}
}
}
]
}
},
"inner_hits": {}
}
}

Related

Mongoose add to array of nested array if exists create otherwise

i'm trying to accomplish the following in mongoose:
Say i have the following collection
{
"_id": {
"$oid": "111"
},
"email": "xxx#mail.com",
"givenName": "xxx",
"familyName": "xxx",
"favoriteProducts": [{
"soldTo": "33040404",
"skus": ["W0541", "W2402"]
}, {
"soldTo": "1223",
"skus": ["12334"]
}]
}
i want to be able to add a sku to the favorite products array based on soldTo and _id.
When doing this there are two possible scenarios.
a. There is already an object in favoriteProducts with the given soldTo in which case the sku is simply added to the array.(for example add sku '12300' to soldTo '1223' for id '111')
b. There is no object with the given soldTo yet in which case this object need to be created with the given sku and soldTo. (for example add sku '123' to soldTo '321' for id '111')
so far i've done this but i feel like there is a way to do it in one query instead.
private async test() {
const soldTo = '1223';
const sku = '12300';
const id = '111';
const hasFavoriteForSoldTo = await userModel.exists({
_id: id,
'favoriteProducts.soldTo': soldTo,
});
if (!hasFavoriteForSoldTo) {
await userModel
.updateOne(
{
_id: id,
},
{ $addToSet: { favoriteProducts: { skus: [sku], soldTo } } },
)
.exec();
} else {
await userModel
.updateOne(
{
_id: id,
'favoriteProducts.soldTo': soldTo,
},
{ $addToSet: { 'favoriteProducts.$.skus': sku } }
)
.exec();
}
}
Use update-documents-with-aggregation-pipeline
Check out mongo play ground below. Not sure you want Output 1 or Output 2.
Output 1
db.collection.update({
_id: { "$oid": "111222333444555666777888" }
},
[
{
$set: {
favoriteProducts: {
$cond: {
if: { $in: [ "1223", "$favoriteProducts.soldTo" ] },
then: {
$map: {
input: "$favoriteProducts",
as: "f",
in: {
$cond: {
if: { $eq: [ "1223", "$$f.soldTo" ] },
then: { $mergeObjects: [ "$$f", { skus: [ "12300" ] } ] },
else: "$$f"
}
}
}
},
else: {
$concatArrays: [ "$favoriteProducts", [ { skus: [ "12300" ], soldTo: "1223" } ] ]
}
}
}
}
}
],
{
multi: true
})
mongoplayground
Output 2
db.collection.update({
_id: { "$oid": "111222333444555666777888" }
},
[
{
$set: {
favoriteProducts: {
$cond: {
if: { $in: [ "1223", "$favoriteProducts.soldTo" ] },
then: {
$map: {
input: "$favoriteProducts",
as: "f",
in: {
$cond: {
if: { $eq: [ "1223", "$$f.soldTo" ] },
then: {
$mergeObjects: [
"$$f",
{ skus: { $concatArrays: [ [ "12300" ], "$$f.skus" ] } }
]
},
else: "$$f"
}
}
}
},
else: {
$concatArrays: [ "$favoriteProducts", [ { skus: [ "12300" ], soldTo: "1223" } ] ]
}
}
}
}
}
],
{
multi: true
})
mongoplayground

How can I loop a sequelize response

If I make a request to a Postgres database with Sequelize, I get the next result:
[
Tags {
dataValues: { idtag: 18 },
_previousDataValues: { idtag: 18 },
_changed: {},
_modelOptions: {
...
},
_options: {
...
},
isNewRecord: false
},
Tags {
dataValues: { idtag: 19 },
_previousDataValues: { idtag: 19 },
_changed: {},
_modelOptions: {
...
},
_options: {
...
},
isNewRecord: false
},
Tags {
dataValues: { idtag: 20 },
_previousDataValues: { idtag: 20 },
_changed: {},
_modelOptions: {
...
},
_options: {
...
},
isNewRecord: false
}
]
I can't find the way to loop this to can get the 'idtag' values.
I tried to parse this to JSON with JSON.stringify() but it gives me a string. Also, I tried to loop this like it was an array but it didn't works too.
Let's assume that your output is stored to an array. Now using following piece of code you can collect idTag values.
const idTagVals = [];
array.forEach(element => {
idTagVals.push(element.dataValues.idtag);
});
console.log(idTagVals); //[ 18, 19, 20 ]
According to sequelize docs
TagsModel.findAll({where: {} ,plain:true});
Will return Array of plain objects.
And finilly I have the answer. First I converted the result with JSON.stringify() and then with JSON.parse().
let tags = await models.Tags.findAll({ attributes: ['idtag'], where: { iduser: iduser } });
let string = JSON.stringify(tags);
tags = JSON.parse(string);
And finally, to loop:
tags.forEach(async (tag) => {
...
});

Convert multiple json object into array of object

My data is currently stored in this format:
{
"Site1":{
"week":[
{
"access":1
},
{
"access":8
}
]
},
"Site2":{
"week":[
{
"access":16
}
]
},
"Site3":{
"week":[
{
"access":2
},
{
"access":6
},
{
"access":2
}
]
}
}
And I need to convert it into this format:
[
{
"id":"Site1",
"access":[1,8]
},
{
"id":"Site2",
"access":[16]
},
{
"id":"Site3",
"access":[2,6,2]
}
]
As you can see, I also need to take the keys (site name) and make them the "id" values.
Any ideas on how I can do this in JavaScript (I'm using angular v9)? I'm not very good at restructuring that type of data.
You can first take entries and then map it:
var data={ "Site1":{ "week":[ { "access":1 }, { "access":8 } ] }, "Site2":{ "week":[ { "access":16 } ] }, "Site3":{ "week":[ { "access":2 }, { "access":6 }, { "access":2 } ] }};
var result = Object.entries(data).map(([k,v])=>({id:k, access: v.week.map(p=>p.access)}));
console.log(result);
Object.keys()
map()
const data = {
Site1: {
week: [
{
access: 1,
},
{
access: 8,
},
],
},
Site2: {
week: [
{
access: 16,
},
],
},
Site3: {
week: [
{
access: 2,
},
{
access: 6,
},
{
access: 2,
},
],
},
};
const result = Object.keys(data).map(key => ({
id: key,
access: data[key].week.map(w => w.access),
}));
console.log(result);
you can simply use this code for your desired result.
Object.keys(data).map(key => (
{
id: key,
access: data[key].week.map(obj => obj.access),
}
))
Let me know if you face any issue.

Using find and aggregate on mongoDB/Mongoose to fetch data

I'm working on a node.js project to display some data using charts and tables on the front end.
I have the two following queries on my route:
atendimentos.find({})
.then(atendimentos => {
final = atendimentos.filter(atendimentos => atendimentos.status === 'F')
testeea = atendimentos.filter(atendimentos => atendimentos.status === 'EA')
res.render('home', {user: req.user, fin: final.length, ea: testeea.length});
//Funciona
console.log(final.length)
})
.catch(err => console.error(err));
atendimentos.aggregate([
{ $project:
{ _id: "$month",
year: {$year: "$date" },
month: { $month: "$date"},
amount: 1
}
},
{ $group:
{ _id: { year: "$year", month: ("$month")},
sum: { $sum: 1}
}
}]).exec(function(error, items){
if(error){return next(error);}
console.log(items);
});
EDIT 1:
So, the input data... I guess that I don't have any because I'm actually fetching everything from the database through my queries. The data that I expect, are the documents/object with the status F or EA which I'm rendering on my chart.
The database has around 8.5k documents, the F one returns 8041 documents and the EA returns 351, it is a simple array with the number that is returned using .length on my route. Those numbers are rendered on the chart.
Now, related to the aggregation part, I'm trying to make a table using the collection. I intend to show the number of support calls (atendimentos) per month. It's actually logging the correct data like this:
[ { _id: { year: 2018, month: 6 }, sum: 4005 },
{ _id: { year: 2018, month: 7 }, sum: 43 },
{ _id: { year: 2018, month: 5 }, sum: 3996 },
{ _id: { year: 2018, month: 4 }, sum: 434 } ]
And I want to use this data to render the table on my view.
END OF EDIT 1
EDIT 2
router.get('/home', isAuthenticated, async (req, res, next) => {
let final;
let testeea;
atendimentos.find({})
.then(atendimentos => {
final = atendimentos.filter(atendimentos => atendimentos.status === 'F')
testeea = atendimentos.filter(atendimentos => atendimentos.status === 'EA')
res.render('home', {user: req.user, fin: final.length, ea: testeea.length});
//Funciona
console.log(final.length)
})
.catch(err => console.error(err));
So, here's the route, the other part is just the aggregation query that I've tried to do and closing brackets. As you can see, I get the data and use Array.filter to filter the results fetched, using status = F or = EA.
It returns me the length of the array, so it counts the number of status with each letter. This number is rendered in the chart, because I'm sending it to the front end as fin: final.length and ea: testeea.length. No formatted data or something like that in here. It's okay this way.
Related to the aggregation part where it returns the calls per month, I want to use just the number of calls, month and year. In this part I expected the data like: [ { _id: { year: 2018, month: 6 }, sum: 4005 }
I wish I could fetch the data the same way as I've fetched the fin and ea, using .length to count and put it into the view.
END OF EDIT 2
Both are returning exactly what I need, the problem is that I can't just put the aggregation query before the find query and add items: items to the render method. I would like to know how do I do these queries to display the same that that I'm fetching on these two queries. Thanks in advance!
MongoDB Server 3.2 and below
You need to run two aggregate queries and merge the objects in the results. This can be done in a multiple ways but can show you the Promise way and the async/await approach.
1. Using Promises
router.get('/home', isAuthenticated, (req, res, next) => {
const counts = atendimentos.aggregate([
{ '$group': {
'_id': null,
'fin': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'F' ] }, 1, 0 ]
}
},
'ea': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'EA' ] }, 1, 0 ]
}
}
} }
]).exec();
const monthly = atendimentos.aggregate([
{ '$group': {
'_id': {
'year': { '$year': '$date' },
'month': { '$month': '$date' }
},
'sum': { '$sum': 1 }
} },
{ '$group': {
'_id': null,
'back': { '$push': '$$ROOT' }
} },
]).exec();
Promise.all([ counts, monthly ]).then(([ counts, monthly ]) => {
const statusData = counts[0];
const monthlyData = monthly[0];
const data = {...statusData, ...monthlyData, user: req.user};
console.log(JSON.stringify(data, null, 4));
res.render('home', data);
}).catch(err => next(err));
});
2. Using async/await
router.get('/home', isAuthenticated, async (req, res, next) => {
try {
const counts = await atendimentos.aggregate([
{ '$group': {
'_id': null,
'fin': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'F' ] }, 1, 0 ]
}
},
'ea': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'EA' ] }, 1, 0 ]
}
}
} }
]).exec();
const monthly = await atendimentos.aggregate([
{ '$group': {
'_id': {
'year': { '$year': '$date' },
'month': { '$month': '$date' }
},
'sum': { '$sum': 1 }
} },
{ '$group': {
'_id': null,
'back': { '$push': '$$ROOT' }
} },
]).exec();
const statusData = counts[0];
const monthlyData = monthly[0];
const data = {...statusData, ...monthlyData, user: req.user};
console.log(JSON.stringify(data, null, 4));
res.render('home', data);
} catch (err) {
next(err);
}
});
MongoDB Server 3.4.4 and above
The aggregation pipeline can also handle filtering, you just need to use the $facet pipeline step which is capable of processing multiple aggregation pipelines within a single stage on the same set of input documents. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.
Consider running the following pipeline:
atendimentos.aggregate([
{ '$facet': {
'counts': [
{ '$group': {
'_id': null,
'fin': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'F' ] }, 1, 0 ]
}
},
'ea': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'EA' ] }, 1, 0 ]
}
}
} }
],
'monthly': [
{ '$group': {
'_id': {
'year': { '$year': '$date' },
'month': { '$month': '$date' }
},
'sum': { '$sum': 1 }
} },
{ '$group': {
'_id': null,
'items': { '$push': '$$ROOT' }
} },
]
} },
{ '$replaceRoot': {
'newRoot': {
'$mergeObjects': {
'$concatArrays': ['$counts', '$monthly']
}
}
} }
]).exec((err, results) => {
const data = results[0];
console.log(data);
res.render('home', { user: req.user, ...data });
})

How to write OR query with elastic search in node js

I have my document like this
Sample document,
[
{
"_index": "xpertdox",
"_type": "disease",
"_id": "Ectopic Heartbeat",
"_score": 24.650267,
"_source": {
"category": "Condition",
"name": "Ectopic Heartbeat",
"dui": "D005117",
"url_name": "Extrasystole"
}
},
This is my sample document.
if (req.param('disease')) {
searchString = req.param('disease');
filterQuery = { Category:
['disease','Condition','speciality','pharm','medicine'] };
} else if (req.param('docorhosp')) {
searchString = req.param('docorhosp');
filterQuery = { Category: ['hospital', 'doctor'] };
} else if (req.param('speciality')) {
searchString = req.param('speciality');
filterQuery = { Category: ['speciality'] };
}
client.search({
index: 'xpertdox',
type: 'disease',
size: 20,
body: {
query: {
must: {
match: {
name: {
query: searchString,
fuzziness: 2,
operator: "or"
}
}
},
filter : {
terms : filterQuery
}
}
}
}).then(function (resp) {
var data = resp.hits.hits;
if (isFromSsr) {
data = helper.prepareSearchDataForSsr(data);
}
res.json(data);
});
I am matching my parameter with name,but here I want to filter records only whose category is either 'doctor' or 'hospital'.How can devolope my query so to get my requirement..
Try this:
client.search({
index: 'dox',
type: 'disease',
size: 20,
body: {
query: {
must: {
match: {
name: {
query: req.param('disease'),
fuzziness: 2,
operator: "or"
}
}
},
filter: {
terms: {
category: ['hospital', 'doctor']
}
}
}
}
})

Categories