MongoDB: nested array count + original document - arrays

I have the following document structure which contains an array of votes:
{ _id: ObjectId("6350e2c1a15e0e656f4a7472"),
category: 'business',
votes:
[ { voteType: 'like',
userId: ObjectId("62314007da34df3f32f7cfc0") },
{ voteType: 'like',
userId: ObjectId("6356b5cbe2272ebf628451b") } ] }
What I would like to achieve is to add for each document the sum of votes for which voteType = like, while keeping the original document, such as:
[ [{ _id: ObjectId("6350e2c1a15e0e656f4a7472"),
category: 'business',
votes:
[ { voteType: 'like',
userId: ObjectId("62314007da34df3f32f7cfc0") },
{ voteType: 'like',
userId: ObjectId("6356b5cbe2272ebf628451b") } ] }, {sum: 2, voteType: "like"} ], ...]
At the moment, the only workaround that I found is through an aggregation although I cannot manage to keep the original documents in the results:
db.getCollection('MyDocument') .aggregate([ {
$unwind: "$votes" }, {
$match: {
"votes.voteType": "like",
} }, {
$group: {
_id: {
name: "$_id",
type: "$votes.voteType"
},
count: {
$sum: 1
}
} },
{ $sort : { "count" : -1 } }, {$limit : 5}
])
which gives me:
{ _id: { name: ObjectId("635004f1b96e494947caaa5e"), type: 'like' },
count: 3 }
{ _id: { name: ObjectId("63500456b96e494947cbd448"), type: 'like' },
count: 3 }
{ _id: { name: ObjectId("63500353b6c7eb0a01df268e"), type: 'like' },
count: 2 }
{ _id: { name: ObjectId("634e315bb7d17339f8077c39"), type: 'like' },
count: 1 }

You can do it like this:
$cond with $isArray - to check if the votes property is of the type array.
$filter - to filter votes based on voteType property.
$size - to get the sized of the filtered array.
db.collection.aggregate([
{
"$set": {
"count": {
"$cond": {
"if": {
"$isArray": "$votes"
},
"then": {
"$size": {
"$filter": {
"input": "$votes",
"cond": {
"$eq": [
"$$this.voteType",
"like"
]
}
}
}
},
"else": 0
}
}
}
}
])
Working example

Related

Mongo aggregation framework match a given _id

My model :
const scheduleTaskSchema = new Schema({
activity: { type: Object, required: true },
date: { type: Date, required: true },
crew: Object,
vehicle: Object,
pickups: Array,
details: String,
});
const ScheduleTaskModel = mongoose.model("schedule_task", scheduleTaskSchema),
and this aggregation pipeline :
let aggregation = [
{
$sort: {
"pickups.0.time": 1,
},
},
{
$group: {
_id: "$date",
tasks: { $push: "$$ROOT" },
},
},
{ $sort: { _id: -1 } },
];
if (hasDateQuery) {
aggregation.unshift({
$match: {
date: { $gte: new Date(start_date), $lte: new Date(end_date) },
},
});
} else {
aggregation.push({ $limit: 2 });
}
const scheduledTasksGroups = await ScheduleTaskModel.aggregate(aggregation);
the crew object can have arbitrary number of keys with this structure :
crew : {
drivers: [
{
_id: "656b1e9cf5b894a4f2v643bc",
name: "john"
},
{
_id: "567b1e9cf5b954a4f2c643bhh",
name: "bill"
}
],
officers: [
{
_id: "655b1e9cf5b6632a4f2c643jk",
name: "mark"
},
{
_id: "876b1e9af5b664a4f2c234bb",
name: "jane"
}
],
//...any number of keys that contain an array of objects that all have an _id
}
I'm looking for a way to return all documents (before sorting/grouping) that contain a given _id anywhere within the crew object without knowing which key to search,it can be many different keys that all contain an array of objects that all have an _id
Any ideas ?
You can use $objectToArray for this:
db.collection.aggregate([
{$addFields: {crewFilter: {$objectToArray: "$crew"}}},
{$set: {
crewFilter: {$size: {
$reduce: {
input: "$crewFilter",
initialValue: [],
in: {$concatArrays: [
"$$value",
{$filter: {
input: "$$this.v",
as: "member",
cond: {$eq: ["$$member._id", _id]}
}
}
]
}
}
}}
}},
{$match: {crewFilter: {$gt: 0}}}
])
See how it works on the playground example

How to get specific fields on document MangoDB&Mongoose and aggregate some of the fields?

My data looks like this:
[
{
"_id":"61717cafd351f3ae8b6d205a",
"restaurant":"Hogwarts",
"purchasedAt":"2021-10-20T17:47:40.166Z",
"products":[
{
"name":"Meat Samosa",
"price":3.95,
"quantity":1,
"_id":"61717cafd351f3ae8b6d205b"
},
{
"name":"Pilau Rice",
"price":2.95,
"quantity":1,
"_id":"61717cafd351f3ae8b6d205f"
}
]
},
{
"_id":"61717cb2d351f3ae8b6dd05b",
"restaurant":"Hogwarts",
"purchasedAt":"2021-10-20T03:14:11.111Z",
"products":[
{
"name":"Pilau Rice",
"price":2.95,
"quantity":1,
"_id":"61717cb2d351f3ae8b6dd05d"
}
]
},
]
I am trying to find a query that will get me all the products (no duplicates) and their quantities added up. Notice that the products id are different even when they are the same(same name) Ideally my response would look like this
[
{
name: "Meat Samosa",
price: 3.95,
quantity: 1
},
{
name: "Pilau Rice",
price: 2.95,
quantity: 2
}
]
$project to show required fields
$unwind deconstruct the products array
$group by name and get the first price and count the quantity sum
$project to show required fields
db.collection.aggregate([
{
$project: {
_id: 0,
products: 1
}
},
{ $unwind: "$products" },
{
$group: {
_id: "$products.name",
price: { $first: "$products.price" },
quantity: { $sum: "$products.quantity" }
}
},
{
$project: {
_id: 0,
name: "$_id",
price: 1,
quantity: 1
}
}
])
Playground

MongoDB - Geometry object to return the total pairs/points in linear ring

I am trying to write a performance report based on shapefile data I have stored within docs stored in collections.
Here is a sample of data:
The following function works quite well as it returns the amount of bytes for each document - great, however I would also like to know how many points/pairs are stored within each polygon's linear string for each document.
db.getCollection("_collectionName").aggregate([{"$project": {"object_size": { $bsonSize: "$$ROOT" }}}])
This returns the following set of data (sample):
{ _id: ObjectId("5ef7da26ae8659149c97657e"), rootSize: 42215 },
{ _id: ObjectId("5ef7da45ae8659149c97657f"), rootSize: 118574 },
{ _id: ObjectId("5ef7daf1ae8659149c976585"), rootSize: 11886 },
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), rootSize: 43136 },
{ _id: ObjectId("5ef7daa6ae8659149c976582"), rootSize: 40823 },
{ _id: ObjectId("5f3495129861ce45eb4e9728"), rootSize: 394884 },
{ _id: ObjectId("5ef7d7f6ae8659149c97657c"), rootSize: 125309 },
{ _id: ObjectId("5ef7dad6ae8659149c976584"), rootSize: 127447 },
{ _id: ObjectId("5fa56ef26538cd3bddd8389e"), rootSize: 17670 },
{ _id: ObjectId("5fa56ef26538cd3bddd8389f"), rootSize: 11398 },
{ _id: ObjectId("5fa56ef16538cd3bddd8389c"), rootSize: 2415 },
{ _id: ObjectId("5fa56ef36538cd3bddd838ae"), rootSize: 1757 },
{ _id: ObjectId("5fa56ef36538cd3bddd838b0"), rootSize: 4866 },
{ _id: ObjectId("5fa56ef36538cd3bddd838a8"), rootSize: 1510 },
{ _id: ObjectId("5fa56ef26538cd3bddd838a7"), rootSize: 39631 },
{ _id: ObjectId("5fa56ef36538cd3bddd838ab"), rootSize: 3662 },
{ _id: ObjectId("5fa56ef36538cd3bddd838aa"), rootSize: 15844 },
{ _id: ObjectId("5fa56ef16538cd3bddd8389d"), rootSize: 17196 },
{ _id: ObjectId("5fa56ef26538cd3bddd838a3"), rootSize: 34940 },
{ _id: ObjectId("5fa56ef36538cd3bddd838af"), rootSize: 468367 }
Which is great but it does not tell me how many elements are in the array/linear string within geometry.coordinates.
I have tried the following, but no cigar:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry", [] ] } } } }])
MongoServerError: The argument to $size must be an array, but was of type: object
It comes back with an error, which i understand - so i referenced the coordinates array:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry.coordinates", [] ] } } } }])
Which, returned the following data, again correct, if you understand GeoJSON files this is normal as this is the top level of the linear ring, sample data:
{ _id: ObjectId("5ef7da26ae8659149c97657e"), count: 1 }
{ _id: ObjectId("5ef7da45ae8659149c97657f"), count: 1 }
{ _id: ObjectId("5ef7daf1ae8659149c976585"), count: 1 }
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), count: 1 }
So I then added the top level array of 0 to my aggregate function:
db.getCollection("_collectionName").aggregate([{$project: { count: { $size: { "$ifNull": [ "$geometry.coordinates.0", [] ] } } } }])
And this is what was returned:
{ _id: ObjectId("5ef7da26ae8659149c97657e"), count: 0 }
{ _id: ObjectId("5ef7da45ae8659149c97657f"), count: 0 }
{ _id: ObjectId("5ef7daf1ae8659149c976585"), count: 0 }
{ _id: ObjectId("5f216685dbef0f7c3339ec03"), count: 0 }
And that is not possible, here is a screenshot from Studio3T software:
Anybody who might be able to help or point me in the right direction please do so....
(I would be very grateful!)
The dot notation won't work on array elements within an aggregation. You'll want to use the $arrayElemAt operator, as follows:
db.getCollection("_aGStbl").aggregate([{
$project: {
count: { $size: { $arrayElemAt: [ "$geometry.coordinates", 0 ]}}
}
}])
To cater for Null values, you can use a $cond, depending on your objective for the output:
INSERT SOME DATA INTO A TESTDB:
db.arrayTest.insertMany([
{ _id: 1, arrayOfArrays: [ [ 1, 2, 3 ], [ 1, 2, 3, 4 ], [ 1, 2, 3, 4, 5, 6, 7 ] ] },
{ _id: 2, arrayOfArrays: [ [ 4, 5 ], [ 5, 6, 7 ] ] },
{ _id: 3, arrayOfArrays: [ [], [] ] },
{ _id: 4, arrayOfArrays: [ [], [], [] ] },
{ _id: 5 }
] )
{ acknowledged: true,
insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 } }
TRY THESE AGGREGATE CALLS:
db.arrayTest.aggregate([{$project: { count: { $size: { "$ifNull": [ { $arrayElemAt: [ "$arrayOfArrays", 0 ] }, [ ] ] } } } } ] )
{ _id: 1, count: 3 }
{ _id: 2, count: 2 }
{ _id: 3, count: 0 }
{ _id: 4, count: 0 }
{ _id: 5, count: 0 }
db.arrayTest.aggregate([{$project: { count: { $cond: { if: {$arrayElemAt: [ "$arrayOfArrays", 0 ]}, then: { $size: { $arrayElemAt: [ "$arrayOfArrays", 0 ] } }, else: null} } } } ] )
{ _id: 1, count: 3 }
{ _id: 2, count: 2 }
{ _id: 3, count: 0 }
{ _id: 4, count: 0 }
{ _id: 5, count: null }

How can I group and count by most frequent values in mongoDB

Having a scheme like this
[{
id:1, category:['gourmet','mexican','breakfasts'], food:'eggs&Beans'
},
{
id:2, category:['breakfasts'], food:'waffles'
},
{
id:3, category:['mexican','breakfasts'], food:'burrito'
},]
How could I group and count categories with most frequent value to achieve something like this:
[
{category: 'breakfasts', count:3, foods:['eggs&beans','waffles','burrito']},
{category: 'mexican', count:2, foods:['eggs&beans','burrito']},
{category: 'gourmet', count:1, foods:['eggs&beans']},
]
Demo - https://mongoplayground.net/p/jYUjXUtzvru
db.collection.aggregate([
{ $unwind: "$category" }, // break into individual documents
{
$group: { // group by category
_id: "$category",
count: { $sum: 1 }, // get the count
foods: { $push: "$food" } // add food the array
}
},
{ $sort: { count: -1 } }, // sort by count
{
$project: {
_id: 0,
category: "$_id",
foods: 1,
count: 1
}
}
])

Mongoose In ArrayElemAt Issue In Aggregate

temporaryshifts is equal to
[{_id:123,
arr:[{_id:123321,name:"Bluh Bluh",date:"bluh bluh"}]
}]
so i want to access temporaryshifts[0].arr[0]
but i dont know how to access
$project:{
shiftArr:{$arrayElemAt:['$temporaryshifts',0]}
}
You have to make use of the let operators to make use of two $arrayElemAt Operators.
db.collection.aggregate([
{
"$project": {
"temporaryshifts": {
"$let": {
"vars": {
"masterKey": {
"$arrayElemAt": [
"$temporaryshifts",
0
]
}
},
"in": {
"$arrayElemAt": [
"$$masterKey.arr",
0
]
}
},
}
},
},
])
Mongo Playground Sample Execution
To Understand Properly i Am taking this example and suppose that this our data
`
[
{
_id: 123,
arr: [
{
_id: 123321,
name: "Bluh Bluh",
date: "bluh bluh"
},
{
_id: 1233219,
name: "Bluh Bluh2",
date: "bluh bluh2"
}
]
},
{
_id: 1234,
arr: [
{
_id: 1233214,
name: "Bluh Bluh4",
date: "bluh bluh4"
}
]
},
]
`
Can get temporaryshifts[0].arr[0] with this query
db.collection.aggregate([
{
"$match": {
"_id": 123
}
},
{
"$project": {
shiftArr: {
$arrayElemAt: [
"$arr",
0
]
}
}
}
])
You need to match at the first time to get the document and rest of thing arrayElementAt can manage.

Resources