Getting item in a nested array mongoldb - mongodb

I am trying to aggregate an object but getting null. Would appreciate help.
here is my sample object
[
{
"_id": 1,
"item": "sweatshirt",
"price.usd": 45.99,
"am": [
{
"one": 100
}
],
qty: 300
},
{
"_id": 2,
"item": "winter coat",
"price.usd": 499.99,
"am": [
{
"one": 50
}
],
qty: 200
},
{
"_id": 3,
"item": "sun dress",
"price.usd": 199.99,
"am": [
{
"one": 10
}
],
qty: 250
}
]
This is my query
db.collection.aggregate([
{
$group: {
_id: {
$getField: {
$literal: "am.one"
}
},
}
}
])
currently I am getting
[
{
"_id": null
}
]
any help is appreciated.
I want my response to look like
{
_id: 1
anotherThin: anotherValue
}

Here's one way you could do it.
db.collection.aggregate([
{ // only pass docs that have a numerical "am.one"
"$match": {
"am.one": {"$type": "number"}
}
},
{ // unwind in case multiple objects in "am"
"$unwind": "$am"
},
{ // group by "id" and average "one" values
"$group": {
"_id": "$am.id",
"am1avg": {"$avg": "$am.one"}
}
}
])
Try it on mongoplayground.net.

Related

Unable to match objects in array

I have several documents like the following and I'm trying to retrieve the documents where the first element of the scores array was created within the past 24hrs:
[
{
"id": 1,
"scores": [
{
"score": 1,
created_at: ISODate("2022-11-19T00:05:00.000+00:00")
},
{
"score": 2,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
{
"id": 2,
"scores": [
{
"score": 3,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
},
{
"score": 5,
created_at: ISODate("2022-11-20T00:05:00.000+00:00")
}
]
},
]
This is the query:
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
"$scores.0.created_at",
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
https://mongoplayground.net/p/L1jI10efWGL
However, nothing is returned. Does anyone know what might be wrong?
Instead of using $scores.0.created_at, use $getField to get the value of created_at from the first element of the scores array.
db.collection.aggregate([
{
$match: {
$expr: {
$gte: [
{
$getField: {
field: "created_at",
input: {
$first: "$scores"
}
}
},
{
$subtract: [
"$$NOW",
86400000
]
}
]
}
}
}
])
Demo # Mongo Playground

mongodb - query to count number of array elements inside an object

I've a mongodb collection "customer_vehicle_details" as below:
{
"_id": ObjectId("5660c2a5b6fcba2d47baa2d9"),
"customer_id": 4,
"customer_vehicles": {
"cars": [
{
"id": 1,
"name": "abc"
},
{
"id": 2,
"name": "xyz"
}
],
"bikes": [
{
"id": 1,
"name": "pqr"
},
{
"id": 2,
"name": "asdf"
}
]
}
}
I want to count total number of "cars" and total number of "bikes" separately in "customer_vehicles" collections. Not a sum of cars and bikes.
I tried with
db.customer_vehicle_details.aggregate(
{
$group: {
_id: "$customer_vehicles.cars",
total: { $sum: { $size:"$cars" } }
}
}
)
but this is giving me an error ""errmsg" : "The argument to $size must be an array, but was of type: missing"
How do I count total number of array elements inside an object in mongoDB?
Does this helps:
db.collection.aggregate([
{
"$project": {
carsCount: {
"$size": "$customer_vehicles.cars"
},
bikesCount: {
"$size": "$customer_vehicles.bikes"
}
}
}
])
Here's the playground link.

How to delete ($pull) nested array elements with property value null in arrays with more than one element

I would like to delete ($pull) nested array elements where one of the element's properties is null and where the array has more than one element.
Here is an example. In the following collection, I would like to delete those elements of the Orders array that have Amount = null and where the Orders array has more than one element. That is, I would like to delete only the element with OrderId = 12, but no other elements.
db.TestProducts.insertMany([
{
ProductDetails: { "ProductId": 1, Language: "fr" },
Orders: [
{ "OrderId": 11, "Amount": 200 },
{ "OrderId": 12, "Amount": null }
]
},
{
ProductDetails: { "ProductId": 2, Language: "es" },
Orders: [
{ "OrderId": 13, "Amount": 300 },
{ "OrderId": 14, "Amount": 400 }
]
},
{
ProductDetails: { "ProductId": 3, Language: "en" },
Orders: [
{ "OrderId": 15, "Amount": null }
]
}
]);
The following attempt is based on googling and a combination of a few other StackOverflow answers, e.g. Aggregate and update MongoDB
db.TestProducts.aggregate(
[
{ $match: { "Orders.Amount": { "$eq": null } } },
{ $unwind: "$Orders" },
{
"$group": {
"_id": {
ProductId: "$ProductDetails.ProductId",
Language: "$ProductDetails.Language"
},"count": { "$sum": 1 }
}
},
{ "$match": { "count": { "$gt": 1 } } },
{ "$out": "temp_results" }
],
{ allowDiskUse: true}
);
db.temp_results.find().forEach((result) => {
db.TestProducts.updateMany({"ProductDetails.ProductId": result._id.ProductId, "ProductDetails.Language": result._id.Language },
{ $pull: { "Orders": {"Amount": null } }})
});
This works, but I am wondering if it can be done in a simpler way, especially if it is possible to delete the array elements within the aggregation pipeline and avoid the additional iteration (forEach).
You can check these conditions in the update query, check 2 conditions
Amount is null
check the expression $expr condition for the size of the Orders array is greater than 1
db.TestProducts.updateMany({
"Orders.Amount": null,
"$expr": {
"$gt": [{ "$size": "$Orders" }, 1]
}
},
{
"$pull": {
"Orders": { "Amount": null }
}
})
Playground
an example
an example might help:
let feed = await Feed.findOneAndUpdate(
{
_id: req.params.id,
feeds: {
$elemMatch: {
type: FeedType.Location,
locations: {
$size: 0,
},
},
},
},
{
$pull: {
feeds: { locations: { $size: 0 }, type: FeedType.Location },
},
},
{ new: true, multi: true }
);

Single array of objects sort and slice not working

I have a single entry on a collection like this:
{
"_id" : ObjectId("60c6f7a5ef86bd1a5402e928"),
"cid" : 1,
"array1" : [
{ "type": "car", value: 20 },
{ "type": "bike", value: 50 },
{ "type": "bus", value: 5 },
{ "type": "cycle", value: 100 },
...... 9000 more entry something like this
],
"array2" : [
{ "type": "laptop", value: 200 },
{ "type": "desktop", value: 15 },
{ "type": "tablet", value: 55 },
{ "type": "mobile", value: 90 },
...... 9000 more entry something like this
]
}
Now I want to sort and slice the data for the pagination purpose.
For that I wrote the query which works well on slice case but not on sort case.
This is my query which works for slice case
let val = await SomeCollectionName.findOne(
{ cid: 1 },
{ _id: 1 , array1: { $slice: [0, 10] } } ---> its return the 10 data. Initially it return from 0 to 10, then next call $slice: [10, 10]
).exec();
if (val) {
//console.log('Got the value')
}
console.log(error)
This is my query When I add sort with slice
let val = await SomeCollectionName.findOne(
{ cid: 1 },
{ _id: 1 , array1: { $sort: { value: -1 }, $slice: [0, 10] } }
).exec();
if (val) {
//console.log('Got the value')
}
console.log(error)
Is there anyone who guide me where I'm wrong or suggest me what is the efficient way for getting the data.
UPDATE
I am getting the answer from the above question and looking for the same implementation for two array.
Everything is same. Earlier I was dealing with 1 array now this time I have to deal with two array.
Just curious to know that how these things happen
I wrote the aggregation query but one array results is fine but others are returning the same data throughout the array.
This is my query as per the suggestion of dealing with single array with sort and slice
db.collection.aggregate([
{
"$match": {
"cid": 1
}
},
{
$unwind: "$array1"
},
{
$unwind: "$array2"
},
{
"$sort": {
"array1.value": -1,
"array2.value": -1,
}
},
{
$skip: 0
},
{
$limit: 3
},
{
$group:{
"_id":"$_id",
"array1":{$push:"$array1"},
"array2":{$push:"$array2"}
}
}
])
The issue is that $sort is not supported by findOne() in its projection parameter.
You can instead use aggregation to achieve the expected result,
db.collection.aggregate([
{
"$match": {
"cid": 1
}
},
{
$unwind: "$array1"
},
{
"$sort": {
"array1.value": -1
}
},
{
$skip: 0
},
{
$limit: 3
},
{
$group: {
"_id": "$_id",
"array1": {
$push: {
"type": "$array1.type",
"value": "$array1.value"
}
},
"array2": {
"$first": "$array2"
}
},
},
{
$unwind: "$array2"
},
{
"$sort": {
"array2.value": -1
}
},
{
$skip: 0
},
{
$limit: 3
},
{
$group: {
"_id": "$_id",
"array2": {
$push: {
"type": "$array2.type",
"value": "$array2.value"
}
},
"array1": {
"$first": "$array1"
}
},
}
])
Aggregation
$unwind

Paginate MongoDB aggregation response

I have a database of users that have skills. I have set up a way to find users in the database using am aggregation method included in mongoose. Depending on the search criteria I input into the aggregation, the results may be too big to actually display on my front end app. I am curious how I can paginate an aggregation query with the typical limit, page, and skip variables like you would do in a typical GET request.
Here is my aggregation query:
const foundUsers = await User.aggregate([
{
$addFields: {
matchingSkills: {
$filter: {
input: '$skills',
cond: {
$or: test,
},
},
},
requiredSkills,
},
},
{
$addFields: {
// matchingSkills: '$$REMOVE',
percentageMatch: {
$multiply: [
{ $divide: [{ $size: '$matchingSkills' }, skillSearch.length] }, // yu already know how many values you need to pass, thats' why `2`
100,
],
},
},
},
{
$addFields: {
matchingSkillsNames: {
$map: {
input: '$matchingSkills',
as: 'matchingSkill',
in: '$$matchingSkill.skill',
},
},
},
},
{
$addFields: {
missingSkills: {
$filter: {
input: '$requiredSkills',
cond: {
$not: {
$in: ['$$this', '$matchingSkillsNames'],
},
},
},
},
},
},
{
$match: { percentageMatch: { $gte: 25 } },
},
]);
Passing these skills to this aggregate function:
{
"skillSearch": [
{
"class": "skills",
"skill": "SQL",
"operator": "GT",
"yearsExperience": 6
},
{
"class": "skills",
"skill": "C",
"operator": "GT",
"yearsExperience": 1
}
]
}
Will result in a response similar to this:
{
"_id": "60184ce81e65633873d709aa",
"name": "Brad",
"email": "brad#gmail.com",
"password": "$2a$12$37v2RwaO5LhSMT8GJQSZyel.Aawn6AmlqqSOkZtopqIIXyJ0LRBfu",
"__v": 0,
"skills": [
{
"_id": "60a306ce819cde701c1934a8",
"skill": "SQL",
"yearsExperience": 8
},
{
"_id": "60a306ce819cde701c1934a9",
"skill": "C",
"yearsExperience": 5
},
{
"_id": "60a306ce819cde701c1934aa",
"skill": "PL/I",
"yearsExperience": 2
},
{
"_id": "60a306ce819cde701c1934ab",
"skill": "Awk",
"yearsExperience": 9
}
],
"matchingSkills": [
{
"_id": "60a306ce819cde701c1934a8",
"skill": "SQL",
"yearsExperience": 8
},
{
"_id": "60a306ce819cde701c1934a9",
"skill": "C",
"yearsExperience": 5
}
],
"requiredSkills": [
"SQL",
"C"
],
"percentageMatch": 100,
"matchingSkillsNames": [
"SQL",
"C"
],
"missingSkills": []
},
For the pagination, you need to pass the page and size form the front end
$sort to sort the documents,
$skip skip the documents. For eg : if you are in page two and u need 10 rows , u need to skip first 10 documents
$limit to how many documents you need to show after skip
here is the code
db.collection.aggregate([
{
$sort: {
_id: 1
}
},
{
$skip: 0 // page*size
},
{
$limit: 10 // size
}
])
Working Mongo playground
More than, the pagination requires total elements too, for that
db.collection.aggregate([
{
"$facet": {
"elements": [
{
"$group": {
"_id": null,
"count": { "$sum": 1 }
}
}
],
"data": [
{ $sort: { _id: 1 } },
{ $skip: 0 }, // page*size
{ $limit: 10 } // size
]
}
},
{ "$unwind": "$elements" },
{
"$addFields": {
"elements": "$$REMOVE",
"totalRecords": "$elements.count"
}
}
])
Working Mongo playground