My MongoDB stores documents having the following structure:
{
"application_detail":{},
"curl_detail":{
"Curl1":{
"key1":"value1",
"key2":"value2"
},
"Curl2":{
"key1":"value1",
"key2":"value2"
},
"Curl3":{
"key1":"value1",
"key2":"value2"
},
"Curl4":{
"key1":"value1",
"key2":"value2"
},
/*total number of curls are unknown*/
}
}
How can I fetch key1 for all the curls present under curl_detail from mongoDB using express and mongoose?
Expected Output:
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
NOTE: Total number of curls are unknown.
I would like to propose you changing data structrure from
"curl_detail": {
"Curl1": {
"key1": "value1",
"key2": "value2"
},
"Curl2": {
"key1": "value1",
"key2": "value2"
}
}
to
"curl_detail": [{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value1",
"key2": "value2"
}
]
Storing data structure in array will allow you to find some data in an easier way. Also there's no need to have object with properties like Curl1, Curl2 etc.
For the new data structure there are already some answers with the explanation:
1) Mongo find value with unknown parent key
2) Query MongoDB by value when parent key is unknown
3) MongoDB: Find document given field values in an object with an unknown key
UPDATE
If there's no possibility to change the data structure please see this solution (trick with use of $arrayToObject / $objectToArray to determine the name of nested key e.g. "Curl1"):
db.collection.aggregate([{
$addFields: {
curl_detail: {
$arrayToObject: {
$map: {
input: {
$objectToArray: "$curl_detail"
},
as: "details",
in: {
k: "$$details.k",
v: {
key1: "$$details.v.key1"
}
}
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: 1
}
}
])
This outputs the following:
[
{
"curl_detail": {
"Curl1": {
"key1": "value1"
},
"Curl2": {
"key1": "value1"
},
"Curl3": {
"key1": "value1"
},
"Curl4": {
"key1": "value1"
}
}
}
]
You can check it through Mongo Playground
You can use projection for the query you are looking for:
collection.find({}, {"curl_detail":1}, (findError, results) => {
if(findError) {
//handle the error
return;
}
// here is the expected result
console.log();
});
If this result also works for you:
[
{
"curl_detail": {
"Curl1": "value1",
"Curl2": "value1",
"Curl3": "value1",
"Curl4": "value1"
}
}
]
You can use the following aggregation using the $objectToArray and $arrayToObject
db.collection.aggregate([
{
$addFields: {
curl_detail: {
$map: {
"input": {
"$objectToArray": "$curl_detail"
},
"as": "el",
"in": {
"k": "$$el.k",
"v": "$$el.v.key1",
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: "$curl_detail"
}
}
}
])
Playground
You can use this aggregation with mongoose like this, let's say your model is MyModel:
MyModel.aggregate([
{
$addFields: {
curl_detail: {
$map: {
input: {
$objectToArray: '$curl_detail'
},
as: 'el',
in: {
k: '$$el.k',
v: '$$el.v.key1'
}
}
}
}
},
{
$project: {
_id: 0,
curl_detail: {
$arrayToObject: '$curl_detail'
}
}
}
]).then(res => {
console.log(res);
});
Related
I have made several efforts to select a single specific document that contains the minimum value from the database.
let Lowestdate = await BTCMongo.aggregate(
[
// { "$sort": { "name": 1,
{
$match : { "createdAt" : { $gte: new Date(last),$lte: new Date(NEW) } } },
{
$group:
{
_id:null,
minFee_doc:{$min: "$$ROOT"},
minFee: { $min:{$toDouble:"$one"}},
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: {$toDouble:"$one"}},
}
},
]
).then(result => {}):
with minFee_doc:{$min: "$$ROOT"}, I have been trying to return the document containing the minimum $min but it keeps returning document containing $first
How do i select the document with minimum value?
Note : i will like to return the whole document including the "CreatedAt" "UpdatedAt", and _id. of the document containing the minimum value
Expected Result should look like:
{
"minFee_doc": {
"_id": "61e84c9f622642463640e05c",
"createdAt": "2022-01-19T17:38:39.034Z",
"updatedAt": "2022-04-24T14:48:38.100Z",
"__v": 0,
"one": 2
},
"minFee": 2,
"firstFee": 3,
"lastFee": 5,
"maxFee": 6
}
Edit: also to provide a single document not multiple
$push all docs in $group then $set the array with $filter
db.collection.aggregate([
{
$match: {}
},
{
$group: {
_id: null,
minFee_doc: { $push: "$$ROOT" },
minFee: { $min: { $toDouble: "$one" } },
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: { $toDouble: "$one" } }
}
},
{
$set: {
minFee_doc: {
$filter: {
input: "$minFee_doc",
as: "m",
cond: { "$eq": [ "$$m.one", "$minFee" ] }
}
}
}
}
])
mongoplayground
hi everyone i am trying to project the cities which are belongs to particular state by taking the country_code and state_code
[
{
'$match': {
'iso2': 'IN'
}
}, {
'$project': {
'states': {
'$slice': [
'$states.cities.name', 1, 1
]
}
}
}
]
when i tried this i am getting the result but is there any better way to do it
tq
I would do it as follow:
db.collection.aggregate([
{
"$match": {
"iso2": "IN",
"states.state_code": "AG"
}
},
{
$addFields: {
states: {
"$filter": {
"input": "$states",
"as": "state",
"cond": {
"$eq": [
"$$state.state_code",
"AG"
]
}
}
}
}
},
{
$project: {
cities: "$states.cities.name"
}
},
{
$unwind: "$cities"
}
])
Explained:
Match only documents having iso2=IN and states.state_code=AG
( For this stage is good if you have index on at least {iso2:1} or compound index on {iso2:1,states.state_code:1} )
Filter only the states with state_code=AG
$project the names only
$unwind to flatten first array.
playground
I am trying to match by comparing the values inside the values of another array, and return it if any one of the values in the 1st array match any one value of the 2nd array. I have a user profile like this
{
"user": "bob"
"hobbies": [jogging]
},
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
My mongoose search query as an example is this array [coding, reading] (but could include any hobby value) and i would like to have this as an output:
{
"user": "bill"
"hobbies": [reading, drawing]
},
{
"user": "thomas"
"hobbies": [reading, cooking]
}
{
"user": "susan"
"hobbies": [coding, piano]
}
I tried:
{"$match": {"$expr": {"$in": [searchArray.toString(), "$hobbies"]}}}
but this only works aslong as the search array has only one value in it.
const searchArray = ["reading", "coding"];
const orArray = searchArray.map((seachValue) => {
return {
hobies: searchValue,
}
});
collection.aggregate([{
$match: { $or: orArray }
}])
The query:
db.collection.aggregate([
{
$match: {
"$or": [
{
hobbies: "reading"
},
{
hobbies: "coding"
}
]
}
}
])
Or you can use another way, no need to handle arr:
db.collection.aggregate([
{
$match: {
hobbies: {
$in: [
"reading",
"coding"
]
}
}
}
])
Run here
This is Code in node js
const result = await OrderDB.aggregate([
{ $match: { _id: mongoose.Types.ObjectId(id) } },
{
$lookup: {
from: 'products',
localField: 'product',
foreignField: '_id',
as: 'productDetail',
},
},
{
$project: {
productDetail: {
name: 1,
price: 1,
productImage: 1,
},
},
},
])
This is the response of code
{
"message": "Get Order Successfully",
"result": [
{
"_id": "5ff47348db5f5917f81871aa",
"productDetail": [
{
"name": "Camera",
"productImage": [
{
"_id": "5fe9b8a26720f728b814e246",
"img": "uploads\\product\\7Rq1v-app-7.jpg"
},
{
"_id": "5fe9b8a26720f728b814e247",
"img": "uploads\\product\\FRuVb-app-8.jpg"
}
],
"price": 550
}
]
}
]
}
I want to display only one productImage from the response using nodejs and mongoose
This is using in aggregate projection
I was use $arrayElemAt but it is don't work
I also use $first but it is don't work
so projection method I use to display only one *productImage*
Data looks like multi level nested.
You have array of results, each result contains array of productDetails
play
You need to unwind the data to get the first productImage
db.collection.aggregate([
{
"$unwind": "$result"
},
{
"$unwind": "$result.productDetail"
},
{
$project: {
pImage: {
"$first": "$result.productDetail.productImage"
}
}
}
])
With the above response
db.collection.aggregate([
{
"$project": {
productDetails: {
$map: {
input: "$productDetail",
in: {
"$mergeObjects": [
"$$this",
{
productImage: {
"$arrayElemAt": [
"$$this.productImage",
0
]
}
}
]
}
}
}
}
}
])
Working Mongo playground
So these are my two documents
Order document:
{
"_id":"02a33b9a-284c-4869-885e-d46981fdd679",
"context":{
"products":[
{
"id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00"
}
],
},
}
Product document:
{
"_id":"e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"context":{
"name": "My Product",
"image": "someimage"
},
}
So I'm trying to do a lookup for a products in order document, but the result should contain combined fields, like so:
"products":[
{
"_id": "e68fc86a-b4ad-4588-b182-ae9ee3db25e4",
"version": "2020-03-14T13:18:41.296+00:00",
"name": "My Product",
"image": "someimage"
}
],
Not sure how to do this, should I do it outside of the lookup, or inside? This is my aggregation
Orders.aggregate([
{
"$lookup":{
"from":"products",
"let":{
"products":"$context.products"
},
"pipeline":[
{
"$match":{
"$expr":{
"$in":[
"$_id",
"$$products.id"
]
}
}
},
{
"$project":{
"_id":0,
"id":1,
"name":"$context.name"
}
}
],
"as":"mergedProducts"
}
},
{
"$project":{
"context":"$context",
"mergedProducts":"$mergedProducts"
}
},
]);
You need to run that mapping outside of $lookup by running $map along with $arrayElemAt to get single pair from both arrays and then apply $mergeObjects to get one object as a result:
db.Order.aggregate([
{
$lookup: {
from: "products",
localField: "context.products.id",
foreignField: "_id",
as: "productDetails"
}
},
{
$addFields: {
productDetails: {
$map: {
input: "$productDetails",
in: {
_id: "$$this._id",
name: "$$this.context.name"
}
}
}
}
},
{
$project: {
_id: 1,
"context.products": {
$map: {
input: "$context.products",
as: "prod",
in: {
$mergeObjects: [
"$$prod",
{ $arrayElemAt: [ { $filter: { input: "$productDetails", cond: { $eq: [ "$$this._id", "$$prod.id" ] } } }, 0 ] }
]
}
}
}
}
}
])
Mongo Playground
The goals of the last step is to take take two arrays: products and productDetails (the output of $lookup) and find matches between them. We know there's always one match so we can get only one item $arrayElemAt 0. As an output of $map there will be single array containing "merged" documents.