Assuming I have a schema that looks something like this:
{
field: [{
subDoc: ObjectId,
...
}],
...
}
and I have some list of ObjectIds (user input), how would I get a count of those specific ObjectIds? For exmaple, if I have data like this:
[
{field: [ {subDoc: 123}, {subDoc: 234} ]},
{field: [ {subDoc: 234}, {subDoc: 345} ]},
{field: [ {subDoc: 123}, {subDoc: 345}, {subDoc: 456} ]}
]
and the list of ids given by the user is 123, 234, 345, I need to get a count the given ids, so a result approximating this:
{
123: 2,
234: 2,
345: 2
}
What would be the best way to go about this?
The aggregation framework itself if not going to dynamically name keys the way you have presented as a proposed output, and that probably is a good thing really. But you can probably just do a query like this:
db.collection.aggregate([
// Match documents that contain the elements
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// De-normalize the array field content
{ "$unwind": "$field" },
// Match just the elements you want
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
// Count by the element as a key
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}}
])
That gives you output like this:
{ "_id" : 345, "count" : 2 }
{ "_id" : 234, "count" : 2 }
{ "_id" : 123, "count" : 2 }
But if you really want to go nuts on this, you are specifying the "keys" that you want as part of your query, so you could form a pipeline like this:
db.collection.aggregate([
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$unwind": "$field" },
{ "$match": {
"field.subDoc": { "$in": [123,234,345] }
}},
{ "$group": {
"_id": "$field.subDoc",
"count": { "$sum": 1 }
}},
{ "$group": {
"_id": null,
"123": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 123 ] },
"$count",
0
]
}
},
"234": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 234 ] },
"$count",
0
]
}
},
"345": {
"$max": {
"$cond": [
{ "$eq": [ "$_id", 345 ] },
"$count",
0
]
}
}
}}
])
Which is a relatively simple thing to construct that last stage in code by just processing the list of arguments:
var list = [123,234,345];
var group2 = { "$group": { "_id": null } };
list.forEach(function(id) {
group2["$group"][id] = {
"$max": {
"$cond": [
{ "$eq": [ "$_id", id ] },
"$count",
0
]
}
};
});
And that comes out more or less how you want it.
{
"_id" : null,
"123" : 2,
"234" : 2,
"345" : 2
}
Not exactly what you're asking for but it can give you an idea:
db.test.aggregate([
{
$unwind: '$field'
},
{
$group: {
_id: {
subDoc: '$field.subDoc'
},
count: {
$sum: 1
}
}
},
{
$project: {
subDoc: '$subDoc.subDoc',
count: '$count'
}
}
]);
Output:
{
"result": [
{
"_id": {
"subDoc": 456
},
"count": 1
},
{
"_id": {
"subDoc": 345
},
"count": 2
},
{
"_id": {
"subDoc": 234
},
"count": 2
},
{
"_id": {
"subDoc": 123
},
"count": 2
}
],
"ok": 1
}
Related
i have the following aggregate function in my code to count how many times a value is found in the db:
let data: any = await this.dataModel.aggregate(
[
{
$match: {
field: new ObjectID(fieldID),
},
},
{
$group: {
_id: "$value",
total_for_value: { $sum: 1 },
},
},
]
);
This works correctly, however my data setup is a bit different. I have two types of value fields. Some like this:
{
"_id" : ObjectId("123"),
"value" : "MALE"
}
and some like this:
{
"_id" : ObjectId("456"),
"value" : {
"value" : "MALE",
}
}
Is there a way to group the ones where the _id and the _id.value are the same? At the moment it counts them separately.
db.collection.aggregate([
{
"$addFields": {
"key2": {
"$cond": {
"if": {
$and: [
{
"$eq": [
{
"$type": "$key"
},
"object"
]
}
]
},
"then": "$key.value",
"else": "$key"
}
}
}
},
{
"$group": {
"_id": "$key2",
"data": {
$push: "$$ROOT"
}
}
}
])
This would do the job if _id.value is an object.
Playground
This is hard for me to put in words, so I am going to try and explain with a code example:
Working MongoDb Playground
db.collection.aggregate([
{
"$facet": {
"countByStatus": [
{
"$group": {
"_id": "$status.name",
"count": {
"$sum": 1
}
}
}
],
"totalCount": [
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
}
]
}
}
])
Output:
[
{
"countByStatus": [
{
"_id": "Approved",
"count": 2
},
{
"_id": "Rejected",
"count": 1
}
],
"totalCount": [
{
"_id": null,
"count": 3
}
]
}
]
Desired Output:
[
{
"countByStatus": [
{
"_id": "Approved",
"count": 2
},
{
"_id": "Rejected",
"count": 1
}
],
"totalCount": 3 //totalCount is a flat count instead of array here
}
]
I could execute the query get the result and then pick first element of array[0].count but I want to know if this could be done natively?
It can be done in a single stage with $let:
{
$project: {
countByStatus: 1,
totalCount: {
$let: {
vars: { first: { $arrayElemAt: [ "$totalCount", 0 ] } },
in: "$$first.count"
}
}
}
}
Mongo Playground
how to retrieve count for quantity and price in for same group in mongoose
db.inventory.find( { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] } )
You can try using $facet to get count of multiple group in single query. check the below query is helpful to your requirement.
db.inventory.aggregate([
{ "$facet": {
"quantity": [
{ "$match" : { "quantity": { "$lt": 20 }}},
{ "$count": "Total" },
],
"price": [
{ "$match" : {"price": { "$eq": 10 }}},
{ "$count": "Released" }
]
}},
{ "$project": {
"quantity": { "$arrayElemAt": ["$quantity.Total", 0] },
"price": { "$arrayElemAt": ["$price.Released", 0] }
}}
])
Collection of my database is something like below
[{
_id:1,
status:"active",
sale: 4,
createdAt:"2019-10-08 08:46:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 06:41:19"
},
{
_id:2,
status:"inactive",
sale:5,
createdAt:"2019-10-08 02:01:19"
}]
I need to group it by "day".The result which I want
[{
createdAt:"2019-10-08 02:01:19",
inactive: 2,
active:1,
salesOfActive: 4,
salesOfInactive:10
}]
I am not getting the actual result which I want any help will be highly appreciated
I had try with this but won't get an idea how i will get salesOfActive and salesOfInactive per day
{
$group: {
_id: {
day: { $dayOfMonth: "$createdAt" }
},
inActive:{$sum: { status:"inactive"}},
active:{$sum: { status:"active"}},
salesOfActive: { $sum:$sale }
}
}
Basically you need to $sum each field $conditionally here
db.collection.aggregate([
{ "$group": {
"_id": {
"$dayOfMonth": { "$dateFromString": { "dateString": "$createdAt" } }
},
"inactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 1, 0] }
},
"active": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, 1] }
},
"salesOfInactive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, "$sale", 0] }
},
"salesOfActive": {
"$sum": { "$cond": [{ "$eq": ["$status", "inactive"] }, 0, "$sale"] }
}
}}
])
MongoPlayground
My documents looks like this.
{
"_id" : ObjectId("572c4bffd073dd581edae045"),
"name" : "What's New in PHP 7",
"description" : "PHP 7 is the first new major version number of PHP since 2004. This course shows what's new, and what's changed.",
"difficulty_level" : "Beginner",
"type" : "Normal",
"tagged_skills" : [
{
"_id" : "5714e894e09a0f7d804b2254",
"name" : "PHP"
}
],
"created_at" : 1462520831.649,
"updated_at" : 1468233074.243 }
Is it possible to get recent 5 documents and total count in a single query.
I am using two queries for this requirement as given below.
db.course.find().sort({created_at:-1}).limit(5)
db.course.count()
This is a perfect job for the aggregation framework.
db.course.aggregate(
[
{ "$sort": { "created_at": -1 }},
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" },
"count": { "$sum": 1 }
}},
{ "$project": { "_id": 0, "count": 1, "docs": { "$slice": [ "$docs", 5 ] } }}
]
)
If your MongoDB server doesn't support $slice then you need to use the ugly and inefficient approach.
db.course.aggregate(
[
{ "$sort": { "created_at": -1 }},
{ "$group": {
"_id": null,
"docs": { "$push": "$$ROOT" },
"count": { "$sum": 1 }
}},
{ "$unwind": "$docs" },
{ "$limit": 5 }
]
)
You can implement this easily with $facet
myCollection.aggregate([
{
$facet: {
count: [{ $count: "value" }],
data: [{ $sort: { _id: -1 } }, { $skip: skip }, { $limit: limit }]
}
},
{ $unwind: "$count" },
{ $set: { count: "$count.value" } }
])
the return result will be like:
[
{
"count": 234,
"data": [
// ...
]
}
]
#styvane I tested in person, this query is even less efficient than twice queries.
// get count
db.course.aggregate([{$match:{}}, {$count: "count"}]);
// get docs
db.course.aggregate(
[
{$match:{}},
{ "$sort": { "created_at": -1 }},
{"$skip": offset},
{"$limit": limit}
]
)
No, there is no other way. Two queries - one for count - one with limit.