How to group multiple documents in mongodb aggregation - mongodb

I have a collection named offers and a sample documents are below,
{
"offerId": "3a06d230-5836-44c2-896b-f5bfb6b27a77",
"outlets": {
"storeUuid": "b3da5136-15a4-4593-aabd-4788f7d80f19",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
}"startTime": "2018-04-06T08:03:37.954Z",
"endTime": "2018-04-07T07:35:00.046Z"
},
{
"offerId": "3a06d230-5836-44c2-896b-f5bfb6b27a77",
"outlets": {
"storeUuid": "f18a9a9e-539e-4a9e-b313-d947e2ce76de",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
"startTime": "2018-04-06T08:03:37.954Z",
"endTime": "2018-04-07T07:35:00.046Z"
},
{
"offerId": "e6c1f140-6407-4481-9a18-56789d90f549",
"outlets": {
"storeUuid": "b3cdd08d-f7f5-4544-8279-08489974148c",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
"startTime": "2018-04-05T12:30:37.954Z",
"endTime": "2018-04-08T12:38:00.046Z"
},
{
"offerId": "e6c1f140-6407-4481-9a18-56789d90f549",
"outlets": {
"storeUuid": "09d6fc18-9d5c-4b4f-8de1-c6f555b8a370",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
"startTime": "2018-04-05T12:30:37.954Z",
"endTime": "2018-04-08T12:38:00.046Z"
},
{
"offerId": "e6c1f140-6407-4481-9a18-56789d90f549",
"outlets": {
"storeUuid": "bf71e102-9da1-47b5-81e1-98d27f20bcf4",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
"startTime": "2018-04-05T12:30:37.954Z",
"endTime": "2018-04-08T12:38:00.046Z"
}
I want to group by offerId and result should be
[
{
"offerId": "e6c1f140-6407-4481-9a18-56789d90f549",
"outlet": [
{
"storeUuid": "bf71e102-9da1-47b5-81e1-98d27f20bcf4",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
{
"storeUuid": "09d6fc18-9d5c-4b4f-8de1-c6f555b8a370",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
{
"storeUuid": "b3cdd08d-f7f5-4544-8279-08489974148c",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
}
],
"startTime": "2018-04-05T12:30:37.954Z",
"endTime": "2018-04-08T12:38:00.046Z"
},
{
"offerId": "3a06d230-5836-44c2-896b-f5bfb6b27a77",
"outlet": [
{
"storeUuid": "f18a9a9e-539e-4a9e-b313-d947e2ce76de",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
},
{
"storeUuid": "b3da5136-15a4-4593-aabd-4788f7d80f19",
"location": {
"type": "Point",
"coordinates": [
77,
22
]
}
}
],
"startTime": "2018-04-06T08:03:37.954Z",
"endTime": "2018-04-07T07:35:00.046Z"
}
]
My aggregation query so far,
db.offers.aggregate([
{
$group: {
_id: "$offerId",
outlet: {
$addToSet: "$outlets"
}
}
}
])
Any help would be appreciated

Do it like this
Add a projection of the fields that you want.
Group by your desired field
Create a new propertie and push to it the nested fields
db.getCollection('offers').aggregate([
{ $project : { offerId : 1 , outlets : 1, startTime: 1, endTime: 1 } },
{ $group: {
_id: "$offerId" ,
outlet: {
$push: {
storeUuid : "$outlets.storeUuid",
location: "$outlets.location"
}
},
startTime: { "$first": "$startTime"},
endTime: { "$first": "$endTime"}
}
}
])

$addToSet: Returns an array of all unique values that results from
applying an expression to each document in a group of documents that
share the same group by key. Order of the elements in the output array
is unspecified. can be used for
$push: Returns an array of all values that result from applying an
expression to each document in a group of documents that share the
same group by key.
In Your case $push operator is required:
db.offers.aggregate([
{$unwind:'$outlets'},
{$group:{_id:'$offerId',outlets:{$push:'$outlets'}}}
])
For more:
https://docs.mongodb.com/manual/reference/operator/aggregation/push/#grp._S_push
https://docs.mongodb.com/manual/reference/operator/aggregation/addToSet/
For complete list of Aggregation pipeline operators
https://docs.mongodb.com/manual/reference/operator/aggregation/

Related

mongodb agregate and filter data

I try to filter some results data from mongodb with mongoose in javascript.
This is my json structure:
{
"name": "john",
"firstname": "doe",
"yearold": 22,
"recipes": [
{
"title": "cheesecake",
"data": [
{
"name": "egg",
"label": "Eggs for",
"value": 6,
"unit": "piece"
},
{
"name": "oil",
"label": "Specific oil",
"unit": "oz",
"value": 0.2
},
{
"name": "flour",
"label": "Wholemel flour",
"value": 450,
"unit": "gr"
}
]
},
{
"title": "cake",
"data": [
{
"name": "egg",
"label": "Eggs for",
"value": 6,
"unit": "piece"
},
{
"name": "flour",
"label": "Wholemel flour",
"value": 500,
"unit": "gr"
},
]
}
]
}
In some case i need to return json data with hiding some values. For example I have a list that specifies all the values ​​to hide
hidekeys=["egg"];
and i would like to get this:
{
"name": "john",
"firstname": "doe",
"yearold": 22,
"recipes": [
{
"title": "cheesecake",
"data": [
{
"name": "egg",
"label": "Eggs for",
"value": #######,
"unit": "piece"
},
{
"name": "oil",
"label": "Specific oil",
"unit": "oz",
"value": 0.2
},
{
"name": "flour",
"label": "Wholemel flour",
"value": 450,
"unit": "gr"
}
]
},
{
"title": "cake",
"data": [
{
"name": "egg",
"label": "Eggs for",
"value": #######,
"unit": "piece"
},
{
"name": "flour",
"label": "Wholemel flour",
"value": 500,
"unit": "gr"
},
]
}
]
}
For each recipe i need to hide ingredient value if it is specified in hidekeys.
I tried something with $project and $cond but it doesnt works
Here's a quick way of how to achieve this using $map
const hidekeys = ["egg"];
db.collection.aggregate([
{
$addFields: {
recipes: {
$map: {
input: "$recipes",
as: "recipe",
in: {
$mergeObjects: [
"$$recipe",
{
data: {
$map: {
input: "$$recipe.data",
as: "datum",
in: {
"$mergeObjects": [
"$$datum",
{
$cond: [
{
"$setIsSubset": [
[
"$$datum.name"
],
hidekeys
]
},
{
value: "#####"
},
{
value: "$$datum.value"
}
]
}
]
}
}
}
}
]
}
}
}
}
}
])
Mongo Playground

MongoDB - Way to find closest results from arrays inside every document

I want to find closest result from array inside document for every doc, and project it new object using MongoDB. It will be easier to explain what I trying to do by example:
Doc schema:
{
"id": "string",
"name": "string",
"track" : [
{
"time": "number",
"distance": "number"
}
]
}
EXAMPLE:
I want to find closest results for every doc for time equals 4
Input data:
[
{
"id": "1",
"name": "test1",
"track" : [
{
"time": 0,
"distance": 0
},
{
"time": 1,
"distance": 5
},
{
"time": 3,
"distance": 17
},
{
"time": 4,
"distance": 23
},
{
"time": 6,
"distance": 33
}
]
},
{
"id": "2",
"name": "test2",
"track" : [
{
"time": 0,
"distance": 0
},
{
"time": 1,
"distance": 5
},
{
"time": 2,
"distance": 12
},
{
"time": 4,
"distance": 26
},
{
"time": 6,
"distance": 32
}
]
},
{
"id": "3",
"name": "test3",
"track" : [
{
"time": 0,
"distance": 0
},
{
"time": 1,
"distance": 5
},
{
"time": 3,
"distance": 12
}
]
}
]
Output data:
[
{
"id": "1",
"result" : {
"time": 4,
"distance": 23
}
},
{
"id": "2",
"result" : {
"time": 4,
"distance": 26
}
},
{
"id": "3",
"result" : {
"time": 3,
"distance": 12
}
}
]
Is it possible to do this using MongoDB?
db.collection.aggregate([
{
"$addFields": {
"tracks": {
"$filter": {
"input": "$track",
"as": "track",
"cond": {
"$lte": [
"$$track.time",
4
]
}
}
}
}
},
{
"$addFields": {
"tracks": {
"$slice": [
"$tracks",
-1
]
}
}
},
{
"$unwind": "$tracks"
},
{
"$project": {
"tracks": 1,
"name": 1
}
}
])
Play
It does below things:
Finds whose track time is <=4 and adds it to an array called items
Then it gets the last element - i.e closer element
Take the element from array - unwind
Projects what is needed.

$geoNear for a list embeded locations and return distance for all location in list

I have an user collection:
{
"name": "David",
"age": 20,
"addresses": [
{
"radius": 10000,
"location": {
"type": "Point",
"coordinates": [106.785299, 20.999999]
}
},
{
"radius": 30000,
"location": {
"type": "Point",
"coordinates": [105.785299, 20.979733]
}
}
]
}
Each user will have one or more address. I want to calculate the distance between these addresses with a point, then using calculated distance to compare with radius of each address. If distance < radius then keep address else remove address from addresses list. I am using below query:
db.collection.aggregrate(
{
"$geoNear": {
"near": {"type": "Point", "coordinates": [ 105.823620, 21.006047 ]},
"distanceField": "distance",
"key": "addresses.location"
}
}
)
But this query only return the distance of nearest address, like this:
{
"name": "David",
"age": 20,
"addresses": [
{
"radius": 10000,
"location": {
"type": "Point",
"coordinates": [105.785299, 20.979733]
}
},
{
"radius": 30000,
"location": {
"type": "Point",
"coordinates": [105.785299, 20.979733]
}
}
],
"distance": 110000 // <--- distance is added here, just for nearest addrest
}
My expected result:
{
"name": "David",
"age": 20,
"addresses": [
{
"radius": 10000,
"location": {
"type": "Point",
"coordinates": [105.785299, 20.979733]
},
"distance": 2000``// <------ add distance here for each addesss`
},
{
"radius": 30000,
"location": {
"type": "Point",
"coordinates": [105.785299, 20.979733]
},
"distance": 30000 // <------ add distance here for each addesss
}
]
}
So next stage I can compare distance with radius and keep proper adddress
Anybody know how to do it ? thanks
You need to store each address in an individual document:
{
"_id" : ObjectId("5ec77d127df107cd889d567d"),
"name" : "David",
"age" : 20,
"addresses" : {
"radius" : 10000,
"location" : {
"type" : "Point",
"coordinates" : [
105.785299,
20.979733
]
}
}
},
{
"_id" : ObjectId("5ec77f7843732e8f9a63bf67"),
"name" : "David",
"age" : 20,
"addresses" : {
"radius" : 30000,
"location" : {
"type" : "Point",
"coordinates" : [
105.795299,
20.989733
]
}
}
}
Now, we perform $geoNear and $group stages:
db.user.aggregate([
{
"$geoNear": {
"near": {
"type": "Point",
"coordinates": [
105.823620,
21.006047
]
},
"distanceField": "distance",
"key": "addresses.location"
}
},
{
"$group": {
"_id": "$name",
"name": {
"$first": "$name"
},
"age": {
"$first": "$age"
},
"addresses": {
"$push": {
"$mergeObjects": [
"$addresses",
{
"distance": "$distance"
}
]
}
}
}
}
])

How to group documents and get the count of documents and also get Avg of sub-documents in mongoDB

How to Group the documents in MongoDB and get the count of it, along with that to get the avg count of subdocuments.
Collection
[{
"type": "FeatureCollection",
"cityName": "Bengaluru",
"features": [
{
"type": "Feature",
"id": 2085,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
},
{
"type": "Feature",
"id": 2085,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
}
]
}
{
"type": "FeatureCollection",
"cityName": "Bengaluru",
"features": [
{
"type": "Feature",
"id": 2095,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
}
]
}
{
"type": "FeatureCollection",
"cityName": "Bengaluru",
"features": [
{
"type": "Feature",
"id": 2035,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
}
]
},
{
"type": "FeatureCollection",
"cityName": "Delhi",
"features": [
{
"type": "Feature",
"id": 2031,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
}
{
"type": "Feature",
"id": 2032,
"properties": {
"countryName": "India",
"continentName": "Asia"
}
}
]
}
...
]
Expected result
[
{
"cityName": "Bengaluru",
"count": 3
"avgFeatures": 1
},
{
"cityName": "Delhi",
"count":1
"avgFeatures": 2
},
]
in the above example count: 1 groups the data according to the cityName. ex: db.mycollection.aggregate({$project: { count: { $size:"$features" }}}).
And avg features nothing but average of features array count by all documents grouped by cityName.
Any kind of help will much be appreciated.
You can check this Mongo PlayGround
db.collection.aggregate({
$group: {
_id: "$cityName",
count: {
$sum: 1
},
sumFeatures: {
$sum: {
$size: "$features"
}
}
}
},
{
$project: {
_id: 1,
count: 1,
avg: {
$floor: {
$divide: [
"$sumFeatures",
"$count"
]
}
}
}
})
this gives the exact result you need:
db.mycollection.aggregate([
{
"$group": {
"_id": "$cityName",
"count": {
"$sum": 1
},
"featureCount": {
"$push": {
"$size": "$features"
}
}
}
},
{
"$project": {
"cityName": "$_id",
"count": "$count",
"avgFeatures": {
"$round": [{
"$divide": [{
"$sum": "$featureCount"
}, "$count"]
},0]
},
"_id": 0
}
}
])

MongoDB Query aggreate/group by and geometry/location/geoNear query

I am trying to fetch the documents with geometry within a certain location, however only want to return a single document per UUID. For this project, in most cases, there are many documents for each UUID that match the $near selector, hence we get many documents with the same UUID.
Can anyone assist with completing the below query so it only returns a single document per uuid (most recent "date")?
db.device.find(
{
location:
{ $near :
{
$geometry: { type: "Point", coordinates: [ -73.9667, 40.78 ] },
$minDistance: 1000,
}
}
}
)
Here's an example of the collection:
{
"_id":ObjectId("5a4f1ff0fc6ded723265e6b0"),
"uuid":"user1",
"date": "2018-01-20 11:58:29.000",
"location":{
"type": "Point",
"coordinates":[
//remove for demo sake
]
}
},
{
"_id":ObjectId("5a62a245ce689f68245450a7"),
"uuid":"user2",
"date": "2018-01-20 11:58:07.000",
"location":{
"type": "Point",
"coordinates":[
//remove for demo sake
]
}
},
{
"_id":ObjectId("5a62a20fce689f7a14648c62"),
"uuid":"user1",
"date": "2018-01-20 11:58:39.000",
"location":{
"type": "Point",
"coordinates":[
//remove for demo sake
]
}
},
{
"_id":ObjectId("5a62a205ce689f7039203923"),
"uuid":"user1",
"date": "2018-01-20 11:58:49.000",
"location":{
"type": "Point",
"coordinates":[
//remove for demo sake
]
}
},
{
"_id":ObjectId("5a62a277ce689f703a3eacb3"),
"uuid":"user2",
"date": "2018-01-20 11:58:59.000",
"location":{
"type": "Point",
"coordinates":[
//remove for demo sake
]
}
}
When performing this kind of heavier operations, you can switch to using an aggregation pipeline.
Using this input:
{
"uuid": "user1",
"date": "2018-01-20 11:58:29.000",
"location": { "type": "Point", "coordinates":[-0.17818, 51.15609] }
},
{
"uuid": "user2",
"date": "2018-01-20 11:58:07.000",
"location": { "type": "Point", "coordinates":[2.35944, 48.72528] }
},
{
"uuid": "user1",
"date": "2018-01-20 11:58:39.000",
"location": { "type": "Point", "coordinates": [1.45414, 43.61132] }
},
{
"uuid": "user1",
"date": "2018-01-20 11:58:49.000",
"location": { "type": "Point", "coordinates":[-6.24889, 53.33306] }
},
{
"uuid": "user2",
"date": "2018-01-20 11:58:59.000",
"location": { "type": "Point", "coordinates":[-3.68248, 40.47184] }
}
Using this index:
db.device.createIndex( { location : "2dsphere" } )
This pipeline should perform what you want:
db.device.aggregate([
{ $match: { location: { $geoWithin: { $centerSphere: [ [ -0.17818, 51.15609 ], 0.1232135647961246 ] } } } },
{ $sort: { "date": -1 } },
{ $group: { _id: { uuid: "$uuid" }, users: { $push: { "uuid": "$uuid", "date": "$date", "location": "$location" } } } },
{ $project: { user: { $arrayElemAt: ["$users", 0] } } }
])
I first adapted the find/$near operator to an aggregation equivalent ($geoWithin/$centerSphere). It matches locations within 0.123 radians (488 kms (0.123*3963.2)).
I then directly sort by date, this way when documents will then be grouped by user, I will be able to easily select the first per user.
I then group by user.
And finally for each user, as I have a value produced by $group which is an array of the user documents (sorted), I just extract the first item of the array with $arrayElemAt.
This produces:
{
"_id" : { "uuid" : "user2" },
"user": {
"uuid": "user2",
"date": "2018-01-20 11:58:07.000",
"location": { "type": "Point", "coordinates": [ 2.35944, 48.72528 ] }
}
}
{
"_id": { "uuid" : "user1" },
"user": {
"uuid": "user1",
"date": "2018-01-20 11:58:49.000",
"location": { "type": "Point", "coordinates": [ -6.24889, 53.33306 ] }
}
}