I have a mongodb aggregation framework query as shown below.I am unable to parse the output of the below query
myModel.aggregate(
[
{
"$match": { "$and": [{ "serviceActiveFlag": "Y" }, { "hospitalName": hospitalName }] }
},
//decompile array
{ $unwind: "$Treatment" },
{
$group: {
_id: "$Treatment.departmentName", "procedureList": {
$push: { "procedureName": "$Treatment.name", "cost": "$Treatment.costLowerBound" }
}
}
},
{
$project: {
"_id": 0,
"department": '$_id',
"procedureList": 1
}
}
], function (err, result) {
})
Output of the above query is shown below
{
"data": [
{
"procedureList": [
{
"procedureName": "Root Canal",
"cost": 10200
}
],
"department": "Dental"
},
{
"procedureList": [
{
"procedureName": "Bone Grafting",
"cost": 20000
}
],
"department": "Ortho"
}
]
}
How do i retrieve the value corresponding to key data?
I tried result.data[0],but I got undefined error
Expected output is given below
Expected output
[
{
"procedureList": [
{
"procedureName": "Root Canal",
"cost": 10200
}
],
"department": "Dental"
},
{
"procedureList": [
{
"procedureName": "Bone Grafting",
"cost": 20000
}
],
"department": "Ortho"
}
]
I think you forgot to parse the json-data, that's why it isn't able to index, Please use the code below to parse the json and then try accessing the array. if you encounter any problems, please mention them.
result = JSON.parse(result)
result.data[0]
Result is an object so,
let data = result.data
Related
I have 2 collections:
Vehicles:
[
{
"_id": "a1",
"type:": "car",
"make": "Honda",
"specifications": ["1", "2"]
},
{
"_id": "a2",
"type:": "car",
"make": "Toyota",
"specifications": ["3", "4"]
},
{
"_id": "a3",
"type:": "car",
"make": "Honda",
"specifications": []
},
{
"_id": "a4",
"type:": "car",
"make": "Toyota"
}
]
Specifications:
[
{
"_id": "1",
"color": "Black"
},
{
"_id": "2",
"sunroof": "yes"
},
{
"_id": "3",
"engine": "1800 CC"
},
{
"_id": "4",
"bodyType": "Sedan"
}
]
I want to fetch those records which has at least one specification.
And also the details from specifications collections should appear in Vehicles collections somewhere.
Expected response:
[
{
"_id": "a1",
"make": "Honda",
"type:": "car",
"carSpecifications": [
{
"color": "Black"
},
{
"sunroof": "yes"
}
],
},
{
"_id": "a2",
"make": "Toyota",
"type:": "car",
"specifications": [
{
"engine": "1800 CC"
},
{
"bodyType": "Sedan"
}
]
}
]
Now what I tried so far is:
db.vehicles.find({type: "car", "specifications": {$exists: true}}, {fields: {"specifications.$": 1}}).fetch()
this query is returning all the records from Vehicles.
After getting all the records I put a loop on the records I get and check manually if specifications.length > 0 than I query from Specifications collection accordingly.
Can I achieve all this with a single query?
You should look for an aggregation query.
$match - Filter documents with "type:" "car" and specifications is not an empty array (with $ifNull, default as [] when specifications field is null or not existed).
$lookup - Vehicles collection join specifications collection (Refer to Use $lookup with an Array). Work with pipeline to return the array without the _id field (Refer to Correlated Subqueries Using Concise Syntax).
MongoDB v5 query
db.vehicles.aggregate({
$match: {
$and: [
{
"type:": "car"
},
{
$expr: {
$ne: [
{
$ifNull: [
"$specifications",
[]
]
},
[]
]
}
}
]
}
},
{
$lookup: {
from: "specifications",
localField: "specifications",
foreignField: "_id",
pipeline: [
{
$project: {
_id: 0
}
}
],
as: "specifications"
}
})
Sample Mongo Playground (v5)
MongoDB v4 query
db.vehicles.aggregate({
$match: {
$and: [
{
"type:": "car"
},
{
$expr: {
$ne: [
{
$ifNull: [
"$specifications",
[]
]
},
[]
]
}
}
]
}
},
{
$lookup: {
from: "specifications",
let: {
specifications: "$specifications"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$_id",
"$$specifications"
]
}
}
},
{
$project: {
_id: 0
}
}
],
as: "specifications"
}
})
Sample Mongo Playground (v4)
currently, I am struggling with how the MongoDB document system works. I want to fetch array elements with an auto-generated id but how to fetch that specific data that I don't know.
my current schema is
const ItemPricesSchema = new mongoose.Schema({
_id : {
type: String
},
ItemsPrices: {
type: [{
barcode : {
type: String
},
itemName : {
type: String
},
price : {
type: String
}
}]
}
});
current data is stored in this way
{
"_id": "sha#c.c",
"ItemsPrices": [
{
"barcode": "345345",
"itemName": "maggie",
"price": "45",
"_id": "620a971e11120abbde5f4c3a"
},
{
"barcode": "356345",
"itemName": "monster",
"price": "70",
"_id": "620a971e11120abbde5f4c3b"
}
],
"__v": 0
}
what I want to achieve is that I want to find array elements through ids
if I want a specific array element with id "620a971e11120abbde5f4c3b" what should I do??
I have tried $unwind , $in, $match...
the result should be like
{
"_id": "sha#c.c",
"ItemsPrices": [
{
"barcode": "356345",
"itemName": "monster",
"price": "70",
"_id": "620a971e11120abbde5f4c3b"
}
],
"__v": 0
}
what I tried is like this from the answer
router.get('/filter/:id', async (req, res) => {
try {
const item = await ItemPricesSchema.aggregate([
{$project: {
"ItemsPrices": {
$filter: {
input: "$ItemsPrices",
as: "item",
cond: {
$eq: [
"$$item._id",
"620a8dd1c88ae3eb88a8107a"
]
}
}
}
}
}
])
res.json(item);
console.log(item);
} catch (error) {
res.status(500).json({message: error.message});
}
})
and returns something like this (Empty arrays)
[
{
"_id": "xvz#zyx.z",
"ItemsPrices": []
},
{
"_id": "zxc#xc.czx",
"ItemsPrices: []
},
{
"_id": "asd#asd.asd",
"ItemsPrices": []
},
{
"_id": "qwe#qwe.qwe",
"ItemsPrices": []
}
]
but If I search for price $$item.price
cond: {
$eq: [
"$$item.price",
"30"
]
}
it returns the perfect output
[
{
"_id": "xvz#zyx.z",
"ItemsPrices": []
},
{
"_id": "zxc#xc.czx",
"ItemsPrices: []
},
{
"_id": "asd#asd.asd",
"ItemsPrices": []
},
{
"_id": "qwe#qwe.qwe",
"ItemsPrices": [
{
"barcode":"234456345",
"price":"30",
"itemName":"monster",
"_id":"620a8dd1c88ae3eb88a8107a"
}
]
}
]
You can do an aggregation with $project and apply $filter on the array part. In mongoose you can apply the aggregation query in a more or less similar way https://mongoosejs.com/docs/api/aggregate.html
db.collection.aggregate([
{
$project: {
"ItemsPrices": {
$filter: {
input: "$ItemsPrices",
as: "item",
cond: {
$eq: [
"$$item._id",
mongoose.Types.ObjectId("620a971e11120abbde5f4c3b")
]
}
}
},
"__v": 1 //when projecting 1 means in the final result this field appears
}
}
])
more examples
demo
Option 1:
Use $filter in an aggregation query as explained by cmgchess
Option 2:
If you only want one object from array you can use $elemMatch like this:
db.collection.find({
"ItemsPrices._id": "620a971e11120abbde5f4c3b"
},
{
"ItemsPrices": {
"$elemMatch": {
"_id": "620a971e11120abbde5f4c3b"
}
}
})
Example here
But take care, using $elemMatch only the first element is returned. Check this other example where there are two objects with the desired _id but only returns one.
As said before, if you only one (or only exists one) maybe you can use find and $elemMatch to avoid a filter by the entire array. But if can be multiple values use $filter.
Structure:
[
{
"_id": "12",
"title": "Vanella Icream",
"contain":"sugar",
"details": [
{
"flavour": "Vanella"
},
{
"weight": "6KG"
}
]
},
{
"_id": "3",
"title": "Pretzels",
"contain":"salt",
"details": [
{
"flavour": "Wheat"
},
{
"weight": "2KG"
}
]
}
]
Expected Output
details:
{
"flavour": [
"Vanella",
"Wheat",
],
"weight": [
"6KG",
"2KG",
]
}
More Important: I've to do this without details.flavour or details.weight.
I’ve tried with $addToSet, $filter $group but I’m not getting results like that. anyone please suggest.
Mongo Playground: https://mongoplayground.net/p/SXw3jaRDzLy
$project to show required field
$unwind deconstruct the details array
$objectToArray convert details object to array key-value format
$unwind deconstruct the details array
$group by details key and construct the array of values
$group by null and construct the array of details in key-value format
$arrayToObject convert above result from key-value format array of object to an object
db.collection.aggregate([
{
$project: {
_id: 0,
details: 1
}
},
{ $unwind: "$details" },
{
$project: {
details: { $objectToArray: "$details" }
}
},
{ $unwind: "$details" },
{
$group: {
_id: "$details.k",
v: { $addToSet: "$details.v" }
}
},
{
$group: {
_id: null,
details: {
$push: { k: "$_id", v: "$v" }
}
}
},
{
$project: {
_id: 0,
details: { $arrayToObject: "$details" }
}
}
])
Playground
I need to output a list of unique values in a collection. I've used the distinct method when the values are either a string or a number. But in this situation, the values are an array of objects.
The simplified model looks like this:
const mongoose = require('mongoose');
const ItemModel = mongoose.Schema({
category: [{
lang: String,
text: String
}],
discount: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Items', ItemModel);
A typical find() query without parameters produces this result:
[
{
"_id": "5fd8712b374a9a1410f786bf",
"category": [
{
"_id": "5fd8712b374a9a1410f786c2",
"lang": "RU",
"text": "Домашняя одежда"
},
{
"_id": "5fd8712b374a9a1410f786c3",
"lang": "EN",
"text": "Homewear"
}
],
"discount": "45%",
"date": "2020-12-12T11:12:37.811Z",
"__v": 0
},
{
"_id": "5fd4a5b95e1a251ac96b2e08",
"category": [
{
"_id": "5fd4a5b95e1a251ac96b2e0b",
"lang": "RU",
"text": "Домашняя одежда"
},
{
"_id": "5fd4a5b95e1a251ac96b2e0c",
"lang": "EN",
"text": "Homewear"
}
],
"discount": "35%",
"date": "2020-12-12T11:12:37.811Z",
"__v": 0
},
{
"_id": "5fd49e415e1a251ac96b2dfc",
"category": [
{
"_id": "5fd49e415e1a251ac96b2dff",
"lang": "RU",
"text": "Активный отдых"
},
{
"_id": "5fd49e415e1a251ac96b2e00",
"lang": "EN",
"text": "Active"
}
],
"discount": "50%",
"date": "2020-12-12T10:06:53.120Z",
"__v": 0
}
]
I need to output a list of unique "category.text" values where "lang" equals "EN". At the output, we should get an array like this:
[ "Active", "Homewear" ]
How to do it the right way, with good performance?
Regards.
You can use aggregation pipeline,
$unwind to deconstruct category array
$match category.lang is EN
$group by null, and get unique text using $addToSet
StockModel.aggregate([
{ $unwind: "$category" },
{ $match: { "category.lang": "EN" } },
{
$group: {
_id: null,
test: { $addToSet: "$category.text" }
}
}
])
Playground
use aggregation :
take a look at the example below :
StockModel.aggregate([
{
$group: {
_id: 0,
title: { $addToSet: '$title' },
stock_id: { $addToSet: '$stock_id' },
},
},
]);
with aggregation $group you can distinct your collections via filter.
I'm converting MongoDB Query to Elasticsearch in NodeJS platform. While developing I'm facing some difficulties with grouping and filtering data (getting nested objects like hits.hits._source) within Elasticsearch Query like we doing in MongoDB Query.
Example:-
UserModel.aggregate([
{
$match: {
uId: req.body.uId, timestamp: { $gte: req.body.date, $lte: new Date() }
},
},
{
$group: {
_id: "$eId",
location: {
$push: {
time: "$timestamp", lat: "$lat"
}
},
timestamp: {
$push: "$timestamp"
},
testId: { $first: "$testId" },
}
},
{
$project: {
eId: 1, location: 1, testId: 1, max: { $max: "$timestamp" }
}
},
{ $unwind: { path: "$location", preserveNullAndEmptyArrays: true } },
{
$redact: {
$cond: {
if: { $eq: ["$location.time", "$max"] },
then: "$$DESCEND",
else: "$$PRUNE"
}
}
},
{
$project: {
eId: 1, latitude: "$location.lat", testId: 1
}
},
]).exec(function (err, result) {
console.log(result)
});
What will be the equivalent query in Elasticsearch?
I'm looking for solution with grouping, unwinding and projecting (MongoDB concepts to Elasticsearch) required data with minimal nested response.
Thanks in Advance.
EDIT:-
Adding Elasticsearch Document:-
{
"timestamp": "2019-10-08T:02:50:15.54Z",
"status" : 1,
"eId": "5d5d7ce0c89852e7bad4a407",
"location": [
2.000,
34.5664111801
],
"zId": "5d5d7ce0c89852e7bad4a4ef"
},
{
"timestamp": "2019-10-09T:02:50:15.54Z",
"status" : 1,
"eId": "5d5d7ce0c89852e7bad4a408",
"location": [
2.100,
35.5664111801
],
"zId": "5d5d7ce0c89852e7bad4a4ef"
},
{
"timestamp": "2019-10-09T:03:50:15.54Z",
"status" : 1,
"eId": "5d5d7ce0c89852e7bad4a407",
"location": [
4.100,
35.5664111801
],
"zId": "5d5d7ce0c89852e7bad4a4ef"
},
{
"timestamp": "2019-10-09T:03:40:15.54Z",
"status" : 1,
"eId": "5d5d7ce0c89852e7bad4a407",
"location": [
2.100,
35.5664111801
],
"zId": "5d5d7ce0c89852e7bad4a4e1"
},
{
"timestamp": "2019-10-10T:03:40:15.54Z",
"status" : 1,
"eId": "5d5d7ce0c89852e7bad4a407",
"location": [
3.100,
35.5664111801
],
"zId": "5d5d7ce0c89852e7bad4a4e1"
}
Match with status =1, and Group By eId
With that results, group by timestamp and get max timestamp value
Expected Result:-
[
{
"_id": "5d5d7ce0c89852e7bad4a407",
"max": "2019-10-10T:03:40:15.54Z", // max timestamp
"zId": [
"5d5d7ce0c89852e7bad4a4e1",
"5d5d7ce0c89852e7bad4a4ef"
]
},
{
"_id": "5d5d7ce0c89852e7bad4a408",
"max": "2019-10-09T:02:50:15.54Z",
"zId": [
"5d5d7ce0c89852e7bad4a4ef"
]
}, // ...etc
]
Thanks for the documents. Sadly, I do not know any way to retrieve only the documents having the max timestamp field value.
The following query will allow you to filter by status and group by eId then get the max timestamp value, but it will not return the documents having the max timestamp value.
{
"size": 0,
"query": {
"term": {
"status": 1
}
},
"aggregations": {
"eId_group": {
"terms": {
"field": "eId"
},
"aggregations": {
"max_timestamp": {
"max": {
"field": "timestamp"
}
}
}
}
}
}
This second query use a top_hits aggregation to retrieve the documents grouped by eId. The returned documents are sorted by decreasing timestamp value so the documents having the max timestamp will be firsts, but you may also get documents with different timestamps.
{
"size": 0,
"query": {
"term": {
"status": 1
}
},
"aggregations": {
"eId_group": {
"terms": {
"field": "eId"
},
"aggregations": {
"max_timestamp": {
"max": {
"field": "timestamp"
}
},
"top_documents": {
"top_hits": {
"size": 20,
"sort": { "timestamp": "desc"}
}
}
}
}
}
}
I used the following mapping for the index
PUT /test_index
{
"mappings": {
"properties": {
"timestamp": {
"type": "date"
},
"eId": {
"type": "keyword"
},
"zId": {
"type": "keyword"
},
"status": {
"type": "keyword"
}
}
}
}