mongoose aggregate sort one array by field - mongodb

I would like to fetch only one element from each group of fields:
here is my result
[{
"_id":{"device_id":"9"},
"x":["2019-11-17T18:03:33.000Z","2019-11-17T12:02:35.000Z"],
"y":["93","3"]
},
{
"_id":{"device_id":"8"},
"x":["2019-11-16T12:05:33.000Z","2019-11-16T12:02:35.000Z"],
"y":["33","3"]
}]
I would like to get only the last inserted data, means only one value from x and one value from y.
This is the code :
aggregate([
{ "$sort": { "timeStamp": -1 } },
{
$group:
{
_id: { device_id: '$device_id' },
x: {
$push: "$timeStamp"
},
y: {
$push: "$value"
},
device_name: {
$push: "$device_name"
},
}
}
}]
I have tried to add project exp:
{
$project: {
time: "$x",
value:"$y",
integerValues:{
$map:{
input: "x",
as: "integerValue",
in: { $trunc: "$$integerValue" }
}
}
}
}
This is the result I expect to have it:
{
"_id":{"device_id":"8"},
"x":["2019-11-16T12:05:33.000Z"],
"y":["33"]
},
{
"_id":{"device_id":"9"},
"x":["2019-11-17T18:03:33.000Z"],
"y":["93"]
}
I was searching so much about this problem without any solution

All you need is a single project stage
aggregate([{
$project: {
x: {
$max: "$x"
},
y: {
$max: "$y"
}
}
}])
The result you get will be something like this,
[
{
"_id": {
"device_id": "9"
},
"x": "2019-11-17T18:03:33.000Z",
"y": "93"
},
{
"_id": {
"device_id": "8"
},
"x": "2019-11-16T12:05:33.000Z",
"y": "33"
}
]

Related

Lodash `countBy` equivalent in MongoDB?

Let's say I have the input docs below:
[
{
"_id": "6225ca4052e7c226e2dd836d",
"data": [
"07",
"07",
"12",
"19",
"07",
"32"
]
},
{
"_id": "6225ca4052e7c226e2dd888f",
"data": [
"99",
"97",
"52",
"99",
"58",
"92"
]
}
]
I want to count the occurrences of every element in data string array per document. In JS, I can use countBy. How can I achieve the same using MongoDB Aggregation Framework?
I have tried to $reduce but MongoDB seems to not support assigning dynamic field to object.
{
$reduce: {
input: '$data',
initialValue: {},
in: { // assign `$$this` with count to `$$value`, but failed! }
}
}
Below is the desired output.
[
{
"_id": "6225ca4052e7c226e2dd836d",
"freqs": {
"12": 1,
"19": 1,
"32": 1,
"07": 3
}
},
{
"_id": "6225ca4052e7c226e2dd888f",
"freqs": {
"52": 1,
"58": 1,
"92": 1,
"97": 1,
"99": 2
}
}
]
db.collection.aggregate([
{
$match: {}
},
{
$unwind: "$data"
},
{
$group: {
_id: "$data",
c: { $sum: 1 },
id: { $first: "$_id" }
}
},
{
$group: {
_id: "$id",
data: { $push: { k: "$_id", v: "$c" } }
}
},
{
$set: {
data: { $arrayToObject: "$data" }
}
}
])
mongoplayground
db.collection.aggregate([
{
$set: {
data: {
$function: {
body: "function(d) {let obj = {}; d.forEach(e => {if(obj[e]==null) { obj[e]=1; }else{ obj[e]++; }}); return obj;}",
args: [
"$data"
],
lang: "js"
}
}
}
}
])
mongoplayground

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

MongoDB Query - Get frequency map of an array

[
{
"_id": ObjectId("id-1"),
"tests": [
{
"category": "cat1",
"status": "status1",
},
{
"category": "cat1",
"status": "status2",
},
{
"category": "cat2",
"status": "status2",
},
],
},
{
"_id": ObjectId("id-2"),
"tests": [
{
"category": "cat2",
"status": "status1",
},
{
"category": "cat1",
"status": "status1",
},
{
"category": "cat1",
"status": "status2",
},
],
}
]
I have the above collection, my intention is to generate the below result. Please note that the statuses and categories are dynamic.
[
{
"id" : id-1,
"status": {
"status1": count,
"status2": count
},
"category": {
"cat1": count of it,
"cat2": count of it
}
},
{
"id" : id-2,
"status": {
"status1": count of it,
"status2": count of it
},
"category": {
"cat1": count of it,
"cat2": count of it
}
}
]
What I've attempted to do till now, is
Unwinded tests field, then
{
"$group": {
"_id": {
"id": "$_id",
"testStatus": "$tests.status"
},
"val": {
"$sum": 1
}
}
},
{
"$group": {
"_id": {
"id": "$_id.id",
},
"resGroup": {
"$addToSet": {
k: "$_id.testStatus",
v: "$val"
}
}
}
},
{
"$project": {
"_id": "$_id.id",
"statusGroup": {
"$arrayToObject": "$resGroup"
}
}
}
I've done the same for the category field and used $facet to run multiple aggregations.
But, am unable to fetch the result in the required format.
Any help on this will be appreciated.
Thanks
MongoDB Version: 3.4
$map to iterate loop of tests array and convert the object to an array using $objectToArray
$unwind deconstruct tests array
$unwind again deconstruct tests array because it's a nested array
$group by _id, k, and v and get the total count
$group by _id and k and construct the array of the status field in items
$arrayToObject convert items key-value array to an object
$group by _id and construct the array of items
$arrayToObject convert items array to object
db.collection.aggregate([
{
$project: {
tests: {
$map: {
input: "$tests",
in: { $objectToArray: "$$this" }
}
}
}
},
{ $unwind: "$tests" },
{ $unwind: "$tests" },
{
$group: {
_id: {
_id: "$_id",
k: "$tests.k",
v: "$tests.v"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: {
_id: "$_id._id",
k: "$_id.k"
},
items: {
$push: {
k: "$_id.v",
v: "$count"
}
}
}
},
{
$group: {
_id: "$_id._id",
items: {
$push: {
k: "$_id.k",
v: { $arrayToObject: "$items" }
}
}
}
},
{ $project: { items: { $arrayToObject: "$items" } } }
])
Playground

Writing an aggregation query in MongodB

Here is my sample document structure e.g.
{
"my_object": {
"1": {
"seq": "1",
"time": "xyz",
},
"2": {
"seq": "2",
"time": "abc",
"sub_aray": {
"0": {
"value": 10
},
"1": {
"value": 10
},
"2": {
"value": -10
}
}
}
}
}
So what I want to achieve is, Sum of all the sub_array's value if exists, if sub_array isn't found, default it to 0
{"seq" : "1", "sub_array" : 0},
{"seq" : "2", "sub_array" : 10}
My Mongo version is 3.4.6 and I am using PyMongo as my driver.
You can do as below
playground
db.collection.aggregate([
{
$project: {
"array": { //To remove dynamic keys - 1,2,etc
"$objectToArray": "$my_object"
}
}
},
{//reshaping array
"$unwind": "$array"
},
{
$project: {//reshaping sub array to access via a static name
"k": {
"$objectToArray": "$array.v.sub_aray"
},
"seq": "$array.v.seq"
}
},
{
"$project": {//actual logic, output structure
"sub_array": {
$sum: "$k.v.value"
},
"_id": 0,
"seq": 1
}
}
])
The trick part is to use the $objectToArray operator to iterate my_object items.
db.collection.aggregate([
{
"$project": {
"my_object": {
"$map": {
input: {
"$objectToArray": "$my_object"
},
as: "obj",
in: {
seq: "$$obj.v.seq",
sub_array: {
$sum: {
$map: {
input: {
"$objectToArray": "$$obj.v.sub_aray"
},
as: "sub",
in: "$$sub.v.value"
}
}
}
}
}
}
}
},
{
"$unwind": "$my_object"
},
{
"$replaceRoot": {
"newRoot": "$my_object"
}
}
])
MongoPlayground

Group on field while getting the last document for each field with MongoDB

Problem
I'm trying to group a stock inventory by products. At first, my stock entries was fully filled each time so I made this aggregate:
[
{ $sort: { date: 1 } },
{
$group: {
_id: '$userId',
stocks: { $last: '$stocks' },
},
},
{ $unwind: '$stocks' },
{
$group: {
_id: '$stocks.productId',
totalQuantity: { $sum: '$stocks.quantity' },
stocks: { $push: { userId: '$_id', quantity: '$stocks.quantity' } },
},
},
]
Now, it can be possible that a stock entry doesn't contain all the products filled. So I'm stuck while writing the new aggregate.
Basically I need to group every products by productId and have an array of the last entry for each user.
Output
This is my expected output:
[
{
"_id": ObjectId("5e75eae1359fc8159d5b6073"),
"totalQuantity": 33,
"stocks": [
{
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"lastDate": "2020-03-21T11:45:53.077Z",
"quantity": 33
}
]
},
{
"_id": ObjectId("5e75eaea359fc8159d5b6074"),
"totalQuantity": 2,
"stocks": [
{
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"lastDate": "2020-03-21T11:45:53.077Z",
"quantity": 2
}
]
}
]
Documents
Documents (when fully filled):
{
"_id": ObjectId("5e75fe71e4a3e0323ba47e0a"),
"date": "2020-03-21T11:45:53.077Z",
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"stocks": [
{
"productId": ObjectId("5e75eae1359fc8159d5b6073"),
"quantity": 33
},
{
"productId": ObjectId("5e75eaea359fc8159d5b6074"),
"quantity": 2
}
]
}
Sometimes it won't be filled for the whole inventory (that's why I need the lastDate):
{
"_id": ObjectId("5e75fe71e4a3e0323ba47e0a"),
"date": "2020-03-21T11:45:53.077Z",
"userId": ObjectId("5e75f498359fc8159d5b6075"),
"stocks": [
{
"productId": ObjectId("5e75eae1359fc8159d5b6073"),
"quantity": 33
}
]
}
Try this one:
db.collection.aggregate([
{
$group: {
_id: "$userId",
root: {
$push: "$$ROOT"
}
}
},
{
$addFields: {
root: {
$map: {
input: "$root",
as: "data",
in: {
"stocks": {
$map: {
input: "$$data.stocks",
as: "stock",
in: {
"productId": "$$stock.productId",
"userId": "$$data.userId",
"quantity": "$$stock.quantity",
"lastDate": "$$data.date"
}
}
}
}
}
}
}
},
{
$unwind: "$root"
},
{
$replaceRoot: {
newRoot: "$root"
}
},
{
$unwind: "$stocks"
},
{
$sort: {
"stocks.lastDate": 1
}
},
{
$group: {
_id: "$stocks.productId",
totalQuantity: {
$last: "$stocks.quantity"
},
stocks: {
$last: "$stocks"
}
}
},
{
$addFields: {
stocks: [
{
"lastDate": "$stocks.lastDate",
"quantity": "$stocks.quantity",
"userId": "$stocks.userId"
}
]
}
}
])
MongoPlayground