Return total fields count in mongodb - node.js

Is it possible to retrieve the total fields count with respect to fields name in a single query from a collection?
I am expecting a result like this
{
"field_1":101,
"field_2":93,
"field_3":1
}

You can try,
$project convert root object to array using $objectToArray, this will create k(key) and v(value) structure of array
$unwind deconstruct root array
$group by k(key) and get total fields count using $sum
$group by null and construct an array with k(key) and v(value) fields format
$replaceRoot to replace object after converting from array using $arrayToObject
db.collection.aggregate([
{ $project: { root: { $objectToArray: "$$ROOT" } } },
{ $unwind: "$root" },
{
$group: {
_id: "$root.k",
total: { $sum: 1 }
}
},
{
$group: {
_id: null,
root: {
$push: { k: "$_id", v: "$total" }
}
}
},
{
$replaceRoot: {
newRoot: {
$arrayToObject: "$root"
}
}
}
])
Playground

Yes
Playground
db.collection.aggregate({
$group: {
_id: null,
field_2: {
$push: {
$cond: [
"$field_2",
1,
0
]
}
},
field_1: {
$push: {
$cond: [
"$field_1",
1,
0
]
}
}
}
},
{
$project: {
_id:0,
field_1: {
$sum: "$field_1"
},
field_2: {
$sum: "$field_2"
}
}
})
Be aware:
When is true, $exists matches the documents that contain the field, including documents where the field value is null

Related

select single document with minimum value $Min mongodb

I have made several efforts to select a single specific document that contains the minimum value from the database.
let Lowestdate = await BTCMongo.aggregate(
[
// { "$sort": { "name": 1,
{
$match : { "createdAt" : { $gte: new Date(last),$lte: new Date(NEW) } } },
{
$group:
{
_id:null,
minFee_doc:{$min: "$$ROOT"},
minFee: { $min:{$toDouble:"$one"}},
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: {$toDouble:"$one"}},
}
},
]
).then(result => {}):
with minFee_doc:{$min: "$$ROOT"}, I have been trying to return the document containing the minimum $min but it keeps returning document containing $first
How do i select the document with minimum value?
Note : i will like to return the whole document including the "CreatedAt" "UpdatedAt", and _id. of the document containing the minimum value
Expected Result should look like:
{
"minFee_doc": {
"_id": "61e84c9f622642463640e05c",
"createdAt": "2022-01-19T17:38:39.034Z",
"updatedAt": "2022-04-24T14:48:38.100Z",
"__v": 0,
"one": 2
},
"minFee": 2,
"firstFee": 3,
"lastFee": 5,
"maxFee": 6
}
Edit: also to provide a single document not multiple
$push all docs in $group then $set the array with $filter
db.collection.aggregate([
{
$match: {}
},
{
$group: {
_id: null,
minFee_doc: { $push: "$$ROOT" },
minFee: { $min: { $toDouble: "$one" } },
firstFee: { $first: "$one" },
lastFee: { $last: "$one" },
maxFee: { $max: { $toDouble: "$one" } }
}
},
{
$set: {
minFee_doc: {
$filter: {
input: "$minFee_doc",
as: "m",
cond: { "$eq": [ "$$m.one", "$minFee" ] }
}
}
}
}
])
mongoplayground

How to count filtered results in $match in mongodb?

I have an aggregation query like this
nSkip=4, count=25, sortOn='first_name' , order=-1, toMatch='biker' // all variables are dynamic
query={status: true, roles: { $regex: toMatchRole, $options: "m" }} // also dynamic
User.aggregate([
{
$match: query
},
// after this I need the total number of documents that matched the criteria,
// before sorting or skipping or limiting in "total_count" variable
{
$sort: {
[sortOn]: order
}
},
{
$skip: nSkip
},
{
$limit: count
},
{
$project: {
last_name: 1,
first_name: 1,
email: 1
}
}
])
User Collection
{
_id:60befdcfa4198332b728f9cd",
status:false,
roles:["biker"],
email:"john#textmercato.com",
last_name:"aggr",
first_name:"john",
}
I am not sure how to achieve this without disturbing the rest of the stages in aggregation. Can someone please help me out.
You can use $group
{
"$group": {
"_id": null,
"data": { "$push": "$$ROOT" },
"total_count": { $sum: 1 }
}
},
{ $unwind: "$data" },
{
"$replaceRoot": {
"newRoot": {
"$mergeObjects": [ "$$ROOT", "$data" ]
}
}
}
and finally Project the total_count
Working Mongo playground

Last value by type with _id relation

I have a collection of Event containing a type that can be 0, 1, 2, 3, 4, 5 and a createdAt date. Each event is related to an other collection called RTS.
I want to gather for each Rts, the last event of each type.
Problem using my soluce :
The problem is, I have to describe each types one by one in order to make it work. Is there any solution to have dynamical key creation induced by the type value ?
Here is what I get now :
I sort the data
Group by idRTS whch contains the link to the second collection. For each type, push the values inside of a specific array.
Remove the null values from the types arrays.
Keep the first value only (the most updated).
Makes the data presentable.
[
{
$sort: {
idRTS: -1,
createdAt: -1
}
},
{
$group: {
_id: '$idRTS',
type0: {
$push: {
$cond: {
if: {
$eq: [
'$type', 0
]
},
then: '$$ROOT',
else: null
}
}
},
type5: {
$push: {
$cond: {
if: {
$eq: [
'$type', 5
]
},
then: '$$ROOT',
else: null
}
}
}
}
},
{
$project: {
_id: '$_id',
type0: {
'$filter': {
'input': '$type0',
'as': 'd',
'cond': {
'$ne': [
'$$d', null
]
}
}
},
type5: {
$filter: {
input: '$type5',
as: 'd',
cond: {
$ne: [
'$$d', null
]
}
}
}
}
},
{
$project: {
_id: '$_id',
type0: {
$arrayElemAt: [
'$type0', 0
]
},
type5: {
'$arrayElemAt': [
'$type5', 0
]
}
}
}
]
$match type in 0 or 5
$sort by idRTS and createdAt in descending order
$group by both idRTS and createdAt field and get first object, this will get first document of both type
$group by idRTS and make array of both types, in k(key) and v(value) format
$project to convert type array to object using $objectToArray
db.collection.aggregate([
{ $match: { type: { $in: [0, 5] } } },
{
$sort: {
idRTS: -1,
createdAt: -1
}
},
{
$group: {
_id: {
idRTS: "$idRTS",
type: "$type"
},
type: { $first: "$$ROOT" }
}
},
{
$group: {
_id: "$_id.idRTS",
type: {
$push: {
k: { $toString: "$type.type" },
v: "$type"
}
}
}
},
{ $project: { type: { $arrayToObject: "$type" } } }
])
Playground

How to find array greater than document size in MongoDB

I have schema like below
[
{
id:"111"
tags:[222,333,444,555]
},
{
id: "222"
tags:[312,345,534]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I want to find all documents where tags array size is greater than document size returned by $match in aggregation pipeline, so in above Ex. the number of documents are 3 so i want to return all documents having tags array size greater that 3
[
{
id:"111"
tags:[222,333,444,555]
},
{
id:"333"
tags:[111,222,333,444,555]
},
]
I am using aggregation pipeline to process other info, I am stuck at how to have store document size so that i can find all tags greater than document size
below is query which i am using, i want to do it in aggregation and in one call
.aggregate([
{
"$match":{
"ids":{
"$in":[
"111",
"222",
"333"
]
}
}
})]
Facet helps you to solve this problem.
$facet helps to categorize the incoming documents. We use totalDoc for counting the document and allDocuments for getting all the documents
$arrayElemAt helps to get the first object from totalDoc where we already know that only one object should be inside the totalDoc. Because when we group it, we use _id:null
$unwind helps to de-structure the allDocuments array
Here is the code
db.collection.aggregate([
{
$facet: {
totalDoc: [
{
$group: {
_id: null,
count: {
$sum: 1
}
}
}
],
allDocuments: [
{
$project: {
tags: 1
}
}
]
}
},
{
$addFields: {
totalDoc: {
"$arrayElemAt": [
"$totalDoc",
0
]
}
}
},
{
$unwind: "$allDocuments"
},
{
$addFields: {
sizeGtDoc: {
$gt: [
{
$size: "$allDocuments.tags"
},
"$totalDoc.count"
]
}
}
},
{
$match: {
sizeGtDoc: true
}
},
{
"$replaceRoot": {
"newRoot": "$allDocuments"
}
}
])
Working Mongo playground
You can try,
$match you condition
$group by null and make root array of documents and get count of root documents in count
$unwind deconstruct root array
$match tags size and count greater than or not using $expr expression match
$replaceRoot to replace root object in root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{ $unwind: "$root" },
{ $match: { $expr: { $gt: [{ $size: "$root.tags" }, "$count"] } } },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
Second option:
first 2 stages $match and $group both are same as like above query,
$project to filter root array match condition if tags size and count greater than or not, this will return filtered root array
$unwind deconstruct root array
$replaceRoot replace root object to root
db.collection.aggregate([
{ $match: { id: { $in: ["111", "222", "333"] } } },
{
$group: {
_id: null,
root: { $push: "$$ROOT" },
count: { $sum: 1 }
}
},
{
$project: {
root: {
$filter: {
input: "$root",
cond: { $gt: [{ $size: "$$this.tags" }, "$count"] }
}
}
}
},
{ $unwind: "$root" },
{ $replaceRoot: { newRoot: "$root" } }
])
Playground
You can skip $unwind and $replaceRoot stages if you want because this query always return one document in root, so you can easily access like this result[0]['root'], you can save 2 stages processing and execution time.
You could use $facet to get two streams i.e. one with the filtered documents and the counts using $count. The resulting streams can then
be aggregated further with a $filter as follows to get the desired result
db.getCollection('collection').aggregate([
{ '$facet': {
'counts': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
{ '$count': "numberOfMatches" }
],
'docs': [
{ '$match': { 'id': { '$in': ['111', '222', '333'] } } },
]
} },
{ '$project': {
'result': {
'$filter': {
'input': '$docs',
'cond': {
'$gt': [
{ '$size': '$$this.tags' },
{ '$arrayElemAt': ['$counts.numberOfMatches', 0] }
]
}
}
}
} }
])

Mongodb aggregate $push with $cond and $each

I'm trying to use $cond to conditionally $push multiple integers onto a numbers array during an aggregate $group without any success. Here is my code:
Item.aggregate(
[
{
$group: {
_id: "$_id",
numbers: {
$push: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: { $each: [10,25,50,70] },
else: null,
}
}
}
}
},
]
)
...
Is Mongo DB just not set up for this right now, or am I looking at this all wrong?
Please try it without $each as below
Item.aggregate(
[{
$group: {
_id: "$_id",
numbers: {
$push: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: [10,25,50,70] ,
else: null,
}
}
}
}
}]);
Provided answers will work but they'll add null to the array whenever else block gets executed & at the end you need to filter out the null values from the actual array (numbers) which is an additional step to do!
You can use $$REMOVE to conditionally exclude fields in MongoDB's $project.
Item.aggregate(
[{
$group: {
_id: "$_id",
numbers: { $push: { $cond: [{ $gt: ["$price.percent", 70] }, [10, 25, 50, 70], '$$REMOVE'] } } // With $$REMOVE nothing happens on else
}
}]);
REF: $cond
Have you tried:
Item.aggregate(
[
{
$group: {
_id: "$_id",
numbers: {
$push: {
$each: {
$cond: {
if: { $gt: [ "$price.percent", 70 ] },
then: [10,25,50,70],
else: null
}
}
}
}
}
},
]
)

Resources