I need query to fetch the total count as per key value of document by matching some value from array using MongoDB. I am explaining my document and Input below.
dataArr=[
{'login_id':9937229853,'location':'Delhi'},
{'login_id':9937229854,'location':'JK'}
]
My document is given below.
feedback:
{
login_id:9937229853,
code: PTP,
remark:'Hello'
},
{
login_id:9937229853,
code: PTP,
remark:'Hii'
},
{
login_id:9937229853,
code: CB,
remark:'aaaaa'
},
{
login_id:9937229854,
code: PTP,
remark:'jjjjj'
},
{
login_id:9937229854,
code: CB,
remark:'dddd'
}
The above is my collection. Here I need as per user input login_id present inside array will match with document and the total count will be fetch as per document key and value. My expected output is given below. I am explaining my code below.
for(var i=0;i<dataArr.length;i++){
var login=dataArr[i]['login_id'];
//console.log('cdocs',dataArr[i]['login_id']);
Feedback.collection.count({login_id:dataArr[i]['login_id']},function(cerr,cdocs){
console.log('cdocs',login);
if (!cerr) {
if(cdocs > 0){
// console.log('login',cdocs);
db.collection.aggregate([
{
$match: {
keywords: { $not: {$size: 0} }
}
},
{ $unwind: "$keywords" },
{
$group: {
_id: {$toLower: '$keywords'},
count: { $sum: 1 }
}
},
{
$match: {
login_id: login
}
}
])
.toArray((err,docs)=>{
if (!err) {
// console.log('count::',docs);
finalArr=docs;
}
})
}
}
})
}
var data={'status':'success','data':finalArr}
res.send(data);
I need the expected result like below.
finalArr=[
{'login_id':9937229853,'location':'Delhi','PTP':2,'CB':1,'remark':3},
{'login_id':9937229854,'location':'JK','PTP':1,'CB':1,'remark':2},
]
But using my code I am getting the blank output. Please help me to resolve this issue.
You can do all this with a single aggregate operation. The first pipeline stage would be filtering the documents in the collection using the input array. You would need to map that array to just a list of ids though in order to use the $in query operator i.e.
const ids = dataArr.map(({ login_id }) => login_id)
which can then be used in the $match pipeline as
const match = { '$match': { 'login_in': { '$in': ids } } }
The next pipeline step will then use the $group stage to group the above filtered documents by the login_id key
const allGroup = { '$group': {
'_id': {
'login_id': '$login_id',
'code': '$code',
'remark': '$remark'
},
'count': { '$sum': 1 }
} }
Another $group pipeline stage to get the remarks counts as a list of key/value documents
const remarksGroup = { '$group': {
'_id': {
'login_id': '$_id.login_id',
'code': '$_id.code'
},
'remarks': {
'$push': {
'k': '$_id.remark',
'v': '$count'
}
},
'count': { '$sum': 1 }
} }
Get the code counts with a similar structure as above
const codeGroup = { '$group': {
'_id': '$_id.login_id',
'codes': {
'$push': {
'k': '$_id.code',
'v': '$count'
}
},
'remarks': { '$first': '$remarks' }
} }
You would need a final pipeline to convert the key/value pairs arrays to objects using $arrayToObject, merge the objects into one using $mergeObjects and replace the root document with the merged docs using $replaceRoot:
const projections = { '$replaceRoot': {
'newRoot': {
'$mergeObjects': [
{ 'login_id': '$_id' },
{ '$arrayToObject': '$codes' },
{ '$arrayToObject': '$remarks' }
]
}
} }
Your full aggregate pipeline operation would be:
(async () => {
try {
const ids = dataArr.map(({ login_id }) => login_id)
const match = { '$match': { 'login_in': { '$in': ids } } }
const allGroup = { '$group': {
'_id': {
'login_id': '$login_id',
'code': '$code',
'remark': '$remark'
},
'count': { '$sum': 1 }
} }
const remarksGroup = { '$group': {
'_id': {
'login_id': '$_id.login_id',
'code': '$_id.code'
},
'remarks': {
'$push': {
'k': '$_id.remark',
'v': '$count'
}
},
'count': { '$sum': 1 }
} }
const codeGroup = { '$group': {
'_id': '$_id.login_id',
'codes': {
'$push': {
'k': '$_id.code',
'v': '$count'
}
},
'remarks': { '$first': '$remarks' }
} }
const projections = { '$$replaceRoot': {
'newRoot': {
'$mergeObjects': [
{ 'login_id': '$_id' },
{ '$arrayToObject': '$codes' },
{ '$arrayToObject': '$remarks' }
]
}
} }
const result = await Feedback.aggregate([
match,
allGroup,
remarksGroup,
codeGroup,
projections
])
/* get the location key */
const data = result.map(item => {
const [{ location }, ...rest] = dataArr.filter(d => d.location_id === item.location_id)
return { location, ...item }
})
console.log(data)
res.send(data)
} catch (err) {
// handle error
}
})()
Related
I have a usecase to find the count of different statues like active, in-active, in-progress, etc,
the documents look like this -
{
"id": "1"
"status": "active"
},
{
"id": "2"
"status": "active"
},
{
"id": "3"
"status": "in-active"
},
{
"id": "4"
"status": "in-progress"
}
I needed output like -
{
"active": 2,
"in-active": 1,
"in-progress": 1
}
I am referring this answer but, not able to get the expected output -
Mongo count occurrences of each value for a set of documents
My code is as follows -
const mongoClient = require('mongodb').MongoClient;
const test = async () => {
const mongoUri = "mongodb://localhost:27017/";
const dbClientConnection = await mongoClient.connect(mongoUri, {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = await dbClientConnection.db("database name here");
const collection = await db.collection("collection name here");
let result = await collection.aggregate([
{
$group: {
_id: "$status",
sum: { $sum: 1 }
}
},
{
$group: {
_id: null,
status: {
$push: { k: "$_id", v: "$sum" }
}
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$status" }
}
}
])
console.log("result => ", result);
return result;
}
test();
The first stage is correct
$group by null and construct the array of key and value format
$arrayToObject convert above converted key-value pair array to an object
$replaceRoot to replace above object to root
let result = await collection.aggregate([
{
$group: {
_id: "$status",
sum: { $sum: 1 }
}
},
{
$group: {
_id: null,
status: {
$push: { k: "$_id", v: "$sum" }
}
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$status" }
}
}
])
Playground
I am using Mongo 5.0.6 and have a document structured like this:
[
{
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
I need to query data based on the properties keys, this works fine in my mongo playground: https://mongoplayground.net/p/lEiLeStWGTn when I query for key that contain mark.
db.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
])
However when I use it through my mongoose object I get [] even so the data is exactly as in playground. I am using locally the latest mongoose6 and mondodb5.0.6. I get the same [] result when I run the query against a mongodb.com hosted database. What could be the problem?
My javascript query below, I tried both using mongoose and the driver directly, as shown below:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
},
{
$addFields: {
filtered: {
$filter: {
input: {
$objectToArray: "$properties"
},
cond: {
$regexMatch: {
input: "$$this.k",
regex: "mark"
}
}
}
}
}
},
{
$project: {
_id: 0,
properties: {
$arrayToObject: "$filtered"
}
}
}
]);
for await (const doc of data) {
console.log(doc);
}
Always gives me:
{ properties: {} }
When I take out the $addFields and $project like this:
const data= User.collection.aggregate([
{
$match: {
username: "admin"
}
}
]);
for await (const doc of data) {
console.log(doc);
}
I get, so the data is there, but aggregation pipeline isn't working:
[
{
_id: "61b9f2f5d2a6021365aae6d6",
username: "admin",
properties: {
bookmarks: {
value: [
1,
2,
3
]
},
landmark: {
value: [
"home"
]
},
other: {
value: "should not show"
}
}
}
]
So the data is there. What am I missing? Do I need to write the query differently?
Example of the document:
{
postId:'232323',
post:'This is my first post',
commentsOnPost:[
{
commentId:'232323_8888',
comment:'Congrats',
repliesOnPost:[
{
replyId:'232323_8888_66666',
reply:'Thanks',
likesOnReply:['user1','user5','user3'],
}
]
}
]
}
I want to add userid in likesOnReply if users do not exist in likesOnReply, similarly remove userid from likesOnReply if exist.
I have tried like this but not working properly
await collection('post').findOneAndUpdate(
{
postId: postId,
'commentsOnPost.commentId': commentId,
'commentsOnPost.repliesOnPost.replyId': replyId
},
{
$push: { 'commentsOnPost.$[].repliesOnPost.$.likes': userid },
},
);
There is no straight way to do both the operation to pull or push in a single query,
There are 2 approaches,
1) Find and update using 2 queries:
use arrayFilters to updated nested array elements
$push to insert element
$pull to remove element
var post = await collection('post').findOne({
posted: postId,
ommentsOnPost: {
$elemMatch: {
commentId: commentId,
repliesOnPost: {
$elemMatch: {
replyId: replyId
likesOnReply: userid
}
}
}
}
});
var updateOperator = "$push";
// FOUND USER ID THEN DO REMOVE OPERATION
if (post) updateOperator = "$pull";
// QUERY
await collection('post').updateOne(
{ postId: postId },
{
[updateOperator]: {
"commentsOnPost.$[c].repliesOnPost.$[r].likesOnReply": userid
}
},
{
arrayFilters: [
{ "c.commentId": commentId },
{ "r.replyId": replyId }
]
}
)
Playground
2) Update with aggregation pipeline starting from MongoDB 4.2:
$map to iterate loop of commentsOnPost array check condition if commentId match then go to next process otherwise return existing object
$mergeObjects to merge current object with updated fields
$map to iterate loop of repliesOnPost array and check condition if replyId match then go to next process otherwise return an existing object
check condition for likesOnReply has userid then do remove using $filter otherwise insert using $concatArrays
await collection('post').findOneAndUpdate(
{ postId: "232323" },
[{
$set: {
commentsOnPost: {
$map: {
input: "$commentsOnPost",
in: {
$cond: [
{ $eq: ["$$this.commentId", commentId] },
{
$mergeObjects: [
"$$this",
{
repliesOnPost: {
$map: {
input: "$$this.repliesOnPost",
in: {
$cond: [
{ $eq: ["$$this.replyId", replyId] },
{
$mergeObjects: [
"$$this",
{
likesOnReply: {
$cond: [
{ $in: [userid, "$$this.likesOnReply"] },
{
$filter: {
input: "$$this.likesOnReply",
cond: { $ne: ["$$this", userid] }
}
},
{
$concatArrays: ["$$this.likesOnReply", [userid]]
}
]
}
}
]
},
"$$this"
]
}
}
}
}
]
},
"$$this"
]
}
}
}
}
}]
)
Playgorund
I'm querying my MongoDB database and don't understand why I am getting an aggregator cursor as a result when I expect to be returned a single number. Maybe I need to get something from the cursor object? Just can't figure out what.
module.exports = CalculateAvg = async collection => {
try {
// const count = await collection.countDocuments({ word: "Hello" });
// console.log(count) // logs 140, which shows that it is accessing the db correctly
const cursor = await collection.aggregate([
{ $match: { word: "Hello" } },
{
$group: {
_id: null,
mean: {
$avg: "$value" // in the dataset, each doc has a value field which equals a number
}
}
}
]);
console.log(cursor) // logs a large AggregationCursor object, rather than a number
} catch (err) {
console.log(err);
}
};
It's because aggregate return value is aggregateCursor, I recommend checking the Mongo's Nodejs driver types file whenever you're not sure whats the return value or the parameter value for any of these functions is.
You want to use cursor toArray like so:
const cursor = await collection.aggregate([
{ $match: { word: "Hello" } },
{
$group: {
_id: null,
mean: {
$avg: "$value" // in the dataset, each doc has a value field which equals a number
}
}
}
]).toArray();
You should use next() method... For Example
const pipeline = [{
$facet: {
total: [{
$count: 'createdAt'
}],
data: [{
$addFields: {
_id: '$_id'
}
}],
},
},
{
$unwind: '$total'
},
{
$project: {
data: {
$slice: ['$data', skip, {$ifNull: [limit,'$total.createdAt']} ]
},
meta: {
total: '$total.createdAt',
limit: {
$literal: limit
},
page: {
$literal: ((skip/limit) + 1)
},
pages: {
$ceil: {
$divide: ['$total.createdAt', limit]
}
}
}
}
}];
const document = await collection.aggregate(pipeline);
const yourData = await document.next();
So, I have the following query on my routes page:
const testeEa = atendimentos.aggregate([
{$group : {_id: "$id_atendente", Idcount:{$sum:1}}},
{$sort: {_id: 1}},
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
},
//'eatest': {'$push': "$$ROOT"}
} }
]).exec();
What I want to do is: This Idcount is counting how many times id_atendente repeats. I need this to check out how many support calls each person answered.
After this is done, I need to check all the support calls with the 'EA' status.
I have 351 calls with the 'EA' status, and I would like to see who is with this status on the support call.
I guess that I'm missing something on the second $group, I just don't what it is.
This eatest is supposed to be the key that will be used on the view.
By the way, I managed to do a query where I can get the number of support calls per id, I need almost the same thing, the difference is that I only need the ones with the 'EA' status.
EDIT 1
const counts = atendimentos.aggregate([
{ '$group': {
'_id': null,
'fin': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'F' ] }, 1, 0 ]
}
},
'ea': {
'$sum': {
'$cond': [ { '$eq': [ '$status', 'EA' ] }, 1, 0 ]
}
}
} }
]).exec()
//Faz uma consulta no mongo e guarda o resultado
//na variável monthly
const monthly = atendimentos.aggregate([
{ '$group': {
'_id': {
'year': { '$year': '$date' },
'month': { '$month': '$date' }
},
'sum': { '$sum': 1 }
} },
{ '$group': {
'_id': null,
//Chave usada para renderizar os dados
'back': { '$push': '$$ROOT' }
} },
]).exec();
//Verificar quantas vezes um id_atendente se repete, contar e guardar o numero
const testeAt = atendimentos.aggregate([
{$group : {_id: "$id_atendente", Idcount:{$sum:1}}},
{$sort: {_id: 1}},
{ '$group': {
'_id': null,
//Chave usada para renderizar os dados
'test': {'$push': "$$ROOT"}
} },
]).exec();
const atendente = atendimentos.aggregate([
{ '$group' : {
'_id': "$id_atendente",
'Idcount': { '$sum': 1 }
} },
{ '$sort': { '_id': 1 } }
]).exec();
const testeEa = atendimentos.aggregate([
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
}
} }
]).exec();
Promise.all([counts, monthly, testeAt, testeEa]).then(([counts, monthly, testeAt, testeEa]) => {
Notice that the atendente query and the testeAt are almost the same.
What I would like to do is use this testeEa variable to store the returned value of the queries that return the number of 'EA' status per id_atendente.
If I use try catch I can't do it I guess because the testeEa would be inside of it, and I wouldn't be ble to pass it to my array.
The eatest is returning the correct value by the way.
END OF EDIT
EDIT 2
An example of the data that I want, this is the query that it's working for checking number of calls/id.
{
"_id": 42,
"Idcount": 3
},
{
"_id": 43,
"Idcount": 155
},
{
"_id": 46,
"Idcount": 69
},
{
"_id": 47,
"Idcount": 16
},
{
"_id": 48,
"Idcount": 4
},
{
"_id": 49,
"Idcount": 21
},
{
"_id": 50,
"Idcount": 4
},
This is exactly the way that I want, but the difference is that I want only the ones with the 'EA' status.
Idcount would be the number of how many times an id with the 'EA' status appears.
END OF EDIT 2
Thanks in advance!
When executing a pipeline in the aggregation framework, MongoDB pipes operators into each other.
"Pipe" here takes the Linux meaning: the output of an operator becomes the input of the following operator. The result of each operator is a new collection of documents.
So when Mongo executes the above pipeline, the results from the first two steps
{ '$group' : { /* First pipeline step */
'_id': "$id_atendente",
'Idcount': { '$sum': 1 }
} },
{ '$sort': { '_id': 1 } } /* Second pipeline step */
will be an array of documents with the schema (for example):
[
{ _id: 'fuzz', IdCount: 2 },
{ _id: 'foo', IdCount: 9 },
{ _id: 'bar', IdCount: 4 },
....
]
Now when it executes the third pipeline
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
}
} }
the documents from the previous pipeline are piped into this pipeline expecting documents with a field called status which does not exist hence
the results wont be correct.
You need to run multiple aggregate pipelines in parallel and this can only be achieved in a single query with $facet:
Using MongoDB 3.4.4 and above:
atendimentos.aggregate([
{ '$facet': {
'atendente': [
{ '$group' : {
'_id': "$id_atendente",
'Idcount': { '$sum': 1 }
} },
{ '$sort': { '_id': 1 } }
],
'eatest': [
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
}
} }
]
} }
]).exec((err, result) => console.log(result));
Using MongoDB 3.2 and below:
(async () => {
try {
const atendente = await atendimentos.aggregate([
{ '$group' : {
'_id': "$id_atendente",
'Idcount': { '$sum': 1 }
} },
{ '$sort': { '_id': 1 } }
]).exec();
const eatest = await atendimentos.aggregate([
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
}
} }
]).exec();
const data = { atendente, eatest };
console.log(JSON.stringify(data, null, 4));
} catch (err) {
console.error(err);
}
})();
or with Promise API
(() => {
const atendente = atendimentos.aggregate([
{ '$group' : {
'_id': "$id_atendente",
'Idcount': { '$sum': 1 }
} },
{ '$sort': { '_id': 1 } }
]).exec();
const eatest = atendimentos.aggregate([
{ '$group': {
'_id': null,
'eatest': {
'$sum': {
'$cond' : [ { '$eq': ['$status', 'EA'] }, 1, 0]
}
}
} }
]).exec();
Promise.all([atendente, eatest]).then(([ atendente, eatest ]) => {
const data = { atendente, eatest };
console.log(JSON.stringify(data, null, 4));
}).catch((err) => {
console.error(err);
});
})();