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.
Related
below is just an example of my mongodb documents. and sum the product qty in every transaction documents in I expected 175 when select transaction August from product documents
the expected result in products documents
[
{
"_id":"61012014f7416b0a41db7055",
"name":"MIE",
"retailPrice":20000,
"qtyInTransaction":175
}
]
here is my document
products document
[
{
"_id":"61012014f7416b0a41db7055",
"name":"MIE",
"retailPrice":20000,
"description":"PRODUK BARU",
}
]
producutInTransaction document
[
{
"_id":"6106a3b1f1bbd62640c7b404",
"transactionDate":{"$date":"2021-08-01T00:00:00.000Z"},
"productsInTransactions":
[
{
"_id":"6106a3cff1bbd62640c7b405",
"productId":"61012014f7416b0a41db7055"},
"qty":100,
"price":5000,
"description":"asdn"
},
{
"_id":"6106a3cff1bbd62640c7b406",
"productId":"61012014f7416b0a41db7055"},
"qty":50,
"price":5000,
"description":"asdn"
}
]
},
{
"_id":"6106a3b1f1bbd62640c7b4007",
"transactionDate":{"$date":"2021-08-01T00:00:00.000Z"},
"productsInTransactions":
[
{
"_id":"6106a3cff1bbd62640c7b407",
"productId":"61012014f7416b0a41db7055"},
"qty":25,
"price":5000,
"description":"asdn"
},
]
}
]
Thanks for any help, and sorry for my English
You can use this query (adding ISODate value on whatever you want:
First $match by transactionData which has to be "greater than or equal" to desired date. Here you have to calculate your date using JS and use the variable.
Then $unwind productsInTransactions field to deconstruct the array.
$group by productId and $sum the qty value.
Later, $lookup to "join" with "product" collection and generate data field with desired values.
And least $project to shown only fields you want.
db.productInTransaction.aggregate([
{
"$match": {
"transactionDate": {
"$gte": ISODate("2021-07-01T00:00:00.000Z")
}
}
},
{
"$unwind": "$productsInTransactions"
},
{
"$group": {
"_id": "$productsInTransactions.productId",
"qty": {
"$sum": "$productsInTransactions.qty"
}
}
},
{
"$lookup": {
"from": "product",
"localField": "_id",
"foreignField": "_id",
"as": "data"
}
},
{
"$project": {
"name": {
"$arrayElemAt": [
"$data.name",
0
]
},
"retailPrice": {
"$arrayElemAt": [
"$data.retailPrice",
0
]
},
"qtyInTransaction": "$qty"
}
}
])
Example here
Result is:
[
{
"_id": "61012014f7416b0a41db7055",
"name": "MIE",
"qtyInTransaction": 175,
"retailPrice": 20000
}
]
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'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"
}
}
])
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
I have created an aggregate function and I feel it's pretty long and non-DRY. I'm wondering what ways I can improve it.
My Thread model has a sub-document called revisions. The function tries to get the most recent revision that has the status of APPROVED.
Here is the full model.
{
"_id": ObjectId("56dc750769faa2393a8eb656"),
"slug": "my-thread",
"title": "my-thread",
"created": 1457249482555.0,
"user": ObjectId("56d70a491128bb612c6c9220"),
"revisions": [
{
"body": "This is the body!",
"status": "APPROVED",
"_id": ObjectId("56dc750769faa2393a8eb657"),
"comments": [
],
"title": "my-thread"
}
]
}
And here is the aggregate function I want to improve.
Thread.aggregate([
{ $match: {
slug: thread
} },
{ $project: {
user: '$user',
created: '$created',
slug: '$slug',
revisions: {
$filter: {
input: '$revisions',
as: 'revision',
cond: { $eq: [ '$$revision.status', 'APPROVED' ] }
}
}
} },
{ $sort: { 'revisions.created': -1 } },
{ $project: {
user: '$user',
created: '$created',
slug: '$slug',
revisions: { $slice: ["$revisions", 0, 1] }
} },
{ $unwind: '$revisions'},
{ $project: {
body: '$revisions.body',
title: '$revisions.title',
user: '$user',
slug: '$slug',
created: '$created'
}}
])
Well you cannot really since there are $sort and $unwind stages in between on purpose. It's also basically "wrong", since the $sort cannot re-order the array until you $unwind it first.
Then it is better to use $group and $first instead, to just get the first element from the sort in each document:
Thread.aggregate([
{ "$match": {
"slug": thread
} },
{ "$project": {
"user": 1,
"created": 1,
"slug": 1,
"revisions": {
"$filter": {
"input": "$revisions",
"as": "revision",
"cond": { "$eq": [ "$$revision.status", "APPROVED" ] }
}
}
} },
// Cannot sort until you $unwind
{ "$unwind": "$revisions" },
// Now that will sort the elements
{ "$sort": { "_id": 1, "revisions.created": -1 } },
// And just grab the $first boundary for everything
{ "$group": {
"_id": "$_id",
"body": { "$first": "$revisions.body" },
"title": { "$first": "$revisions.title" },
"user": { "$first": "$user" },
"slug": { "$first": "$slug" },
"created": { "$first": "$created" }
}}
])
You could always reform the array with $push and then apply $arrayElemAt instead of the $slice to yield just a single element, but it's kind of superflous considering it would need another $project after the $group in the first place.
So even though there are "some" operations you can do without using $unwind, unfortunately "sorting" the arrays generated out of functions like $filter is not something that can be presently done, until you $unwind the array first.
If you didn't "need" the $sort on the "revisions.created" ( notably missing from your sample document ) then you can instead just use normal projection instead:
Thread.find(
{ "slug": slug, "revisions.status": "APPROVED" },
{ "revisions.$": 1 },
)
Only when sorting array elements would you need anything else, since the $ positional operator will just return the first matched element anyway.