mongodb group each fields along with total count - database

I have a collection with documents in cosmosDB .How can I group each field distinct values using mongoDB?
Here is my sample data:
{
"_id" : ObjectId("61ba65af74cf385ee93ad2c8"),
"Car_brand":"A",
"Plate_number":"5",
"Model_year":"2015",
"Company":"Tesla Motors"
},
{
"_id" : ObjectId("61ba65af74cf385ee93ad2c9"),
"Car_brand":"B",
"Plate_number":"2",
"Model_year":"2021",
"Company":"Tesla Motors",
},
{
"_id" : ObjectId("61ba65af74cf385ee93ad2ca"),
"Car_brand":"B",
"Plate_number":"2",
"Model_year":"2011",
"Company":"Lamborghini",
}
expected:
{
"Car_brand":["A","B"]
"Plate_number":["5","2"]
"Model_year":["2015","2021","2011"]
"Company":["Lamborghini","Tesla Motors"]
}

Option1: Here is how to do in mongoDB , I guess it is similar in cosmosDB:
db.collection.aggregate([
{
$group: {
_id: null,
Car_brand: {
$push: "$Car_brand"
},
Plate_number: {
$push: "$Plate_number"
},
Model_year: {
$push: "$Model_year"
},
Company: {
$push: "$Company"
}
}
}
])
playground
Option2: Later I have identified you need the distinct values , here is an example:
db.collection.aggregate([
{
$group: {
_id: null,
Car_brand: {
$addToSet: "$Car_brand"
},
Plate_number: {
$addToSet: "$Plate_number"
},
Model_year: {
$addToSet: "$Model_year"
},
Company: {
$addToSet: "$Company"
}
}
}
])
playground2

Related

MongoDB using skip and distinct in a query based on values inside an array

So I have document that is structure like this
_id: ObjectId('62bbe17d8fececa06b91873d')
clubName: 'test'
staff:[
'62bbe47f8fececa06b9187d8'
'624f4b56ab4f5170570cdba3' //IDS of staff members
]
A single staff can be assigned to multiple clubs so what I'm trying to achieve is to get all staff that has been assigned to at least one club and display them on a table on the front end, I followed this solution since distinct and skip can't be used on a single query but it just returned this:
[
{ _id: [ '624f5054ab4f5170570cdd16', '624f5054ab4f5170570cdd16' ] } //staff from club 1,
{ _id: [ '624f5054ab4f5170570cdd16', '624f9194ab4f5170570cded1' ] } //staff from club 2,
{ _id: [ '624f4b56ab4f5170570cdba3' ]} //staff from club 3
]
my desired outcome would be like this:
[ _id : ['624f5054ab4f5170570cdd16', '624f9194ab4f5170570cded1', '624f4b56ab4f5170570cdba3'] ]
here's my query:
const query = this.clubModel.aggregate(
[{ $group: { _id: '$staff' } }, { $skip: 0}, { $limit: 10}],
(err, results) => {
console.log(results);
},
);
the values returned are not distinct at all, is there an operation that can evaluate the value inside an array and make them distinct?
Here's my new query after adding the 'createdAt' field in my document structure:
const query = this.clubModel.aggregate([
{ $sort: { createdAt: -1 } },
{
$unwind: '$drivers',
},
{
$project: {
isActive: true,
},
},
{
$group: {
_id: 'null',
ids: {
$addToSet: '$drivers',
},
},
},
{
$project: {
_id: 0,
},
},
{
$skip: skip,
},
{
$limit: limit,
},
]);
Does this works for you, first UNWIND the staff array, and then group on "_id" as null and add staff values using $addToSet:
db.collection.aggregate([
{
"$unwind": "$staff"
},
{
"$group": {
"_id": "null",
"ids": {
"$addToSet": "$staff"
}
}
},
{
"$project": {
"_id": 0,
}
},
{
$skip: 0
},
{
$limit: 10
}
])
Here's the working link.

How to query two collections with related data?

I have 2 collections, collection A has some documents like {'id':1,'field':'name'},{'id':1,'field':'age'},and collection B has some documents like
{'_id':1,'name':'alice','age':18,'phone':123},{'_id':2,'name':'bob','age':30,'phone':321}
and I want to find all the document whose '_id' is in collectionA, and just project the corresponding field.
for example:
collection A
{'id':1,'field':'name'},
{'id':1,'field':'age'}
collection B
{'_id':1,'name':'alice','age':18,'phone':123},
{'_id':2,'name':'bob','age':30,'phone':321}
the result is:
{'name':'alice','age':18},
I don't know if there is an easy way to do that?
You can use $lookup to join two collection
db.col1.aggregate([
{
$match: {
id: 1
}
},
{
"$lookup": {
"from": "col2",
"localField": "id",
"foreignField": "_id",
"as": "listNames"
}
},
{
$project: {
listNames: {
$first: "$listNames"
}
}
},
{
$project: {
_id: 0,
name: "$listNames.name",
age: "$listNames.age"
}
}
])
Mongo Playground: https://mongoplayground.net/p/E-0WvK_SUS_
So the idea is:
Convert the documents in to key, value pair for both the collections using $objectToArray.
Then perform a join operation based on key k and (id <-> _id) using $lookup.
Replace the result as root element using $replaceRoot.
Convert array to object using $arrayToObject and again $replaceRoot.
Query:
db.colB.aggregate([
{
$project: {
temp: { $objectToArray: "$$ROOT" }
}
},
{
$lookup: {
from: "colA",
let: { temp: "$temp", colB_id: "$_id" },
pipeline: [
{
$addFields: {
temp: { k: "$field", v: "$id" }
}
},
{
$match: {
$expr: {
$and: [
{ $in: ["$temp.k", "$$temp.k"] },
{ $eq: ["$temp.v", "$$colB_id"] }
]
}
}
},
{
$replaceRoot: {
newRoot: {
$first: {
$filter: {
input: "$$temp",
as: "item",
cond: { $eq: ["$field", "$$item.k"] }
}
}
}
}
}
],
as: "array"
}
},
{
$replaceRoot: {
newRoot: { $arrayToObject: "$array" }
}
}
]);
Output:
{
"name" : "alice",
"age" : 18
}

How to compare the elements of an array from the same object in MongoDB?

I've a document collection with the below data
[{
_id:ObjectId(),
status:"created",
users:[{
email:"xyz#gmail.com",
role:"admin"
},
{
email:"abc#gmail.com",
role:"admin"
},
{
email:"abc#gmail.com",
role:"user"
}]
},
{
_id:ObjectId(),
status:"created",
users:[{
email:"pqr#gmail.com",
role:"admin"
},
{
email:"abc#gmail.com",
role:"admin"
},
{
email:"pqr#gmail.com",
role:"user"
}]
}]
I want to fetch the count of documents which have the user with both "admin" and "user" roles and the status is "created". Now I'm trying to match the email id and also the role.
db.documents.aggregate([
{"$match":{
"status":"created",
"$expr":{
"$eq":[
{"$size":{"$setIntersection":["$users.email_id","$users.email_id"]}},
0
]
}
}},
])
Is there any way to compare the elements of the array from the same object?
I want to fetch the count of documents which have the user with both
"admin" and "user" roles and the status is "created". Now I'm trying
to match the email id and also the role.
You can use an aggregation or a find query as follows:
var MATCH_EMAIL = "abc#gmail.com"
db.collection.aggregate( [
{
$match: {
$and: [ { status: "created" } , { "users": { $elemMatch: { role: "admin", email: MATCH_EMAIL } } } ],
"users": { $elemMatch: { role: "user", email: MATCH_EMAIL } }
}
},
{
$count: "Count"
}
] )
// -- or --
db.collection.find( {
$and: [ { status: "created" } , { "users": { $elemMatch: { role: "admin", email: MATCH_EMAIL } } } ],
"users": { $elemMatch: { role: "user", email: MATCH_EMAIL } }
} ).count()
Note the query filter is same in both cases. The aggregation returns a result document like { "Count" : 1 }, but the find query returns just the count value, like 1.
[ EDIT ADD ]
Updated code to have the same email for "user" and "admin" riles.
db.collection.aggregate( [
{
$match: {
$and: [ { status: "created" } , { "users.role": "admin" } ],
"users.role": "user"
}
},
{
$unwind: "$users"
},
{
$group: {
_id: { _id: "$_id", email: "$users.email" },
roles: { $push: "$users.role" }
}
},
{
$match: {
$expr: { $gt: [ { $size: "$roles" }, 1 ] }
}
},
{
$count: "Count with user+admin roles and same email"
}
] )

Update value in nested array of mongo 3.5

I want to make an upsert call to update as well as insert my data in nested array of mongo db.
This is my mongo document.
{
"_id" : "575",
"_class" : "com.spyne.sharing.SpyneShareUserProject",
"spyneSharePhotoList" : [
{
"_id" : "fxLO68XyMR",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : false
},
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
]
},
{
"_id" : "nVpD0KoQAI",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#eventila.com",
"selectedByClient" : true
}
]
},
{
"_id" : "Pm0B3Q9Igv",
"spyneShareUsers" : [
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : true
}
]
}
]
}
Here my requirement is,
lets say i have an ID i.e. 575 (_id)
Then i will have the nested array ID i.e. fxLO68XyMR (spyneSharePhotoList._id)
Then i will have nested email id as ID i.e. chittaranjan#eventila.com (spyneSharePhotoList.spyneShareUsers._id) and selectedByClient (boolean)
Now i want is to check if this ID (spyneSharePhotoList.spyneShareUsers._id) is already present in the desired location i want to update the boolean value i.e. selectedByClient (true / false) according to that email id.
If the id is not present in the array, the it will make a new entry. as
{
"_id" : "chittaranjan#gmail.com",
"selectedByClient" : false
}
in spyneShareUsers list.
Please help me to do this task. Thank you
Most likely this is not the smartest solution, but it should work:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR",
ShareUserId: "chittaranjan_new#eventila.com"
}
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$facet: {
root: [{ $match: {} }],
update: [
{ $unwind: "$spyneSharePhotoList" },
{ $match: { "spyneSharePhotoList._id": shareUserProject.PhotoListId } },
{
$set: {
"spyneSharePhotoList.spyneShareUsers": {
$concatArrays: [
{
$filter: {
input: "$spyneSharePhotoList.spyneShareUsers",
cond: { $ne: ["$$this._id", shareUserProject.ShareUserId] }
}
},
[{
_id: shareUserProject.ShareUserId,
selectedByClient: { $in: [shareUserProject.ShareUserId, "$spyneSharePhotoList.spyneShareUsers._id"] }
}]
]
}
}
}
]
}
},
{ $unwind: "$root" },
{ $unwind: "$update" },
{
$set: {
"root.spyneSharePhotoList": {
$concatArrays: [
["$update.spyneSharePhotoList"],
{
$filter: {
input: "$root.spyneSharePhotoList",
cond: { $ne: ["$$this._id", shareUserProject.PhotoListId] }
}
}
]
}
}
},
{ $replaceRoot: { newRoot: "$root" } }
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
I did not check whether all operators are available in MongoDB 3.5
My goal was to process everything in aggregation pipeline and run just a single replaceOne() at the end.
Here another solution based on $map operator:
db.collection.aggregate([
{ $match: { _id: shareUserProject.id } },
{
$set: {
spyneSharePhotoList: {
$map: {
input: "$spyneSharePhotoList",
as: "photoList",
in: {
$cond: {
if: { $eq: [ "$$photoList._id", shareUserProject.PhotoListId ] },
then: {
"_id": "$$photoList._id",
spyneShareUsers: {
$cond: {
if: { $in: [ shareUserProject.ShareUserId, "$$photoList.spyneShareUsers._id" ] },
then: {
$map: {
input: "$$photoList.spyneShareUsers",
as: "shareUsers",
in: {
$cond: {
if: { $eq: [ "$$shareUsers._id", shareUserProject.ShareUserId ] },
then: { _id: shareUserProject.ShareUserId, selectedByClient: true },
else: "$$shareUsers"
}
}
}
},
else: {
$concatArrays: [
"$$photoList.spyneShareUsers",
[ { _id: shareUserProject.ShareUserId, selectedByClient: false } ]
]
}
}
}
},
else: "$$photoList"
}
}
}
}
}
}
]).forEach(function (doc) {
db.collection.replaceOne({ _id: doc._id }, doc);
})
You can achieve the same result also with two updates:
shareUserProject = {
id: "575",
PhotoListId: "fxLO68XyMR_x",
ShareUserId: "chittaranjan_new#gmail.com"
}
ret = db.collection.updateOne(
{ _id: shareUserProject.id },
{ $pull: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)
db.collection.updateOne(
{ _id: shareUserProject.id },
{ $push: { "spyneSharePhotoList.$[photoList].spyneShareUsers": { _id: shareUserProject.ShareUserId, selectedByClient: ret.modifiedCount == 1 } } },
{ arrayFilters: [{ "photoList._id": shareUserProject.PhotoListId }] }
)

MongoDB aggregate, how to addToSet each element of array in group pipeline

I have documents that contains a tags fields. It's a simple array with tag names inside, no object nor _id inside.
Just plain tags like this ["Protocol", "Access", "Leverage", "Capability"].
And in my group pipeline I tried something like 'selectedTags': { $addToSet: '$tags' } but then I end up with an array containing arrays of tags. And I get the same with $push.
I tried to use $each or $pushAll but they are not supported as grouping operator as my shell tell me.
Can someone help me on this one please ?
Thank you
Edit:
Sample docs:
{
"_id" : "HWEdDGsq86x4ikDSQ",
"teamId" : "AdLizGnPuqbWNsFHe",
"ownerId" : "Qb5EigWjqn2t3bfxD",
"type" : "meeting",
"topic" : "Grass-roots hybrid knowledge user",
"fullname" : "Guidouil",
"startDate" : ISODate("2017-07-30T09:00:05.513Z"),
"shareResults" : true,
"open" : true,
"language" : "fr",
"tags" : [
"Protocol",
"Challenge",
"Artificial Intelligence",
"Capability"
],
"isDemo" : true,
"createdAt" : ISODate("2017-11-15T19:24:05.513Z"),
"participantsCount" : 10,
"ratersCount" : 10,
"averageRating" : 3.4,
"hasAnswers" : true,
"updatedAt" : ISODate("2017-11-15T19:24:05.562Z")
}
{
"_id" : "rXvkFndpXwJ6KAvNo",
"teamId" : "AdLizGnPuqbWNsFHe",
"ownerId" : "Qb5EigWjqn2t3bfxD",
"type" : "meeting",
"topic" : "Profit-focused modular system engine",
"fullname" : "Guidouil",
"startDate" : ISODate("2017-07-24T12:00:05.564Z"),
"shareResults" : true,
"open" : true,
"language" : "fr",
"tags" : [
"Initiative",
"Artificial Intelligence",
"Protocol",
"Utilisation"
],
"isDemo" : true,
"createdAt" : ISODate("2017-11-15T19:24:05.564Z"),
"participantsCount" : 33,
"ratersCount" : 33,
"averageRating" : 2.9393939393939394,
"hasAnswers" : true,
"updatedAt" : ISODate("2017-11-15T19:24:05.753Z")
}
Aggregation:
db.surveys.aggregate(
{ $match: query },
{
$group: {
'_id': {
'year': { $year: '$startDate' },
'day': { $dayOfYear: '$startDate' },
},
'participants': { $sum: '$ratersCount' },
'rating': { $avg: '$averageRating' },
'surveys': { $push: '$_id' },
'selectedTags': { $addToSet: '$tags' },
'peoples': { $addToSet: '$fullname' },
}
},
{ $sort: { _id: 1 } }
);
then I tried to change the selectedTags to { $push: { $each: '$tags' } } or { $pushAll: '$tags' } but this does not execute :(
Edit 2:
In javascript I do it like that:
return Surveys.aggregate(
{ $match: query },
{ $group: {
_id: dateGroup,
participants: { $sum: '$ratersCount' },
rating: { $avg: '$averageRating' },
surveys: { $push: '$_id' },
selectedTags: { $push: '$tags' },
peoples: { $addToSet: '$fullname' },
} },
{ $project: {
_id: null,
selectedTags: {
$reduce: {
input: "$selectedTags",
initialValue: [],
in: { $setUnion: ["$$value", "$$this"] }
}
},
} }
);
To mimic functionality of $addToSet update operator with $each modifier in aggregation pipeline you can use a combination of $push on grouping stage and $reduce + $setUnion on projection stage. E.g.:
db.collection.aggregate([
{$group:{
_id: null,
selectedTags: { $push: '$tags' }
}},
{$project: {
selectedTags: { $reduce: {
input: "$selectedTags",
initialValue: [],
in: {$setUnion : ["$$value", "$$this"]}
}}
}}
])
results with a single document which contains a distinct list of tags from all documents in selectedTags array.
You can also use $unwind to get result:
db.collection.aggregate([
{$unwind: "$tags"},
{$group:{
_id: null,
selectedTags: { $addToSet: '$tags' }
}}
])
Dannyxu and Alex Beck's answers both worked, but only partially when used with a group stage. I needed to combine both to get the desired result of a single flat array of tags:
Model.aggregate()
.match({ /** some query */ })
.group({
_id: '$teamId',
tagsSet: { $push: '$tags' },
numRecords: { $sum: 1 },
})
.project({
_id: 0,
numRecords: 1,
tagsSet: {
$reduce: {
input: '$tagsSet',
initialValue: [],
in: { $setUnion: ['$$value', '$$this'] },
},
},
})
.unwind({ path: '$tagsSet' })
.group({
_id: null,
selectedTags: { $addToSet: '$tagsSet' },
numRecords: { $sum: '$numRecords' },
})

Resources