Change aggregate result - javascript

Im currently having an almost working aggregate query, that would get the users array, and order the objects there by their score.
But not getting the expected output, for some reason the entire family data is beeing printed again.
How do i fix this?
Executed code:
return Family.aggregate([
// Initial document match (uses index, if a suitable one is available)
{ $match: {name: 'Management'}},
// Expand the scores array into a stream of documents
{ $unwind: '$users' },
// Sort in descending order
{ $sort: {
'users.score': -1
}}]
Current result:
{ _id: 5c8e5c79e55ef42ce4923e0b,
name: 'Management',
time_started: 1552833657354,
location: 1,
isFamily: true,
last_member: 0,
score: 0,
users:
{ userid: '5c852292d1bd911abc4957dc',
joined_date: 1552839246371,
permission: 5,
upgrade: 0,
score: 141,
_id: 5c8e724e6e5e6512447c1a61 },
__v: 0 },
{ _id: 5c8e5c79e55ef42ce4923e0b,
name: 'Management',
time_started: 1552833657354,
location: 1,
isFamily: true,
last_member: 0,
score: 0,
users:
{ userid: '5c8522a96bcca9268c0753fe',
joined_date: 1552833657354,
permission: 6,
upgrade: 0,
score: 32,
_id: 5c8e5c79e55ef42ce4923e0c },
__v: 0 } ]
wanted result:
{
name: 'Management',
time_started: 1552833657354,
location: 1,
isFamily: true,
last_member: 0,
score: 0,
users:
[{ userid: '5c852292d1bd911abc4957dc',
joined_date: 1552839246371,
permission: 5,
upgrade: 0,
score: 141,
_id: 5c8e724e6e5e6512447c1a61 },
__v: 0 },
{ userid: '5c8522a96bcca9268c0753fe',
joined_date: 1552833657354,
permission: 6,
upgrade: 0,
score: 32,
_id: 5c8e5c79e55ef42ce4923e0c },
__v: 0 }
]}

You need to use one more $group stage to reshape the splited array into its original form after $unwind
Family.aggregate([
{ "$match": { "name": "Management" }},
{ "$unwind": "$users" },
{ "$sort": { "users.score": -1 }},
{ "$group": {
"_id": "$_id",
"users": { "$push": "$users" },
"name": { "$first": "$name" },
"time_started": { "$first": "$time_started" },
"isFamily": { "$first": "$isFamily" },
"last_member": { "$first": "$last_member" },
"score": { "$first": "$score" },
}}
])

Related

Getting data from a different collection while performing aggregation on a different collection

I have a collection named Vote that looks like the following:
{
postId: "1",
comment:{
text_sentiment: "positive",
topic: "A"
}
}, // DOC-1
{
postId: "2",
comment:{
text_sentiment: "negative",
topic: "A"
}
}, // DOC-2
{
postId: "3",
comment:{
text_sentiment: "positive",
topic: "B"
}
},..//DOC-3 ..
and a collection named post which looks as follows?
{
_id: "1",
category: "a"
},
{
_id: "2",
category: "b",
}
I want to do an aggregation on this collection such that it returns the following structure. (which also takes into account the other post collection`
[
{
_id: "hash",
topic: "A",
topicOccurance: 2,
sentiment: {
positive: 1,
negative: 1,
neutral: 0
},
postIds: [1,2],
categories: [a,b]
},
..
]
I created the following aggregation:
db.Vote.aggregate([
{
$match: {
surveyId: "e6d38e1ecd",
"comment.topic": {
$exists: 1
},
}
},
{
$group: {
_id: {
topic: "$comment.topic",
text_sentiment: "$comment.text_sentiment"
},
total: {
$sum: 1
},
postIds: {
$push: "$postId"
}
}
},
{
$group: {
_id: "$_id.topic",
total: {
$sum: "$total"
},
text_sentiments: {
$push: {
k: "$_id.text_sentiment",
v: "$total"
}
},
postIds: {
"$push": "$postIds"
}
}
},
{
$project: {
topic: "$_id",
topicOccurance: "$total",
sentiment: {
"$arrayToObject": "$text_sentiments"
},
postIds: {
$reduce: {
input: "$postIds",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$sort: {
"topicOccurance": -1
}
}
])
This works fine but I do not know, how to also get categories which exist in a separate collection named posts. How can I query a different collection while performing aggregation?
A simple $lookup will suffice:
db.Vote.aggregate([
{
$match: {
"comment.topic": {
$exists: 1
},
}
},
{
$group: {
_id: {
topic: "$comment.topic",
text_sentiment: "$comment.text_sentiment"
},
total: {
$sum: 1
},
postIds: {
$push: "$postId"
}
}
},
{
$group: {
_id: "$_id.topic",
total: {
$sum: "$total"
},
text_sentiments: {
$push: {
k: "$_id.text_sentiment",
v: "$total"
}
},
postIds: {
"$push": "$postIds"
}
}
},
{
$project: {
topic: "$_id",
topicOccurance: "$total",
sentiment: {
"$arrayToObject": "$text_sentiments"
},
postIds: {
$reduce: {
input: "$postIds",
initialValue: [],
in: {
$concatArrays: [
"$$value",
"$$this"
]
}
}
}
}
},
{
$sort: {
"topicOccurance": -1
}
},
{
$lookup: {
from: "post",
localField: "postIds",
foreignField: "_id",
as: "categories"
}
},
{
$addFields: {
categories: {
"$setUnion": {
$map: {
input: "$categories",
in: "$$this.category"
}
}
}
}
}
])
Mongo Playground

How to return sorted array from object from MongoDB document?

I want to return an array, which is a property inside my mongo model/document, and I want that array to be sorted.
My MongoDB document looks like:
_id: ObjectID("6248e49c88ff07aedee8c000")
title: "School"
items: [
{
sort: 2,
name: "homework"
},
{
sort: 1,
name: "exam"
},
{
sort: 3,
name: "essay"
},
]
And I'm trying to return:
items: [
{
sort: 1,
name: "exam"
},
{
sort: 2,
name: "homework"
},
{
sort: 3,
name: "essay"
}
]
I have tried aggregation:
app.get("/api/v1/lists/:id", async (req,res) =>{
List.aggregate([{
"$match" :{"_id": req.params.id}
},{
"$unwind" : "$items"
} , {
"$sort" : {"sort": 1}
}
], (err, items)=>{
res.json(items)
})
}
Mongo Playground reference
Since $unwind returns the arrays as objects, we are using the $group to push the objects back into the items array
db.collection.aggregate([
{
$unwind: "$items"
},
{
$sort: {
"items.sort": 1
}
},
{
$group: {
_id: "$_id",
items: {
$push: "$items"
}
}
},
])
Output -
[
{
"_id": 1.212112e+06,
"items": [
{
"name": "exam",
"sort": 1
},
{
"name": "homework",
"sort": 2
},
{
"name": "essay",
"sort": 3
}
]
}
]

How to merge duplicates in an array of objects and sum a specific property in MongoDB?

I am building an URL shortener. So I want to track datewise click count when a URL is visited using the short URL. For example: on 30th January if the URL was visited 3 times by using the short URL it will show the click count 5 but my document looks like this -
{
"_id":{"$oid":"61f6322cd3e3484d97a25e7c"},
"dayWiseClicks":
[{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66ff95fadc3e9f01b0d34"}},
{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66ffd5fadc3e9f01b0d38"}},
{"date":"01/30/2022","dailyClicks":1,"_id":{"$oid":"61f66fff5fadc3e9f01b0d3c"}}]
}
I want my document to look like this:
{
"_id":{"$oid":"61f6322cd3e3484d97a25e7c"},
"dayWiseClicks":
[{"date":"01/30/2022","dailyClicks":3,"_id":{"$oid":"61f66ff95fadc3e9f01b0d34"}}]
}
How can I achieve this using the MongoDB aggregation pipeline?
UPDATE
Here's the full schema:
{
merchant_name: String,
store_id: String,
original_url: String,
url_hash: String,
subdomain: String,
// password: String,
totalClicks: {
type: Number,
default: 0,
},
igClicks: {
type: Number,
default: 0,
},
fbClicks: {
type: Number,
default: 0,
},
directClicks: {
type: Number,
default: 0,
},
dayWiseClicks: [
{
date: {
type: String,
default: moment(new Date()).format("L"),
},
dailyClicks: {
type: Number,
default: 0,
},
},
],
desktopClicks: {
device: { type: String, default: "Desktop" },
clicks: { type: Number, default: 0 },
},
mobileClicks: {
device: { type: String, default: "Mobile" },
clicks: { type: Number, default: 0 },
},
},
{ timestamps: true }
After the aggregation is done I want my document to look like this:
{
"_id": {
"$oid": "61f7a3b83dcebb77b05bd180"
},
"desktopClicks": {
"device": "Desktop",
"clicks": 5
},
"mobileClicks": {
"device": "Mobile",
"clicks": 0
},
"totalClicks": 5,
"igClicks": 0,
"fbClicks": 0,
"directClicks": 5,
"dayWiseClicks": [
{
"date": "01/31/2022",
"dailyClicks": 5,
"_id": {
"$oid": "61f7a3fe5bd4f779cc53f697"
}
}
],
"merchant_name": "Akash DTH",
"store_id": "3333",
"url_hash": "OGZhYTI",
"subdomain": "",
"original_url": "https://akashdth.com/",
"createdAt": {
"$date": "2022-01-31T08:54:16.472Z"
},
"updatedAt": {
"$date": "2022-01-31T09:03:55.925Z"
},
"__v": 0
}
try
https://mongoplayground.net/p/osWlBuYy0NZ
db.collection.aggregate([
{
$unwind: {
"path": "$dayWiseClicks"
}
},
{
$group: {
_id: {
"oid": "$_id",
"date": "$dayWiseClicks.date"
},
"dailyClicks": {
$sum: "$dayWiseClicks.dailyClicks"
},
"originalDayWiseClicks": {
$push: "$dayWiseClicks"
}
}
},
{
"$addFields": {
"dayWiseClicks": [
{
"date": "$_id.date",
"dailyClicks": "$dailyClicks",
"_id": {
"$first": "$originalDayWiseClicks._id"
}
}
]
}
},
{
"$project": {
_id: "$_id.oid",
"dayWiseClicks": 1
}
}
])
If you don't need the dayWiseClicks as nested or the first dayWiseClicks._id
It can be simplified to this:
https://mongoplayground.net/p/Zc9whjiLWdt
db.collection.aggregate([
{
$unwind: {
"path": "$dayWiseClicks"
}
},
{
$group: {
_id: {
"oid": "$_id",
"date": "$dayWiseClicks.date"
},
"dailyClicks": {
$sum: "$dayWiseClicks.dailyClicks"
}
}
},
{
$addFields: {
"_id": "$_id.oid",
"date": "$_id.date",
}
}
])

Mongoose group by an lookup with nested arrays

I have two schema's , first one is Quiz and the other one is quizResults, I want to get aggregated data from the quizResult while doing the lookup in quiz Schema. Below is my Quiz schema:
vidId: {type: Number, required: true},
status: {type: Number, enum: [statusType.quizStatusEnums.LIVE, statusType.quizStatusEnums.DELETED], default: statusType.quizStatusEnums.LIVE},
questions: [
{
questionNum: { type: Number },
questionName: { type: String },
answers: [
{
answerId: { type: String, default: uuid.v4() },
answerName: { type: String },
isCorrect: { type: Boolean },
answerType: { type: Number, enum: [statusType.quizTypeEnums.QUIZ, statusType.quizTypeEnums.SURVEY], default: statusType.quizTypeEnums.QUIZ},
hotspotId: { type: Number },
overlayId: {type: Number},
panelId: {type: String}
}
]
}
],
The Second one is QuizResults: The aggregation query needs to be performed on this collection.
created: {
type: Date,
},
vidId: {
type: Number,
required: true,
},
viewerId: {
type: String,
required: true,
},
quizId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
questionId: {
type: Schema.Types.ObjectId,
ref: 'quiz'
},
answerId: {
type: String,
required: true
},
isCorrect: {
type: Boolean,
default: false,
},
whenAnswered: {
type: Date
},
I want the final aggregated result like that:
[
{
"questionNum": 2,
"questionName": "Which is the best selling record in history ?",
"correct": 10,
"incorrect": 20,
"totalAnswers": 30,
"answers": [
{
"answerId": "123abc",
"answerName": "Thriller Michel Jackson",
"numResponses": 10
},
{
"answerId": "234d",
"answerName": "A kind of Magic Queen",
"numResponses": 10
},
{
"answerId": "432e",
"answerName": "help The Beatles",
"numResponses": 10
}
]
},
{
"questionNum": 1,
"questionName": "What value has the number PI?",
"correct": 5,
"incorrect": 3,
"totalAnswers": 8,
"answers": [
{
"answerId": "111",
"answerName": "3.12",
"numResponses": 0
},
{
"answerId": "222",
"answerName": "3.14",
"numResponses": 5
},
{
"answerId": "333",
"answerName": "3.16",
"numResponses": 3
}
]
}
]
What I tried is :
aggregate([
{ "$match": { "vidId": 8225342, } },
{
"$group": {
"_id": "$questionId",
"Correct": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", true] },
1,
0
]
},
},
"Incorrect": {
"$sum": {
"$cond": [
{ "$eq": ["$isCorrect", false] },
1,
0
]
}
},
}
},
{
"$lookup": {
"from": "quiz",
"let": { "id": "$_id" },
"pipeline": [
{ "$match": { "$expr": { "$in": ["$$id", "$questions._id"] } } },
{ "$unwind": "$questions" },
{ "$match": { "$expr": { "$eq": ["$questions._id", "$$id"] } } },
],
"as": "quizData"
}
},
{ $unwind: '$quizData' },
{ "$project": {
"questionName": "$quizData.questions.questionName",
"questionNum": "$quizData.questions.questionNum",
"Correct": "$Correct",
"Incorrect": "$Incorrect",
"answers": "$quizData.questions.answers" } },
])
I got the results something like that:
{
"_id": "611632305bd3910929b95552",
"questionName": "Which is the best selling record in history?",
"questionNum": 5,
"Correct": 3,
"Incorrect": 0,
"answers": [
{
"answerId": "078f441b-373f-40e9-89e1-04fca0a9fc5d",
"answerType": 0,
"_id": "611632305bd3910929b95553",
"answerName": "Thriller Michel Jackson",
"isCorrect": true,
"hotspotId": 470114,
"overlayId": 3,
"panelId": "12abc"
},
{
"answerId": "644b80fe-5778-46fa-b3a6-1eff5989cdee",
"answerType": 0,
"_id": "611632305bd3910929b95554",
"answerName": "A kind of Magic Queen",
"isCorrect": false,
"hotspotId": 470113,
"overlayId": 4,
"panelId": "12345abc"
},
{
"answerId": "5bde2682-66fe-4c79-a728-aea67f6842a8",
"answerType": 0,
"_id": "611632305bd3910929b95555",
"answerName": "help The Beatles",
"isCorrect": false,
"hotspotId": 470112,
"overlayId": 3,
"panelId": "12abc"
}
]
},
how I can get the Answers array like that:
answers: [
{
answerId: "123abc",
answerName: "Thriller Michel Jackson",
numResponses: 10
},
{
answerId: "234d",
answerName: "A kind of Magic Queen",
numResponses: 10
},
{
answerId: "432e",
answerName: "help The Beatles",
numResponses: 10
}
]
         
You can try to approach it the other way around. Use $lookup to get quizResults filtered and aggregated and then run $map along with $filter to get matching statistics for each answer:
db.quiz.aggregate([
{
$match: { "vidId": 8225342 }
},
{
$lookup: {
from: "quizResults",
pipeline: [
{ $match: { "vidId": 8225342 } },
{
$group: {
_id: "$answerId",
count: { $sum: 1 }
}
}
],
as: "quizResults"
}
},
{
$project: {
_id: 1,
questions: {
$map: {
input: "$questions",
as: "q",
in: {
_id: "$$q._id",
questionName: "$$q.questionName",
questionNum: "$$q.questionNum",
answers: {
$map: {
input: "$$q.answers",
as: "a",
in: {
$mergeObjects: [
"$$a",
{
$let: {
vars: {
fst: {
$first: {
$filter: { input: "$quizResults", cond: { $eq: [ "$$this._id", "$$a._id" ] } }
}
}
},
in: { numResponses: "$$fst.count" }
}
}
]
}
}
}
}
}
}
}
}
])
Mongo Playground

how to group two elements using Mongoose?

I need to create a query using mongoose, which allows me to use the group, to count the elements that are in the STARTED or NOT INITIATED state, and then return the count of the elements that are in each of the states.
This is the json object that I am working with.
[
{
status: "INITIATED",
_id: "6057e0013a3dec1d44a7d152",
group: "dairy",
location: "001",
total_items: 30,
__v: 0
},
{
status: "INITIATED",
_id: "6057e0013a3dec1d44a7d153",
group: "dairy",
location: "002",
total_items: 45,
__v: 0
},
{
status: "INITIATED",
_id: "6057e0013a3dec1d44a7d154",
group: "dairy",
location: "003",
total_items: 12,
__v: 0
},
{
status: "NOT INITIATED",
_id: "6057e0013a3dec1d44a7d155",
group: "dairy",
location: "004",
total_items: 50,
__v: 0
},
{
status: "NOT INITIATED",
_id: "6057e0013a3dec1d44a7d156",
group: "drugs",
location: "005",
total_items: 23,
__v: 0
},
{
status: "NOT INITIATED",
_id: "6057e0013a3dec1d44a7d157",
group: "drugs",
location: "006",
total_items: 76,
__v: 0
}]
For example, from the dairy group I need to return a json with the following structure.
{
'id': null,
'group': 'dairy',
'INITIATED': 3,
'NO INITIATED': 1
}
Because of the dairy group 3 of the elements are in the STARTED state and one of the elements is in the NOT INITIATED state. Another example would be with the group of drugs that should return the following json.
{
'id': null,
'group': 'medications',
'STARTED': 0,
'NOT INITIATED': 2
}
In order to do this I am trying to get the following code to work.
db.collection.aggregate ([
{
"$ group": {
_id: {
source: "$ group",
status: "$ status"
},
count: {
$ sum: 1
}
}
}
])
But I am not very clear on how to use the group to generate the response as I need it.
Ok, so this was trickier than I thought at first, but here's how you could do it:
First, you group your documents by the group field, and sum up the counts for the status field. I'm using the $cond and $eq operators to make sure that the status matches the count field. Finally, you apply $project to get the desired output without the _id field:
db.collection.aggregate([
{ "$group": {
"_id": "$group",
"NOT INITIATED": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "NOT INITIATED" ] }, 1, 0]
}
},
"INITIATED": {
"$sum": {
"$cond": [ { "$eq": [ "$status", "INITIATED" ] }, 1, 0]
}
},
} },
{ "$project": {
"_id": 0,
"group": "$_id",
"INITIATED":1,
"NOT INITIATED":1
} }])
I've created an example on mongoplayground for you: https://mongoplayground.net/p/H-72VzWoeoN

Categories