Mongodb group and sum two arrays of similar data from aggregate facet - arrays

I am running queries that target different levels of social data and have got this working with some fairly large aggregate pipelines which then run inside a $facet.
The results of the $facet section come out with two arrays I then want to combine. eg.
{
"first": [
{
"_id": {
"name": "one",
"_id": "1"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
1000
]
},
{
"_id": {
"name": "two",
"_id": "2"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
2000
]
}
],
"second": [
{
"_id": {
"name": "1",
"_id": "one"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
1000
]
},
{
"_id": {
"name": "two",
"_id": "2"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
2000
]
}
]
}
I would like to group these results together and add up the totals to end up with
[ {
"_id": {
"name": "1",
"_id": "one"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
2000
]
},{
"_id": {
"name": "2",
"_id": "two"
},
"date": [
"2017-09-07T00:00:00.000Z"
],
"data": [
4000
]
}]
I have a query that groups the individual responses to get them to this level but is missing something that allows me to merge across the two arrays. I have experimented with unwinding the first and second array but haven't been able to find a solution that combines the data across both arrays.
Any help, tips or knowledge greatly appreciated.

Use $concatArrays to merge arrays from $facets output followed by $group with $arrayElemAt to pick the first element for $first and $sum accumulator.
[
{
"$project": {
"combine": {
"$concatArrays": [
"$first",
"$second"
]
}
}
},
{
"$unwind": "$combine"
},
{
"$group": {
"_id": "$combine._id",
"date": {
"$first": {
"$arrayElemAt": [
"$combine.date",
0
]
}
},
"data": {
"$sum": {
"$arrayElemAt": [
"$combine.data",
0
]
}
}
}
}
]

Related

In MongoDB I am searching in Nested Object Array but the result it is showing not appropriate

I am using collection Name "History" having below data
[
{
"_id": {
"$oid": "634a9d1b269c99e9364e8750"
},
"marks": [
{
"results": [
{
"product": "Abc",
"score": 55
}
]
}
]
},
{
"_id": {
"$oid": "634a9fae269c99e9364e8755"
},
"marks": [
{
"results": [
{
"product": "Abc",
"score": 10
},
{
"product": "Xyz",
"score": 5
}
]
}
]
},
{
"_id": {
"$oid": "634a9fae269c99e9364e8756"
},
"marks": [
{
"results": [
{
"product": "Abc",
"score": 8
},
{
"product": "Xyz",
"score": 7
}
]
}
]
},
{
"_id": {
"$oid": "634a9fae269c99e9364e8757"
},
"marks": [
{
"results": [
{
"product": "Abc",
"score": 7
},
{
"product": "Xyz",
"score": 8
}
]
}
]
}
]
My Fetch command is this..
db.History.findOne({"marks.results.product":"Xyz"})
command executes without an error but it shows wrong results..
{
"_id": {
"$oid": "634a9fae269c99e9364e8755"
},
"marks": [
{
"results": [
{
"product": "Abc",
"score": 10
},
{
"product": "Xyz",
"score": 5
}
]
}
]
}
The 1st object in results (product:"Abc") should not display as its not meet the criteria (Product="Xyz")
please correct me and guaid me how to fetch desired data (objects only meet criteria i.e. Product="Xyz" )
Your sample data here is a collection with 4 documents. 3 of these contain product="Xyz" along with other products. findOne finds one document that has product="Xyz" inside it, and return the document as is (without manipulating it).
What you are requesting is to get back only a part of the document - meaning you want to manipulate the (double) nested array results in the returned answer. You can do it using an aggregation pipeline.
One option is to use $unwind for this
db.collection.aggregate([
{$match: {"marks.results.product": "Xyz"}},
{$limit: 1} // if you want only one document to return
{$unwind: "$marks"},
{$unwind: "$marks.results"},
{$match: {"marks.results.product": "Xyz"}}
])
See how it works on the playground example - unwind
Another option is to $map and $filter:
See how it works on the playground example - filter

Performance issue running mongodb aggregation

I need to run a query that joins documents from two collections, I wrote an aggregation query but it takes too much time when running in the production database with many documents. Is there any way to write this query in a more efficient way?
Query in Mongo playground: https://mongoplayground.net/p/dLb3hsJHNYt
There are two collections users and activities. I need to run a query to get some users (from users collection), and also their last activity (from activities collection).
Database:
db={
"users": [
{
"_id": 1,
"email": "user1#gmail.com",
"username": "user1",
"country": "BR",
"creation_date": 1646873628
},
{
"_id": 2,
"email": "user2#gmail.com",
"username": "user2",
"country": "US",
"creation_date": 1646006402
}
],
"activities": [
{
"_id": 1,
"email": "user1#gmail.com",
"activity": "like",
"timestamp": 1647564787
},
{
"_id": 2,
"email": "user1#gmail.com",
"activity": "comment",
"timestamp": 1647564834
},
{
"_id": 3,
"email": "user2#gmail.com",
"activity": "like",
"timestamp": 1647564831
}
]
}
Inefficient Query:
db.users.aggregate([
{
// Get users using some filters
"$match": {
"$expr": {
"$and": [
{ "$not": { "$in": [ "$country", [ "AR", "CA" ] ] } },
{ "$gte": [ "$creation_date", 1646006400 ] },
{ "$lte": [ "$creation_date", 1648684800 ] }
]
}
}
},
{
// Get the last activity within the time range
"$lookup": {
"from": "activities",
"as": "last_activity",
"let": { "cur_email": "$email" },
"pipeline": [
{
"$match": {
"$expr": {
"$and": [
{ "$eq": [ "$email", "$$cur_email" ] },
{ "$gte": [ "$timestamp", 1647564787 ] },
{ "$lte": [ "$timestamp", 1647564834 ] }
]
}
}
},
{ "$sort": { "timestamp": -1 } },
{ "$limit": 1 }
]
}
},
{
// Remove users with no activity
"$match": {
"$expr": {
"$gt": [ { "$size": "$last_activity" }, 0 ] }
}
}
])
Result:
[
{
"_id": 1,
"country": "BR",
"creation_date": 1.646873628e+09,
"email": "user1#gmail.com",
"last_activity": [
{
"_id": 2,
"activity": "comment",
"email": "user1#gmail.com",
"timestamp": 1.647564788e+09
}
],
"username": "user1"
},
{
"_id": 2,
"country": "US",
"creation_date": 1.646006402e+09,
"email": "user2#gmail.com",
"last_activity": [
{
"_id": 3,
"activity": "like",
"email": "user2#gmail.com",
"timestamp": 1.647564831e+09
}
],
"username": "user2"
}
]
I'm more familiar with relational databases, so I'm struggling a little to run this query efficiently.
Thanks!

How can Update a Nested array in mongoDB

let data = [
{
"_id": '101',
"name": 'category_1',
"subcategory": [
{
"_id": '201',
"name": 'subCategory_1'
},
{
"_id": '202',
"name": 'subCategory_2',
"subsubcategory": [
{
"_id": '301',
"name": 'subsubcategory_1'
},
{
"_id": '302',
"name": 'subsubcategory_2'
}
]
}
]
}
]
How can I change subsubcategory name ?

MongoDB: find sum of nested array elements

Let's say I have a Customer document like the following
db.collection.insertOne( {
"customerName": "John Doe",
"orders": [
{
"type": "regular",
"items": [
{
"name": "itemA",
"price": 11.1
},
{
"name": "itemB",
"price": 22.2
}
]
},
{
"type": "express",
"items": [
{
"name": "itemC",
"price": 33.3
},
{
"name": "itemD",
"price": 44.4
}
]
}
]
})
How can I calculate the total price of all orders (111 in this example)?
You can $unwind twice (because nested array) and group using $sum like this:
db.collection.aggregate([
{
"$unwind": "$orders"
},
{
"$unwind": "$orders.items"
},
{
"$group": {
"_id": "$customerName",
"total": {
"$sum": "$orders.items.price"
}
}
}
])
Example here

lookup compare collection data with array in aggregate result in mongo DB

i want to compare collection with array in aggregate result
i have following two collection.
chat collection
chat.tags is a array value in reference key come from the tags collection.
"chat": [
{
"id": "test1",
"tags": [
"AAA",
"BBB",
"CCC",
"AAA"
]
},
{
"id": "test2",
"tags": [
"AAA",
"BBB",
"CCC"
]
}
]
tag collection
"tag": [
{
"id": "1234",
"key": "AAA",
"name": "a"
},
{
"id": "1235",
"key": "BBB",
"name": "b"
},
{
"id": "1236",
"key": "CCC",
"name": "c"
},
{
"id": "1237",
"key": "DDD",
"name": "d"
},
]
i want to result that id is "test1" and unique tags in chat collection.
i want to following result using with mongo aggregate.
Is it possible with from, let, pipeline when using lookup?
[
{
"chat": [
{
"id": "test1",
"setTags": [
"AAA",
"BBB",
"CCC"
]
}
],
"tag": [
{
"id": "1234",
"key": "AAA",
"name": "a"
},
{
"id": "1235",
"key": "BBB",
"name": "b"
},
{
"id": "1236",
"key": "CCC",
"name": "c"
}
]
}
]
please help me.
This can be achieved with a simple $lookup, like so:
db.chat.aggregate([
{
$match: {
id: "test1"
}
},
{
$lookup: {
from: "tag",
localField: "tags",
foreignField: "key",
as: "tagDocs"
}
},
{
$project: {
chat: [
{
id: "$id",
setTags: "$tags"
}
],
tag: "$tagDocs"
}
}
])
Mongo Playground
I didn't fully understand what the output structure you want is but it can easily be changed via a different $project stage.
--- EDIT ---
With Mongo's v3.6 $lookup syntax the pipeline remains the same, just the $lookup stage changes:
{
$lookup: {
from: "tag",
let: {
tagKeys: "$tags"
},
pipeline: [
{
$match: {
$expr: {
$in: [
"$key",
"$$tagKeys"
]
}
}
}
],
as: "tagDocs"
}
},
Mongo Playground

Resources