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

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.

Related

MongoDB Select By Group along with that Count Unique match exclude array and object fields Get data sort by latest objects

I have a collection where from the backend user can input multiple same name bikes but with different registration number but in front-End I want them to be grouped by matching the same name but as user updates separately display image changes but I want only one display image as it is 1 vehicle
provided there is a node created I will implement it we can sort it by the latest and take the price and image of it
Activa -2 Count
KTM -1 Count
but there is a catch.
Activa 2 bikes but I want only count 2 and the price as it is the same in an array I want only 1 and the same applies to displayimage here display image file path is different but I want the latest one only Sharing data below
Data:
[
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62e69ee3edfe4d0f3cb4994a",
"bikename": "KTM",
"bikenumber": "KA05HM2034",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-2020-honda-city-exterior-8-1659281111883.jpg",
"file_path": "https://www.example.com/images/upload/bike-2020-honda-city-exterior-8-1659281111883.jpg",
"idx": 1
}
},
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62dba8418ef8f51f454ed757",
"bikename": "Activa",
"bikenumber": "KA05HM2033",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658562557459.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658562557459.jpg",
"idx": 0
}
},
{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62d7ff7e70b9ab38c6ab0cb1",
"bikename": "Activa",
"bikenumber": "KA05HM2223",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"afterfreekmprice": 22,
"descreption": "Activa",
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658322798414.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658322798414.jpg",
"idx": 0
}
}
]
Expected:
[
{
"_id":{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62dba8418ef8f51f454ed757",
"bikename": "Activa",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-v_activa-i-deluxe-1658562557459.jpg",
"file_path": "https://www.example.com/images/upload/bike-v_activa-i-deluxe-1658562557459.jpg",
"idx": 0
}
},
"count": 2
},
{
"_id":{
"price": [
{
"Description": "Hourly",
"Price": "1"
},
{
"Description": "Daily",
"Price": "11"
},
{
"Description": "Monthly",
"Price": "111"
}
],
"_id": "62e69ee3edfe4d0f3cb4994a",
"bikename": "KTM",
"bikebrand": {
"id": 1,
"label": "Honda"
},
"freekm": 234,
"displayimage": {
"file": "bike-2020-honda-city-exterior-8-1659281111883.jpg",
"file_path": "https://www.example.com/images/upload/bike-2020-honda-city-exterior-8-1659281111883.jpg",
"idx": 1
}
}
"count": 1
}
]
You can use the aggregation pipeline,
$sort by _id in descending order
$group by bikename and get the first root document that is latest one in root and count total documents in count
$project to show required documents
db.collection.aggregate([
{ $sort: { _id: -1 } },
{
$group: {
_id: "$bikename",
root: { $first: "$$ROOT" },
count: { $sum: 1 }
}
},
{
$project: {
_id: "$root",
count: 1
}
}
])
Playground
You can use $group for this:
db.collection.aggregate([
{$group: {
_id: "$bikename",
count: {$sum: 1},
data: {$first: "$$ROOT"}
}
},
{$set: {"data.count": "$count"}},
{$replaceRoot: {newRoot: "$data"}}
])
See how it works on the playground example

Get all matching subdocuments and add property from parent document

Let's say i have following documents:
[
{
"key": 1,
"sub": [
{
"id": 4,
"value": 23
},
{
"id": 1,
"value": 24
}
]
},
{
"key": 2,
"sub": [
{
"id": 1,
"value": 92
},
{
"id": 2,
"value": 93
}
]
},
{
"key": 4,
"sub": [
{
"id": 3,
"value": 22
},
{
"id": 2,
"value": 43
}
]
}
]
I now want to find subdocuments by their id and also see the corresponding parent property key. I have tried following query:
db.collection.aggregate([
{
"$match": {
"sub.id": 1
}
},
{
"$addFields": {
"value": {
"$filter": {
"input": "$sub",
"cond": {
$eq: [
"$$this.id",
1
]
}
}
}
}
},
{
"$project": {
sub: 0
}
}
])
This essentially returns the right information:
[
{
"_id": ObjectId("5a934e000102030405000000"),
"key": 1,
"value": [
{
"id": 1,
"value": 24
}
]
},
{
"_id": ObjectId("5a934e000102030405000001"),
"key": 2,
"value": [
{
"id": 1,
"value": 92
}
]
}
]
But it is not formatted how I need it and also adding more properties from the subdocuments is annoying because of the $addFields. I would rather have it formatted like this:
[
{
"id": 1,
"value": 24,
"key": 1
},
{
"id": 1,
"value": 92,
"key": 2
}
]
So I can have just an array of the matching subdocuments with additional parent properties added.
How would I do that?
Mongo Playground
After you have filtered out the unwanted subdocuments, $reduce over the response to build the subdocuments you want to see:
{"$project": {
_id: 0,
"sub": {
$reduce: {
input: {
"$filter": {
"input": "$sub",
"cond": {$eq: ["$$this.id", 1]}
}
},
initialValue: [],
in: {
$concatArrays: [
[{
key: "$key",
id: "$$this.id",
value: "$$this.value"
}],
"$$value"
]
}
}
}
}
}
Playground

Get the $size (length) of a nested array and calculate the difference to a stored value on the parent object - using aggregate

Let's consider that I have the following documents (ignoring the _id):
[
{
"Id": "Store1",
"Info": {
"Location": "Store1 Street",
"PhoneNumber": 111
},
"MaxItemsPerShelf": 3,
"Shelf": [
{
"Id": "Shelf1",
"Items": [
{
"Id": "Item1",
"Name": "bananas"
},
{
"Id": "Item2",
"Name": "apples"
},
{
"Id": "Item3",
"Name": "oranges"
}
]
},
{
"Id": "Shelf2",
"Items": [
{
"Id": "Item4",
"Name": "cookies"
},
{
"Id": "Item5",
"Name": "chocolate"
}
]
},
{
"Id": "Shelf3",
"Items": []
}
]
},
{
"Id": "Store3",
"Info": {
"Location": "Store2 Street",
"PhoneNumber": 222
},
"MaxItemsPerShelf": 2,
"Shelf": [
{
"Id": "Shelf4",
"Items": [
{
"Id": "Item6",
"Name": "champoo"
},
{
"Id": "Item7",
"Name": "toothpaste"
}
]
},
{
"Id": "Shelf5",
"Items": [
{
"Id": "Item8",
"Name": "chicken"
}
]
}
]
}
]
Given a specific Shelf.Id I want to get the following result ( Shelf.Id = "Shelf2"):
[{
"Info": {
"Location": "Store1 Street",
"PhoneNumber": 111
},
"ItemsNumber": 2,
"ItemsRemaining": 1
}]
Therefore:
ItemsNumberis the $size of Shelf
and
ItemsRemainingis equal to MaxItemsPerShelf $size of Shelf
also I want to copy the value of the Info to the aggregate output.
How can I accomplish this with aggregate? On my efforts I couldn't pass through an iterator that gets the $size of $Shelf.Items
You can use below aggregation
db.collection.aggregate([
{ "$match": { "Shelf.Id": "Shelf2" }},
{ "$replaceRoot": {
"newRoot": {
"$let": {
"vars": {
"shelf": {
"$filter": {
"input": {
"$map": {
"input": "$Shelf",
"in": {
"Id": "$$this.Id",
"count": { "$size": "$$this.Items" }
}
}
},
"as": "ss",
"cond": { "$eq": ["$$ss.Id", "Shelf2"] }
}
}
},
"in": {
"Info": "$Info",
"ItemsNumber": { "$arrayElemAt": ["$$shelf.count", 0] },
"ItemsRemaining": {
"$subtract": [
"$MaxItemsPerShelf",
{ "$ifNull": [
{ "$arrayElemAt": ["$$shelf.count", 0] },
0
]}
]
}
}
}
}
}}
])

How to use aggregate for group by in mongodb

I have collection that contains documents with below schema.
Schema
{
"categoryId": "1234",
"sellerId": "2323",
"productId": "121",
"rating": 1
},
{
"categoryId": "1235",
"sellerId": "2323",
"productId": "122",
"rating": -1
},
{
"categoryId": "1234",
"sellerId": "2323",
"productId": "123",
"rating": -1
},
{
"categoryId": "1235",
"sellerId": "2323",
"productId": "124",
"rating": 1
},
{
"categoryId": "1234",
"sellerId": "2323",
"productId": "125",
"rating": 1
},
{
"categoryId": "1234",
"sellerId": "2325",
"productId": "125",
"rating": 1
}
The rating can have values 1 or -1. I want to find all documents grouped by categoryId and sum of the ratings.
Example Result:
{categoryId: 1234, positiveRatingCount: 2, negativeRatingCount: 1}
This is what I have done so far:
ratingsCollection.aggregate(
{
$match: {sellerId: "2323" }
},
{
$group: {
_id: "$categoryId",
count: { $sum: "rating" }
}
}
);
I get the following result. I am able to group by category but not able to figure out to get count of positive and negative ratings.
[
{
"_id": "1234",
"count": 3
},
{
"_id": "1235",
"count": 2
}
]
You need to use $sum with the conditions($cond) where the rating is $gt or $lt then 0
db.collection.aggregate([
{ "$match": { "sellerId": "2323" } },
{ "$group": {
"_id": "$categoryId",
"positiveRatingCount": {
"$sum": { "$cond": [{ "$gt": [ "$rating", 0 ] }, "$rating", 0 ] }
},
"negativeRatingCount": {
"$sum": { "$cond": [{ "$lt": [ "$rating", 0 ] }, "$rating", 0 ] }
}
}}
])
Output
[
{
"_id": "1235",
"negativeRatingCount": -1,
"positiveRatingCount": 1
},
{
"_id": "1234",
"negativeRatingCount": -2,
"positiveRatingCount": 3
}
]

MongoDB projection. Operator $add field|expression array awareness or after $slice

I've got collection that looks like:
[{
"org": "A",
"type": "simple",
"payFor": 3,
"price": 100
},
{
"org": "A",
"type": "custom",
"payFor": 2,
"price": 115
},
{
"org": "B",
"type": "simple",
"payFor": 1,
"price": 110
},
{
"org": "B",
"type": "custom",
"payFor": 2,
"price": 200
},
{
"org": "B",
"type": "custom",
"payFor": 4,
"price": 220
}]
And need to produce result with query to perform group by "org" where payments appears for only first "payFor" prices in "type".
I'm trying to use expression result by $slice operator in $add but this is not works.
pipeline:
[{
"$group": {
"_id": {
"org": "$org",
"type": "$type"
},
"payFor": {
"$max": "$payFor"
},
"count": {
"$sum": 1
},
"prices": {
"$push": "$price"
}
}
},
{
"$group": {
"_id": "$_id.org",
"payments": {
"$push": {
"type": "$_id.type",
"forFirst": "$payFor",
"sum": {
"$cond": [
{
"$gte": [
"$payFor",
"$count"
]
},
{
"$add": {
"$prices": {
"$slice": "$count"
}
}
},
{
"$add": "$prices"
}
]
}
}
}
}
}]
I know that it is possible to traverse unwinded prices and pick only "payFor" count of them. but result collections are more rich than in example above and this operation will produce some unecessary overheads.
Need some advice from community. Please. Thanks.