Get previous and next inside sub array in MongoDB - mongodb

I have a database as below.
[
{
"title": "Data 1",
"slug": "data-1",
"sub_docs": [
{
"name": "Sub Doc 1",
"sub_slug": "sub-doc-1",
"urls": [
"https://example.com/path-1",
]
},
{
"name": "Sub Doc 2",
"sub_slug": "sub-doc-2",
"urls": [
"https://example.com/path-2"
]
},
{
"name": "Sub Doc 3",
"sub_slug": "sub-doc-3",
"urls": [
"https://example.com/path-3",
]
}
]
}
]
When I give data-1 and sub-doc-2 as input, I want to get the following result.
{
"title": "Data 1",
"sub_doc_title": "Sub Doc 2",
"urls": [
"https://example.com/path-2"
],
"prev": {
"name": "Sub Doc 1",
"sub_slug": "sub-doc-1"
},
"next": {
"name": "Sub Doc 3",
"sub_slug": "sub-doc-3"
}
}
I'm a bit of a beginner at MongoDB.
How can I do this query? Do you have an idea?

That's a tough query for beginner, although I still consider myself a novice so maybe there's a better/easy way.
Here's one way you could do it.
db.collection.aggregate([
{
"$match": {
"slug": "data-1" // given argument
}
},
{
"$set": {
"reduceOut": {
"$reduce": {
"input": "$sub_docs",
"initialValue": {
"sub_doc_title": null,
"urls": [],
"prev": null,
"next": null
},
"in": {
"$cond": [
{
"$eq": [
"$$this.sub_slug",
"sub-doc-2" // given argument
]
},
{
"$mergeObjects": [
"$$value",
{
"sub_doc_title": "$$this.name",
"urls": "$$this.urls"
}
]
},
{
"$cond": [
{ "$eq": [ "$$value.sub_doc_title", null ] },
{
"$mergeObjects": [
"$$value",
{
"prev": {
"name": "$$this.name",
"sub_slug": "$$this.sub_slug"
}
}
]
},
{
"$cond": [
{ "$eq": [ "$$value.next", null ] },
{
"$mergeObjects": [
"$$value",
{
"next": {
"name": "$$this.name",
"sub_slug": "$$this.sub_slug"
}
}
]
},
"$$value"
]
}
]
}
]
}
}
}
}
},
{
"$replaceWith": {
"$mergeObjects": [
{
"title": "$title"
},
"$reduceOut"
]
}
}
])
Try it on mongoplayground.net.

Thanks rickhg12hs for your answer. I also found a solution to this question.
Here is my answer;
db.collection.aggregate([
{
"$match": {
"slug": "data-1"
}
},
{
"$addFields": {
"sub_doc_index": {
"$indexOfArray": [
"$sub_docs.sub_slug",
"sub-doc-2"
]
}
}
},
{
"$project": {
"title": 1,
"sub_doc_title": {
"$arrayElemAt": [
"$sub_docs.name",
"$sub_doc_index"
]
},
"urls": {
"$arrayElemAt": [
"$sub_docs.urls",
"$sub_doc_index"
]
},
"prev": {
"$cond": {
"if": {
"$eq": [
{
"$subtract": [
"$sub_doc_index",
1
]
},
-1
]
},
"then": {},
"else": {
"name": {
"$arrayElemAt": [
"$sub_docs.name",
{
"$subtract": [
"$sub_doc_index",
1
]
}
]
},
"slug": {
"$arrayElemAt": [
"$sub_docs.sub_slug",
{
"$subtract": [
"$sub_doc_index",
1
]
}
]
}
},
},
},
"next": {
"name": {
"$arrayElemAt": [
"$sub_docs.name",
{
"$add": [
"$sub_doc_index",
1
]
}
]
},
"slug": {
"$arrayElemAt": [
"$sub_docs.sub_slug",
{
"$add": [
"$sub_doc_index",
1
]
}
]
}
}
}
},
])

Related

mongodb $lookup and match for an array item from inside look up

return the order with the user where purchase id: 123, product id: p123 and user and order shipping.mode =2
db={
"orders": [
{
"_id": ObjectId("62155381877d4300196008ef"),
"shipping": {
"mode": 1
},
"products": [],
"user": ObjectId("6186bd3315a342001bd84f42"),
},
{
"_id": ObjectId("6215569b54cc7f0030c44e0f"),
"shipping": {
"mode": 2
},
"user": ObjectId("6186bd3315a342001bd84f43"),
"products": [
{
"id": "p123"
}
],
}
],
"users": [
{
"_id": ObjectId("6186bd3315a342001bd84f43"),
"shipping": {
"mode": 2
},
"name": "user100",
"purchase": [
{
"id": "123"
},
{
"id": "hjhh"
}
],
}
]
}
https://mongoplayground.net/p/IomR8U7Ard-
db.orders.aggregate([
{
"$lookup": {
"from": "users",
"localField": "user",
"foreignField": "_id",
"as": "user"
}
},
{
"$unwind": "$user"
},
{
"$unwind": "$user.purchase"
},
{
"$match": {
"$and": [
{
"shipping.mode": {
"$gt": 0
}
},
]
}
},
{
"$match": {
"$or": [
{
"$and": [
{
"user.shipping.mode": {
"$eq": 2
}
},
{
"user.purchase.id": {
"$eq": "123"
}
},
{
"$expr": {
"$in": [
"p123",
"$products.id"
]
}
}
]
},
]
}
},
])

mongodb aggregate nested arrays filter not empty

But now I don't know how to filter
I'm aggregating the filtered dataļ¼š
[
{
"_id": "61cea071cfa3c96b9a4d2657",
"name": "Utils",
"children": [
{
"name": "Code",
"_id": "61cebb4e6c4a5c643494d1a1",
"children": [{name:"jahn"}]
},
{
"name": "Image",
"_id": "61ceb8ad6c4a5c643494d11e",
"children": []
}
]
},
{
"_id": "61cea071cfa3c96b9a4d2111",
"name": "Names",
"children": [
{
"name": "que",
"_id": "61cebb4e6c4a5c643494d1a1",
"children": [
]
},
{
"name": "filter",
"_id": "61cebb4e6c4a5c643494d1a1",
"children": [
{name:"jahn"}
]
}
]
},
]
Looking forward to your help
How to filter out children when children are empty and not displayed
Desired result :
[
{
"_id": "61cea071cfa3c96b9a4d2657",
"name": "Utils",
"children": [
{
"name": "Code",
"_id": "61cebb4e6c4a5c643494d1a1",
"children": [
{name:"jahn"}
]
}
]
},
{
"children": [
{
"children": [
{name:"jahn"}
]
}
]
},
]
I want if children in children, if it's empty it doesn't show the whole object
If you tried to filter for the second level children array, you can use $filter.
db.collection.aggregate([
{
$project: {
_id: 1,
name: 1,
children: {
"$filter": {
"input": "$children",
"cond": {
"$ne": [
"$$this.children",
[]
]
}
}
}
}
}
])
Sample Mongo Playground
Note:
"$ne": [
"$$this.children",
[]
]
Can be replaced with:
"$ne": [
{
$size: "$$this.children"
},
0
]

insert multiple objects into nested arrays with condition

I'm a mongo beginner and struggling to insert multiple objects into multiple nested array in one document.
The document looks like this:
[
{
"id": 1,
"name": "myObject",
"sections": [
{
"id": "section1",
"items": [
{
"id": 1,
"name": "item1.1",
"scores": [
{
"userId": 13,
"score": 10
}
]
},
{
"id": 2,
"name": "item1.2",
"scores": [
{
"userId": 66,
"score": 10
}
]
}
]
},
{
"id": "section2",
"items": [
{
"id": 3,
"name": "item2.1",
"scores": [
{
"userId": 13,
"score": 20
}
]
}
]
}
]
}
]
I now want to insert new scores for userId=10 for every item in every section.
The score is of course different for every item.
let's assume scores like this (all for userId=10)
[
{
"sectionId": "section1"
"itemName: "item1.1"
"score: 10,
"userId": 10
},
{
"sectionId": "section1"
"itemName: "item1.2"
"score: 15,
"userId": 10
},
{
"sectionId": "section2"
"itemName: "item2.1"
"score: 33,
"userId": 10
}
]
Added for clarification
the updated document should look like the following.
{
"id": 1,
"name": "myObject",
"sections": [
{
"id": "section1",
"items": [
{
"id": 1,
"name": "item1.1",
"scores": [
{
"userId": 13,
"score": 10
},
{ // <-- newly added score
"userId": 10,
"score": 10
}
]
},
{
"id": 2,
"name": "item1.2",
"scores": [
{
"userId": 66,
"score": 10
},
{ // <- newly added score
"userId": 10,
"score": 15
}
]
}
]
}
// the remaining document is omitted for brevity but the above should also be applied to this sections
so far I have been able to achieve what I want for one single score like this
db.collection.update({
id: 1
},
{
$push: {
"sections.$[item].items.$[score].scores": {
"userId": 10,
"score": 13
},
}
},
{
arrayFilters: [
{
"score.userId": {
$ne: 10
}
},
{
"item.name": {
$eq: "item1.1"
}
}
]
})
This inserts a score for userId=10 in itemName=item1.1 if no score for userId=10 exists.
But I'm struggling on how to insert multiple scores into multiple items.
I saw that you can merge objects together, so maybe this would be an option although it kinda fells like an overkill.
So how can I insert all my scores for the different items in one atomic operation?
EDIT: Added clarification about the desired result.
Query
pipeline update, requires MongoDB >= 4.2
reduce on the data that you want to insert, with initial value the sections
nested 3 maps that always do the same
if its not the key-value i want, keep the old value
else (merge {:newkey (map ...)})
if userId exists updates its score, else insert new userID and score
query assumes that there is a score array even if empty, i mean it
only creates new userId+score, not sections items etc
*you can avoid the set/unset and use driver variables in all the places where data is used
PlayMongo
db.collection.update({},
[
{
"$set": {
"data": [
{
"sectionId": "section1",
"itemName": "item1.1",
"userId": 10,
"score": 20
},
{
"sectionId": "section1",
"itemName": "item1.1",
"userId": 13,
"score": 30
},
{
"sectionId": "section2",
"itemName": "item2.1",
"score": 33,
"userId": 10
}
]
}
},
{
"$set": {
"sections": {
"$reduce": {
"input": "$data",
"initialValue": "$sections",
"in": {
"$let": {
"vars": {
"data": "$$this"
},
"in": {
"$map": {
"input": "$$value",
"in": {
"$cond": [
{
"$ne": [
"$$section.id",
"$$data.sectionId"
]
},
"$$section",
{
"$mergeObjects": [
"$$section",
{
"items": {
"$map": {
"input": "$$section.items",
"in": {
"$cond": [
{
"$ne": [
"$$item.name",
"$$data.itemName"
]
},
"$$item",
{
"$mergeObjects": [
"$$item",
{
"scores": {
"$let": {
"vars": {
"user_exist": {
"$in": [
"$$data.userId",
"$$item.scores.userId"
]
}
},
"in": {
"$cond": [
{
"$not": [
"$$user_exist"
]
},
{
"$concatArrays": [
"$$item.scores",
[
{
"userId": "$$data.userId",
"score": "$$data.score"
}
]
]
},
{
"$map": {
"input": "$$item.scores",
"in": {
"$cond": [
{
"$ne": [
"$$score.userId",
"$$data.userId"
]
},
"$$score",
{
"$mergeObjects": [
"$$score",
{
"score": "$$data.score"
}
]
}
]
},
"as": "score"
}
}
]
}
}
}
}
]
}
]
},
"as": "item"
}
}
}
]
}
]
},
"as": "section"
}
}
}
}
}
}
}
},
{
"$unset": [
"data"
]
}
])

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
]}
]
}
}
}
}
}}
])

Get key value pair result of activities in Mongodb

Get key value pair result of activities. get all activities under first elements term as key.
INPUT
[
{
"_id": "diamond",
"activities": [
[
{
"term": "11",
"sport_name": "football"
}
]
]
},
{
"_id": "topaz",
"activities": [
[
{
"term": "12",
"sport_name": "football"
}
],
[
{
"term": "11",
"sport_name": "football"
},
{
"term": "11",
"sport_name": "hand ball"
}
]
]
}
]
OUTPUT
[
{
"_id": "diamond",
"activities": [
{
"11": [
{
"term": "11",
"sport_name": "football"
}
]
}
]
},
{
"_id": "topaz",
"activities": [
{
"12": [
{
"term": "12",
"sport_name": "football"
}
]
},
{
"11": [
{
"term": "11",
"sport_name": "football"
},
{
"term": "11",
"sport_name": "hand ball"
}
]
}
]
}
]
You can use below aggregation
db.collection.aggregate([
{ "$project": {
"activities": {
"$arrayToObject": {
"$map": {
"input": "$activities",
"in": {
"k": { "$arrayElemAt": ["$$this.term", 0] },
"v": "$$this"
}
}
}
}
}}
])
MongoPlayground