All fields containing given subfield in MongoDB - mongodb

In MongoDB I know I can check if a subfield exist with $exists and dot notation, like this:
...{field.subfield: {$exists: 1}}...
but this obviously work only if I know which field to look for my subfield in.
I have documents in this format:
{
field1: {
subfield1: asd,
subfield2: asd
},
field2: {
subfield2: asd,
subfield3: asd
},
field3: {
subfield1: asd,
subfield3: asd,
}
}
In this example, say that my subfield of interest is subfield2, I want to project (or match, or whatever, I'm looking for a general answer) only field1 and field2, excluding field3 (that doesn't contain subfield2).
My documents could contain tens or hundreds of fields, so manually checking one by one isn't viable.

Is this what you want?
db.collection.aggregate([
{
"$project": {
_id: 0
}
},
{
"$project": {
"root": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$root"
},
{
"$match": {
"root.v.subfield2": {
"$exists": true
}
}
},
{
"$group": {
"_id": null,
"root": {
"$addToSet": "$root"
}
}
},
{
"$project": {
"root": {
"$arrayToObject": "$root"
}
}
},
{
"$project": {
"field1": "$root.field1",
"field2": "$root.field2",
_id: 0
}
}
])
Try it here

Related

MongoDB: How to merge all documents into a single document in an aggregation pipeline

I have the current aggregation output as follows:
[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
]
The array has two documents. I would like to combine all the documents into a single document having all the fields in mongoDB
db.collection.aggregate([
{
$group: {
_id: 0,
merged: {
$push: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: {
"$mergeObjects": "$merged"
}
}
}
])
Explained:
Group the output documents in one field with push
Replace the document root with the merged objects
Plyaground
{
$group: {
"_id": "null",
data: {
$push: "$$ROOT"
}
}
}
When you add this as the last pipeline, it will put all the docs under data, but here data would be an array of objects.
In your case it would be
{ "data":[
{
"courseCount": 14
},
{
"registeredStudentsCount": 1
}
] }
Another approach would be,
db.collection.aggregate([
{
$group: {
"_id": "null",
f: {
$first: "$$ROOT",
},
l: {
$last: "$$ROOT"
}
}
},
{
"$project": {
"output": {
"courseCount": "$f.courseCount",
"registeredStudentsCount": "$l.registeredStudentsCount"
},
"_id": 0
}
}
])
It's not dynamic as first one. As you have two docs, you can use this approach. It outputs
[
{
"output": {
"courseCount": 14,
"registeredStudentsCount": 1
}
}
]
With extra pipeline in the second approach
{
"$replaceRoot": {
"newRoot": "$output"
}
}
You will get the output as
[
{
"courseCount": 14,
"registeredStudentsCount": 1
}
]

Mongodb group by values and count the number of occurence

I am trying to count how many times does a particular value occur in a collection.
{
_id:1,
field1: value,
field2: A,
}
{
_id:2,
field1: value,
field2: A,
}
{
_id:3,
field1: value,
field2: C,
}
{
_id:4,
field1: value,
field2: B,
}
what I want is to count how many times A occurs, B occurs and C occurs and return the count.
The output I want
{
A: 2,
B: 1,
C: 1,
}
You can use $facet in an aggregate pipeline like this:
$facet create "three ways" where in each one filter the values by desired key (A, B or C).
Then in a $project stage you can get the $size of the matched values.
db.collection.aggregate([
{
"$facet": {
"first": [
{
"$match": {
"field2": "A"
}
}
],
"second": [
{
"$match": {
"field2": "B"
}
}
],
"third": [
{
"$match": {
"field2": "C"
}
}
]
}
},
{
"$project": {
"A": {
"$size": "$first"
},
"B": {
"$size": "$second"
},
"C": {
"$size": "$third"
}
}
}
])
Example here
This is typical use case for $group stage in Aggregation Pipeline. You can do it like this:
$group - to group all the documents by field2
$sum - to count the number of documents for each value of field2
db.collection.aggregate([
{
"$group": {
"_id": "$field2",
"count": {
"$sum": 1
}
}
}
])
Working example
Leverage the $arrayToObject operator and a final $replaceWith pipeline to get the desired result. You would need to run the following aggregate pipeline:
db.collection.aggregate([
{ $group: {
_id: { $toUpper: '$field2' },
count: { $sum: 1 }
} },
{ $group: {
_id: null,
counts: {
$push: { k: '$_id', v: '$count' }
}
} },
{ $replaceWith: { $arrayToObject: '$counts' } }
])
Mongo Playground

Find all objects that have duplicated values in Mongo DB

How can I return all objects from a collection where name is same in all objects?
For example in this case name: John
[
{
_id: 1,
name: "John",
last: "Smith"
},
{
_id: 8,
name: "John",
last: "Snow"
},
{
_id: 16,
name: "John",
last: "McKay"
},
]
you can use group in aggregate to return all data that have the same name
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"orig": {
"$push": "$$ROOT"
}
}
},
{
"$addFields": {
"sizeOrig": {
$size: "$orig"
}
}
},
{
"$match": {
sizeOrig: {
$gt: 0
}
}
},
{
$unwind: "$orig"
},
{
"$replaceRoot": {
"newRoot": "$orig"
}
}
])
example : https://mongoplayground.net/p/DfTA6_pUaRA
but if you want just single data for each duplication , you need just do it by group
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"orig": {
"$push": "$$ROOT"
}
}
}
])
https://mongoplayground.net/p/hd1z77cdtp0
you need to use "findAll({})" or just "find({})" with the collection name it will return an array object that will contain all the documents from the collection. For example if you have a collection or a model which is named "Employees" you need to do the following after connection to db.
Employees.find({});
This will return an array with all docs inside Employee collection. Once you have this returned you can iterate over it.

How to compute frequency for multiple fields using a single pipeline in MongoDB?

Is it possible to calculate the frequency of multiple fields with a single query in MongoDB? I can do that with separate $group stages for each field. How can I optimize it and build one pipeline that can do the job for all items?
I have the following pipeline in MongoDB 4.5
{
$match: {
field1: { $in: ['value1', 'value2'] },
field2: { $in: ['v1', 'v2'] },
}
},
{
$group: {
_id: {
field1: '$field1',
field2: '$field2'
},
frequency: { $sum: 1.0 }
}
}
From this, I obtain data like the following:
{
"_id": {
"field1": "value1",
"field2": "v1"
},
"count": 7.0
},
{
"_id": {
"field1": "value1",
"field2": "v2"
},
"count": 3.0
},
{
"_id": {
"field1": "value2",
"field2": "v1"
},
"count": 4.0
}
The result that I am trying to get is:
{
"field1": [
"value1": 10.0,
"value2": 4.0
],
"field2": [
"v1": 11.0,
"v2": 3.0
]
}
convert your required fields into array key-value format using $objectToArray
$unwind to deconstruct the above converted array
$group by key and value and count sum
$group by key and construct the array of value and count
$group by null and construct the array of field and above array after converting from $arrayToObject
$replaceToRoot to replace above array after converting from array to object
db.collection.aggregate([
{
$match: {
field1: { $in: ["value1", "value2"] },
field2: { $in: ["v1", "v2"] }
}
},
{
$project: {
arr: {
$objectToArray: {
fields1: "$field1",
fields2: "$field2"
}
}
}
},
{ $unwind: "$arr" },
{
$group: {
_id: {
k: "$arr.k",
v: "$arr.v"
},
count: { $sum: 1 }
}
},
{
$group: {
_id: "$_id.k",
arr: {
$push: {
k: "$_id.v",
v: "$count"
}
}
}
},
{
$group: {
_id: null,
arr: {
$push: {
k: "$_id",
v: { $arrayToObject: "$arr" }
}
}
}
},
{ $replaceRoot: { newRoot: { $arrayToObject: "$arr" } } }
])
Playground

Mongodb having problem of adding two values inside nested document with dynamic key

I wish to add currentAsset.total and longTermAsset.total for each of my child documents with dynamic key to a new field. My current mongodb version is 4.0.12
My source document is as below:
{
"_id":"5f44bc4c36ac3e2c8c6db4bd",
"counter":"Apple",
"balancesheet":{
"0":{
"currentAsset":{
"total":123.12
},
"longTermAsset":{
"total":10.16
}
},
"1":{
"currentAsset":{
"total":10.23
},
"longTermAsset":{
"total":36.28
}
}
}
}
The result document I wanted to get is:
{
"_id": "5f44bc4c36ac3e2c8c6db4bd",
"counter": "Apple",
"balancesheet": {
"0": {
"currentAsset": {
"total": 123.12
},
"longTermAsset": {
"total": 10.16
},
"totalAsset": 133.28
},
"1": {
"currentAsset": {
"total": 10.23
},
"longTermAsset": {
"total": 36.28
},
"totalAsset": 46.51
}
}
}
I have tried a few aggegrates but failed as it is giving me "errmsg" : "$add only supports numeric or date types, not array"
db.balancesheets.aggregate([
{
$match: { counter: "Apple" }
},
{
$project: {
bs: { $objectToArray: "$balancesheet" }
}
},
{
$addFields: {
totalAsset: {
$add: ["$bs.k.currentAsset.total", "$bs.k.longTermAsset.total"]
}
}
}
])
As I refer to this, it seems like the version needs to be 4.2 and above. Is there anyway that will be able to do it on my existing 4.0.12 version?
MongoDB Aggregation: add field from an embedded document via a dynamic field path
There is no version issues, follow few fixes,
first 2 pipelines looks good,
$unwind deconstruct bs array
$addFields corrected, you used k instead of v in accessing field total
$group to reconstruct and prepare again object to array
$addFields to convert bs array to object using $reduce
db.collection.aggregate([
// $match ... pipeline
// $project ... pipeline
// unwind bs array
{ $unwind: "$bs" },
{
$addFields: {
"bs.v.totalAsset": { $add: ["$bs.v.currentAsset.total", "$bs.v.longTermAsset.total"] }
}
},
{
$group: {
_id: "$_id",
bs: { $push: { $arrayToObject: [["$bs"]] } },
counter: { $first: "$counter" },
},
}
{
$addFields: {
bs: {
$reduce: {
input: "$bs",
initialValue: {},
in: { $mergeObjects: ["$$value", "$$this"] }
}
}
}
}
])
Playground