MongoDB - Pivot data down instead of flattening with aggregation query - mongodb

Can we unwind/flatten both old and new arrays and pivote data down like the below examples, consider both arrays might have different sizes and order. Looking for a solution in mongo aggregation query
Ex.1:
JSON/Object
{
"sno": "1001",
"owner": "Tim",
"old": [
{
"name": "John",
"age": "20"
},
{
"name": "Park",
"age": "40"
}
],
"new": [
{
"name": "Snow",
"age": "10"
},
{
"name": "Mike",
"age": "25"
},
{
"name": "New Rec",
"age": "55"
}
]
}
Output
sno owner current_name current_age renew_name renew_age
----------------------------------------------------------------------
1001 Tim John 20 Snow 10
1001 Tim Park 40 Mike 25
1001 Tim New Rec 55
Ex.2:
JSON/Object
{
"sno": "1001",
"owner": "Tim",
"old": [
{
"name": "John",
"age": "20"
},
{
"name": "Park",
"age": "40"
}
],
"new": [
{
"name": "Snow",
"age": "10"
}
]
}
Output
sno owner current_name current_age renew_name renew_age
----------------------------------------------------------------------
1001 Tim John 20 Snow 10
1001 Tim Park 40

$project to show required fields
$range to make array from 0 to total max element size of old or new array
$map to iterate loop of the above range
$arrayElemAt to select the object of the specific element from old and new array
$unwind deconstruct names array
$project to format your result and show required fields
db.collection.aggregate([
{
$project: {
sno: 1,
owner: 1,
names: {
$map: {
input: {
$range: [0,
{
$cond: [
{ $gt: [{ $size: "$old" }, { $size: "$new" }] },
{ $size: "$old" },
{ $size: "$new" }
]
}
]
},
in: {
old: { $arrayElemAt: ["$old", "$$this"] },
new: { $arrayElemAt: [ "$new", "$$this"] }
}
}
}
}
},
{ $unwind: "$names" },
{
$project: {
sno: 1,
owner: 1,
current_name: "$names.old.name",
current_age: "$names.old.age",
renew_name: "$names.new.name",
renew_age: "$names.new.age"
}
}
])
Playground

Related

mongodb update and push array of objects

I have this simple collection of students:
{
"_id": "btv7865reVGlksabv",
"students": [
{
"name": "John",
"age": 30
},
{
"name": "Henry",
"age": 25
}
]
}
Now I want to push new students into this array:
const newStudents = [
{
"name": "Mike",
"age": 22
},
{
"name": "Kim",
"age": 20
}
]
What I tried so far is:
Students.update(
{
"_id": "btv7865reVGlksabv"
},
{
$push: {
"students": newStudents
}
}
);
The above query doesn't update my collection for some reason. Can anyone help me correct this query?
Chain up $push with $each
db.collection.update({
"_id": "btv7865reVGlksabv"
},
{
$push: {
"students": {
$each: [
{
"name": "Mike",
"age": 22
},
{
"name": "Kim",
"age": 20
}
]
}
}
})
Mongo Playground
Maybe something like this:
db.collection.update({},
[
{
$addFields: {
students: {
$concatArrays: [
"$students",
[
{
name: "New1",
age: "New1"
},
{
name: "New2",
age: "New2"
}
]
]
}
}
}
])
Explained:
Use $addFileds->$concatArrays to update via aggregation pipeline ( 4.2+) to add the elements from your new array to the already existing array ...
Playground

Query maximum N records of each group base on a condition in MongoDB?

I have a question regarding querying data in MongoDB. Here is my sample data:
{
"_id": 1,
"category": "fruit",
"userId": 1,
"name": "Banana"
},
{
"_id": 2,
"category": "fruit",
"userId": 2,
"name": "Apple"
},
{
"_id": 3,
"category": "fresh-food",
"userId": 1,
"name": "Fish"
},
{
"_id": 4,
"category": "fresh-food",
"userId": 2,
"name": "Shrimp"
},
{
"_id": 5,
"category": "vegetable",
"userId": 1,
"name": "Salad"
},
{
"_id": 6,
"category": "vegetable",
"userId": 2,
"name": "carrot"
}
The requirements:
If the category is fruit, returns all the records match
If the category is NOT fruit, returns maximum 10 records of each category grouped by user
The category is known and stable, so we can hard-coded in our query.
I want to get it done in a single query. So the result expected should be:
{
"fruit": [
... // All records of
],
"fresh-food": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "fresh-food"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "fresh-food"
]
},
...
],
"vegetable": [
{
"userId": 1,
"data": [
// Top 10 records of user 1 with category = "vegetable"
]
},
{
"userId": 2,
"data": [
// Top 10 records of user 2 with category = "vegetable"
]
},
]
}
I've found the guideline to group by each group using $group and $slice, but I can't apply the requirement number #1.
Any help would be appreciated.
You need to use aggregation for this
$facet to categorize incoming data, we categorized into two. 1. Fruit and 2. non_fruit
$match to match the condition
$group first group to group the data based on category and user. Second group to group by its category only
$objectToArray to make the object into key value pair
$replaceRoot to make the non_fruit to root with fruit
Here is the code
db.collection.aggregate([
{
"$facet": {
"fruit": [
{ $match: { "category": "fruit" } }
],
"non_fruit": [
{
$match: {
$expr: {
$ne: [ "$category", "fruit" ]
}
}
},
{
$group: {
_id: { c: "$category", u: "$userId" },
data: { $push: "$$ROOT" }
}
},
{
$group: {
_id: "$_id.c",
v: {
$push: {
uerId: "$_id.u",
data: { "$slice": [ "$data", 3 ] }
}
}
}
},
{ $addFields: { "k": "$_id", _id: "$$REMOVE" } }
]
}
},
{ $addFields: { non_fruit: { "$arrayToObject": "$non_fruit" } }},
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$$ROOT", "$non_fruit" ]
}
}
},
{ $project: { non_fruit: 0 } }
])
Working Mongo playground

How to group data and get and get all the field back in mongodb [duplicate]

This question already has an answer here:
How to group documents with specific field in aggregation of mongodb
(1 answer)
Closed 1 year ago.
I have collection in a mongodb and want to group invoice by modified and get all the fields in those objects.
{
modified: "11/02/2020",
stocks: [
{
product:{
name:"Milk",
price: 20
}
quantity: 2,
paid: true
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Sugar",
price: 50
}
quantity: 1,
paid: false
}
]
},
{
modified: "10/02/2020",
stocks: [
{
product:{
name:"Butter",
price: 10
}
quantity: 5,
paid: false
}
]
}
So I tried:
db.collection.aggregate([{
$group: {
_id: "$modified",
records: { $push: "$$ROOT" }
}
}
])
But It reaggregate on the modifier field being pushed into the records generating duplicates
Demo - https://mongoplayground.net/p/4AGuo5zfF4V
Use $group
db.collection.aggregate([
{
$group: { _id: "$grade", records: { $push: "$$ROOT" } }
}
])
Output
[
{
"_id": "A",
"records": [
{
"_id": ObjectId("5a934e000102030405000000"),
"grade": "A",
"name": "John",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000001"),
"grade": "A",
"name": "John1",
"subject": "English"
}
]
},
{
"_id": "B",
"records": [
{
"_id": ObjectId("5a934e000102030405000002"),
"grade": "B",
"name": "JohnB",
"subject": "English"
},
{
"_id": ObjectId("5a934e000102030405000003"),
"grade": "B",
"name": "JohnB1",
"subject": "English"
}
]
}
]

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

MongoDB multiple counts, single document, arrays

I have been searching on stackoverflow and cannot find exactly what I am looking for and hope someone can help. I want to submit a single query, get multiple counts back, for a single document, based on array of that document.
My data:
db.myCollection.InsertOne({
"_id": "1",
"age": 30,
"items": [
{
"id": "1",
"isSuccessful": true,
"name": null
},{
"id": "2",
"isSuccessful": true,
"name": null
},{
"id": "3",
"isSuccessful": true,
"name": "Bob"
},{
"id": "4",
"isSuccessful": null,
"name": "Todd"
}
]
});
db.myCollection.InsertOne({
"_id": "2",
"age": 22,
"items": [
{
"id": "6",
"isSuccessful": true,
"name": "Jeff"
}
]
});
What I need back is the document and the counts associated to the items array for said document. In this example where the document _id = "1":
{
"_id": "1",
"age": 30,
{
"totalIsSuccessful" : 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1,
"totalNameNull": 2
}
}
I have found that I can get this in 4 queries using something like this below, but I would really like it to be one query.
db.test1.aggregate([
{ $match : { _id : "1" } },
{ "$project": {
"total": {
"$size": {
"$filter": {
"input": "$items",
"cond": { "$eq": [ "$$this.isSuccessful", true ] }
}
}
}
}}
])
Thanks in advance.
I am assuming your expected result is invalid since you have an object literal in the middle of another object and also you have totalIsSuccessful for id:1 as 2 where it seems they should be 3. With that said ...
you can get similar output via $unwind and then grouping with $sum and $cond:
db.collection.aggregate([
{ $match: { _id: "1" } },
{ $unwind: "$items" },
{ $group: {
_id: "_id",
age: { $first: "$age" },
totalIsSuccessful: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalNotIsSuccessful: { $sum: { $cond: [{ "$ne": [ "$items.isSuccessful", true ] }, 1, 0 ] } },
totalSuccessfulNull: { $sum: { $cond: [{ "$eq": [ "$items.isSuccessful", null ] }, 1, 0 ] } },
totalNameNull: { $sum: { $cond: [ { "$eq": [ "$items.name", null ]}, 1, 0] } } }
}
])
The output would be this:
[
{
"_id": "_id",
"age": 30,
"totalIsSuccessful": 3,
"totalNameNull": 2,
"totalNotIsSuccessful": 1,
"totalSuccessfulNull": 1
}
]
You can see it working here