how to count/sum array on mongodb? - mongodb

I have a collection where every documents has an array named "contacts".
Sample Docs :
{
"_id" : ObjectId("5660c2dfdfdfcba2d47baa2d9"),
"name" : john,
"contacts" : [
{
"name" : "ari",
"phone" : "12341234"
},
{
"name" : "dea",
"phone" : "34234234"
}
]
},
{
"_id" : ObjectId("5660c2dfdfdfcba2d47baa2d9"),
"name" : joni,
"contacts" : [
{
"name" : "budi",
"phone" : "13341234"
},
{
"name" : "ade",
"phone" : "3242343"
},
{
"name" : "are",
"phone" : "64545345"
}
]
}
I want to know get total count of contacts for all docs.
From sample docs output should be 5 contacts
Thank you for helping me.

You can try below query :
1) If contacts array exists in every doc :
db.collection.aggregate([
/** project only needed field contacts with size of array in each doc */
{
$project: {
_id: 0,
contacts: {
$size: "$contacts"
}
}
},
/** group on empty(without any filter) & sum contacts field */
{
$group: {
_id: "",
TotalContacts: {
$sum: "$contacts"
}
}
},
/** Optional projection */
{
$project: {
_id: 0
}
}
])
2) If contacts array field might not exist in every doc, if it exists & not an array then an additional type check has to be done in below $cond :
db.collection.aggregate([
/** group on empty(without any filter) & sum contacts field's size if contacts exists else sum 0, You've can have optional projection as first stage like above query */
{
$group: {
_id: "",
TotalContacts: {
$sum: {
$cond: [
{
"$ifNull": [
"$contacts",
false
]
},
{
$size: "$contacts"
},
0
]
}
}
}
},
/** Optional projection */
{
$project: {
_id: 0
}
}
])
Test : MongoDB-Playground

Related

MongoDB get user which are new today

I am trying to find a user list which is new for day-1. I have written the query to find the users who arrived till the day before yesterday and the list of users arrived yesterday. Now I want minus those data how can I do that in a single aggregate function.
Function to get the list before yesterday
db.chat_question_logs.aggregate([
{
$match : {"createdDate":{$lte: ISODate("2020-04-29T00:00:00Z")}}
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
similarly for the day-1 is as below
db.chat_question_logs.aggregate([
{
$match : {"createdDate":{$gte: ISODate("2020-04-30T00:00:00Z"),$lte: ISODate("2020-05-01T00:00:00Z")}}
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
Result JSON are as below
/* 1 */
{
"_id" : {
"userId" : "2350202241750776"
},
"count" : 1
},
/* 2 */
{
"_id" : {
"userId" : "26291570771793121"
},
"count" : 1
},
/* 3 */
{
"_id" : {
"userId" : "2742872209107866"
},
"count" : 5
},
/* 4 */
{
"_id" : {
"userId" : "23502022417507761212"
},
"count" : 1
},
/* 5 */
{
"_id" : {
"userId" : "2629157077179312"
},
"count" : 43
}
How can I find the difference.
It sounds like what you want is to get all users created yesterday (which is the 28th in this example).
db.chat_question_logs.aggregate([
{
$match : { $and: [
{ "createdDate":{$lt: ISODate("2020-04-29T00:00:00Z")} },
{ "createdDate": {$gte: ISODate("2020-04-28T00:00:00Z") }}
] }
},
{
"$project" :
{
_id : 0,
"userInfo.userId":1
}
},
{
"$group": {
"_id": {userId:"$userInfo.userId"},"count": {$sum : 1}}
}
])
Is this what you want?
Hi found the solution which is below
I used the group and first appearance of the Id and then filter record on date which I wanted.The query is as below
db.chat_question_logs.aggregate([
{
$group:
{
_id: "$userInfo.userId",
firstApprance: { $first: "$createdDate" }
}
},
{
$match : { "firstApprance": { $gte: new ISODate("2020-05-03"), $lt: new ISODate("2020-05-05") } }
}
])

How can I correctly output an array of objects in the reverse order from mongodb?

Comments are saved in an array of objects. How can I correctly output them in reverse order (comments from newest to oldest)?
My db:
{"_id":{"$oid":"5e3032f14b82d14604e7cfb7"},
"videoId":"zX6bZbsZ5sU",
"message":[
{"_id":{"$oid":"5e3032f14b82d14604e7cfb8"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"1"
},
{"_id":{"$oid":"5e3032f14b82d14604e7cfb9"},
"user":{"$oid":"5e2571ba388ea01bcc26bc96"},"text":"2"
},
....
]
My sheme Mongoose:
const schema = new Schema({
videoId: { type: String, isRequired: true },
message: [
{
user: { type: Schema.Types.ObjectId, ref: 'User' },
text: { type: String }
},
]
});
My code:
const userComments = await Comment.find(
{ videoId: req.query.videoId },
{ message: { $slice: [skip * SIZE_COMMENT, SIZE_COMMENT] } }
)
.sort({ message: -1 })
.populate('message.user', ['avatar', 'firstName']);
but sort not working;
thanks in advance!
You can simply use $reverseArray to reverse content of an array.
db.collection.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}
])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
}
]
}
Result :
/* 1 */
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb7"),
"videoId" : "zX6bZbsZ5sU",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "2"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfb8"),
"user" : ObjectId("5e2571ba388ea01bcc26bc96"),
"text" : "1"
}
]
}
/* 2 */
{
"_id" : ObjectId("5e309318d02e05b694b0b25f"),
"videoId" : "zX6bZbsZ5sUNEWWWW",
"message" : [
{
"_id" : ObjectId("5e3032f14b82d14604e7cfd0"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "New"
},
{
"_id" : ObjectId("5e3032f14b82d14604e7cfc9"),
"user" : ObjectId("5e2571ba388ea01bcc26bc87"),
"text" : "Old"
}
]
}
Your Query : You can use native MongoDB's $lookup instead of .populate() , So try below :
Comments.aggregate([
{
$addFields:
{
message: { $reverseArray: "$message" }
}
}, {
$lookup: {
from: "User",
let: { ids: "$message.user" },
pipeline: [
{
$match: { $expr: { $in: ["$_id", "$$ids"] } }
},
{ $project: { avatar: 1, firstName: 1, _id: 0 } }
],
as: "userData"
}
}
])
You're going to want to use one of two things:
The MongoDB aggregation framework
Sorting within the application
If you choose to use the MongoDB aggregation framework, you'll likely want to use the $unwind operation to expand the array into separate documents, then sorting those documents with the $sort operation.
You can see more on how to do that in this ticket:
how to sort array inside collection record in mongoDB
You could also do this within your application by first executing a query, and then sorting each of the arrays in the result set.
Best,

Mongo search Array with ONLY one match in $or

I have an array of objects to search. I want to ensure that ONLY ONE MATCH in a list of elements is returned. The following works to find if ANY of the elements in the array matches:
db.users.find({
$or:[
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f00e' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f02d' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a7d6f1aaf645c94f2cf' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a7b6f1aaf645c94f132' )} } },
{ subscriberOf:{ $elemMatch:{ town: ObjectId( '58252a796f1aaf645c94f02f' )} } }
]
})
However, records can have more than one of these ids, and I want to isolate to ensure that only one of these matches. Please note that these particular 5 matches that we care about are a subset of a list that is over 2,000 strong.
In other words, I need to match ONLY ONE of these - and don't care if they match the other 2,000. That's why I can't use a "subscriberOf.1":{$exists:false}, to weed these out.
I can run multiple queries manually, if I can figure out how - maybe do a specific match on one id, then a $nor on the rest?
Any help would be appreciated.
Please try this :
var inputArr = [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a796f1aaf645c94f00e")
]
db.yourCollectionName.aggregate([{ $addFields: { matchingCount: { $size: { $setIntersection: ["$subscriberOf.town", inputArr] } } } },
{ $match: { matchingCount: 1 } }, {$project : {matchingCount:0}}])
Collection Data :
/* 1 */
{
"_id" : ObjectId("5e0be0f9400289966e8c9bb8"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
/* 2 */
{
"_id" : ObjectId("5e0be106400289966e8c9da2"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02d")
]
}
}
/* 3 */
{
"_id" : ObjectId("5e0be2ab400289966e8ce29b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7d6f1aaf645c94f2cf")
]
}
}
/* 4 */
{
"_id" : ObjectId("5e0be2d7400289966e8cea84"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 5 */
{
"_id" : ObjectId("5e0be2eb400289966e8cee3b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
/* 6 */
{
"_id" : ObjectId("5e0be303400289966e8cf210"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 7 */
{
"_id" : ObjectId("5e0be327400289966e8cf92a"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
Result :
/* 1 */
{
"_id" : ObjectId("5e0be0f9400289966e8c9bb8"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f00e")
]
}
}
/* 2 */
{
"_id" : ObjectId("5e0be106400289966e8c9da2"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02d")
]
}
}
/* 3 */
{
"_id" : ObjectId("5e0be2ab400289966e8ce29b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7d6f1aaf645c94f2cf")
]
}
}
/* 4 */
{
"_id" : ObjectId("5e0be2d7400289966e8cea84"),
"subscriberOf" : {
"town" : [
ObjectId("58252a7b6f1aaf645c94f132")
]
}
}
/* 5 */
{
"_id" : ObjectId("5e0be2eb400289966e8cee3b"),
"subscriberOf" : {
"town" : [
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
Note : Returning only docs where exactly one matches, 6 & 7 are not retained.
Ref : Aggregation-pipeline-operators
MongoDB aggregation offers better solution to achieve that.
First, we extend array into single document with $unwind. Then we apply $matchstage to filter desired id's. In the last stage, we $group with accumulator operator $addToSet to create array with unique values.
db.collection.aggregate([
{
$unwind: "$subscriberOf"
},
{
$match: {
"subscriberOf.town": {
$in: [
ObjectId("58252a796f1aaf645c94f00e"),
ObjectId("58252a796f1aaf645c94f02d"),
ObjectId("58252a7d6f1aaf645c94f2cf"),
ObjectId("58252a7b6f1aaf645c94f132"),
ObjectId("58252a796f1aaf645c94f02f")
]
}
}
},
{
$group: {
_id: "$_id",
subscriberOf: {
$addToSet: "$subscriberOf"
}
}
}
])
MongoPlayground
EDIT: If you want to export the result, follow these steps:
Iterate with .forEach method aggregation cursor to retrieve all values.
Create .js file (for instance export.js) with your aggregation, wrap it inside a function like this:
function exporter(){
db.collection.aggregate(...).forEach(function(doc){
printjson(doc);
}
})
exporter();
Now, call mongo with js file and redirect output into file from cmd / command shell:
path\to\mongo\mongo[.exe] --quiet [-u username -p password] [host:port/]database export.js > export.csv 2>&1

Create new properties based on embedded array elements in MongoDB

I have this kind of document
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"presences" : [
{
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f5"),
"createdAt" : NumberLong(1458751885000)
},
{
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
}
]
}
How can I extract first and last presences element to new properties firstPresence and lastPresence like this?
{
"_id" : ObjectId("573342930348ce88ff1685f3"),
"firstPresence": {
"_id" : ObjectId("573342930348ce88ff1685f2"),
"createdAt" : NumberLong(1458751869000)
},
"lastPresence": {
"_id" : ObjectId("573342930348ce88ff1685f7"),
"createdAt" : NumberLong(1458751894000)
},
"presences" : [
...
]
}
I want to use a query that can be applied to all documents in one time.
You need to $unwind your presences array to do the aggregation. Before grouping you can sort them by createdAt to utilize $first and $last operators.
db.collection.aggregate(
[
{ $unwind: "$presences" },
{ $sort: { "presences.createdAt": 1 } },
{
$group: {
_id: "$_id",
"presences": { $push: "$presences" },
"lastPresence": { $last: "$presences" },
"firstPresence": { $first: "$presences" },
}
},
{ $out : "collection" }
])
Last aggregation pipeline ($out) will replace existing collection.
According to above mentioned description as a solution to it please try executing following aggregate query into MongoDB shell
db.collection.aggregate(
// Pipeline
[
// Stage 1
{
$project: {
first: {
$arrayElemAt: ["$presences", 0]
},
last: {
$arrayElemAt: ["$presences", -1]
},
presences: 1
}
},
]
);

Mongodb count() of internal array

I have the following MongoDB collection db.students:
/* 0 */
{
"id" : "0000",
"name" : "John"
"subjects" : [
{
"professor" : "Smith",
"day" : "Monday"
},
{
"professor" : "Smith",
"day" : "Tuesday"
}
]
}
/* 1 */
{
"id" : "0001",
"name" : "Mike"
"subjects" : [
{
"professor" : "Smith",
"day" : "Monday"
}
]
}
I want to find the number of subjects for a given student. I have a query:
db.students.find({'id':'0000'})
that will return the student document. How do I find the count for 'subjects'? Is it doable in a simple query?
If query will return just one element :
db.students.find({'id':'0000'})[0].subjects.length;
For multiple elements in cursor :
db.students.find({'id':'0000'}).forEach(function(doc) {
print(doc.subjects.length);
})
Do not forget to check existence of subjects either in query or before check .length
You could use the aggregation framework
db.students.aggregate(
[
{ $match : {'_id': '0000'}},
{ $unwind : "$subjects" },
{ $group : { _id : null, number : { $sum : 1 } } }
]
);
The $match stage will filter based on the student's _id
The $unwind stage will deconstruct your subjects array to multiple documents
The $group stage is when the count is done. _id is null because you are doing the count for only one user and only need to count.
You will have a result like :
{ "result" : [ { "_id" : null, "number" : 187 } ], "ok" : 1 }
Just another nice and simple aggregation solution:
db.students.aggregate([
{ $match : { 'id':'0000' } },
{ $project: {
subjectsCount: { $cond: {
if: { $isArray: "$subjects" },
then: { $size: "$subjects" },
else: 0
}
}
}
}
]).then(result => {
// handle result
}).catch(err => {
throw err;
});
Thanks!