Node js + Mongodb better way to select count of specific documents - node.js

What would be the better way to select count of users
Will the JavaScript filtering code work fine if the number of users increases?
Using multiple mongodb requests
const db = fastify.mongo.db;
const users_collection = await db.collection('users');
let users = {
registered: {
count: null,
typed_count: {
vk: null,
ok: null,
google: null,
oneclick: null,
},
},
};
users.registered.count = await users_collection.countDocuments();
users.registered.typed_count.vk = await users_collection.countDocuments({ 'social.vk': { $exists: true } });
users.registered.typed_count.ok = await users_collection.countDocuments({ 'social.ok': { $exists: true } });
users.registered.typed_count.google = await users_collection.countDocuments({ 'social.google': { $exists: true } });
users.registered.typed_count.oneclick = await users_collection.countDocuments({ social: { $exists: false } });
Using one mongodb request and javascript filtering
const db = fastify.mongo.db;
const users_collection = await db.collection('users');
let users = {
registered: {
count: null,
typed_count: {
vk: null,
ok: null,
google: null,
oneclick: null,
},
},
};
const data = await (await users_collection.find()).toArray();
users.registered.count = data.length;
users.registered.typed_count.vk = data.filter((obj) => obj.social && obj.social.vk).length;
users.registered.typed_count.ok = data.filter((obj) => obj.social && obj.social.ok).length;
users.registered.typed_count.google = data.filter((obj) => obj.social && obj.social.google).length;
users.registered.typed_count.oneclick = data.filter((obj) => !obj.social).length;

The First Method will take more time as too many network requests are involved
and
The Second Method will take too much of your server's memory (RAM) as all the documents will first be brought into the memory.
So we can reduce both time and memory by using MongoDB aggregation's $group pipeline, which will look something like this
db.collection.aggregate([
{
$group: {
_id: null,
vk: {
$sum: {
$cond: [{ $gt: ["$social.vk", null]}, 1, 0]
}
},
ok: {
$sum: {
$cond: [{ $gt: ["$social.ok", null]}, 1, 0]
}
},
google: {
$sum: {
$cond: [{ $gt: ["$social.google", null]}, 1, 0]
}
},
oneclick: {
$sum: {
$cond: [{ $lte: ["$social", null]}, 1, 0]
}
},
}
}
])
Working Example

Related

mongodb sum of price*quantity

Hey I'm trying to sum of some values but it return empty.
const resp = await Cart.find(
{ userId },
{
totalPrice: {
$sum: { $multiply: ["$cartItems.price" , "$cartItems.quantity"] },
},
}
);
I also tried this.
const resp = await Cart.aggregate([
{
$match: { userId },
},
{
$project: {
totalPrice: {
$sum: { $multiply: ["$cartItems.price", "$cartItems.quantity"] },
},
},
},
]);
When I write $multiply it is return nothing.
do you guys have any idea?
To get sum of values

Mongo occurance count by column

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

aggregate query in mongoose mongodb nodejs

Hi I am trying the below query in my nodejs code
const totalCount = await model.countDocuments({
'createdAt': { $gte: new Date(startDate), $lte: new Date(endDate) },
}).exec();
const activeCount = await model.countDocuments({
'createdAt': { $gte: new Date(startDate), $lte: new Date(endDate) },
'enabled': true,
}).exec();
const inactiveCount = (totalCount - activeCount);
return { totalCount, activeCount, inactiveCount };
Is there any way i can combine the above in a single query using aggregate in mongoose? Kindly guide me to the best solution .
Yes, quite simple using some basic operators, like so:
model.aggregate([
{
$match: {
createdAt: {
$gte: new Date(startDate),
$lte: new Date(endDate)
}
}
},
{
$group: {
_id: null,
totalCount: {
$sum: 1
},
activeCount: {
$sum: {
$cond: [
{
$eq: [
"$enabled",
true
]
},
1,
0
]
}
}
}
},
{
$project: {
_id: 0,
totalCount: 1,
activeCount: 1,
inactiveCount: {
$subtract: [
"$totalCount",
"$activeCount"
]
}
}
}
])
Mongo Playground

mongoose sorting by ascending order

const followingUsers = await User.find({ _id: { $in: foundUser.followings } })
const getFeedData = async() => {
let data = []
for (let user of followingUsers) {
const userPosts = user.posts
for (let post of userPosts) {
const posts = await Post.find({ _id: post })
for (let post of posts) {
const foundPost = await Post.findById(post._id).sort({ createdAt: -1 })
data.push(foundPost)
}
}
}
return data
}
const posts = await getFeedData()
Here is some sample data, imagine there are identical two users
and I want to get their posts and sort them by ascending order, those
two users are the users I follow and I need to get all their posts
and show them on the news feed
"user": [
{
_id: ObjectId("625c053cfdd023e3713b297f"),
email: "user1#yahoo.com",
isAdmin: false,
chats: [],
blockedChats: [],
feedback: [],
plans: [],
posts: [
ObjectId("625c0577fdd023e3713b29c7"),
ObjectId("625c0582fdd023e3713b29f5"),
ObjectId("625c075f8f794ea1fcf6c6af"),
ObjectId("625c4a742db74795a43d5243")
],
opportunities: [],
username: "sam",
createdAt: ISODate("2022-04-17T12:17:01.095Z"),
updatedAt: ISODate("2022-04-17T17:12:20.341Z"),
__v: 4
}
],
"post": [
{
_id: ObjectId("625c0577fdd023e3713b29c7"),
postText: "hi this is sam\r\n",
likes: [],
likesCount: [],
timePosted: ISODate("2022-04-17T12:09:05.535Z"),
postImage: [],
user: ObjectId("625c053cfdd023e3713b297f"),
createdAt: ISODate("2022-04-01T00:00:00.00Z")
},
{
_id: ObjectId("625c075f8f794ea1fcf6c6af"),
postText: "it works !!!",
likes: [],
likesCount: [],
timePosted: ISODate("2022-04-17T12:20:08.794Z"),
postImage: [],
user: ObjectId("625c053cfdd023e3713b297f"),
createdAt: ISODate("2022-04-17T12:26:07.075Z"),
updatedAt: ISODate("2022-04-17T12:26:07.075Z")
}
]
Mongo playground
everything is working okay, except the documents that I'm retrieving back are not in ascending order, it may also be due to the loops, or maybe to perform as less as possible queries to the database, what's the problem here can anyone help?
You can probably achieving the same logic through $lookup.
const getFeedData = async() => {
const foundPost = await User.aggregate([
{
$match: {
_id: {
$in: foundUser.followings
}
}
},
{
"$lookup": {
"from": "post",
"let": {
posts: "$posts"
},
"pipeline": [
{
$match: {
$expr: {
"$in": [
"$_id",
"$$posts"
]
}
}
},
{
"$sort": {
createdAt: -1
}
}
],
"as": "postLookup"
}
},
{
"$unwind": "$postLookup"
},
{
"$replaceRoot": {
"newRoot": "$postLookup"
}
}
])
return foundPost
}
const posts = await getFeedData()
Here is the Mongo playground for your reference.

Why am I getting a AggregationCursor as a result and not an average?

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();

Resources