MongoDB - double $group make 2nd ObjectID a nested document - mongodb

I have a collection that after unwind has this structure (I've deleted information which I think is not relevant to the question)
{
"_id" : ObjectId("1"),
"Members" : {
"City" : "New York"
},
"Group_name" : "Group A"
}
{
"_id" : ObjectId("2"),
"Members" : {
"City" : "Seattle"
},
"Group_name" : "Group A"
}
{
"_id" : ObjectId("3"),
"Members" : {
"City" : "Seattle"
},
"Group_name" : "Group A"
}
{
"_id" : ObjectId("4"),
"Members" : {
"City" : "New York"
},
"Group_name" : "Group B"
}
{
"_id" : ObjectId("5"),
"Members" : {
"City" : "Los Angeles"
},
"Group_name" : "Group B"
}
{
"_id" : ObjectId("6"),
"Members" : {
"City" : "Los Angeles"
},
"Group_name" : "Group B"
}
I have used double Object Id to get a result like this:
{ "_id" : { "group" : "A", "city" : "New York" }, "totalMembers" : 1 }
{ "_id" : { "group" : "A", "city" : "Seattle" }, "totalMembers" : 2 }
{ "_id" : { "group" : "B", "city" : "New York" }, "totalMembers" : 1 }
{ "_id" : { "group" : "B", "city" : "Los Angeles" }, "totalMembers" : 2 }
I want to be able to obtain a document with the following structure:
{
"_id" : "A",
"Cities" : {
"New York" : 1,
"Seattle" : 2
}
}
{
"_id" : "B",
"Cities" : {
"New York" : 1,
"Los Angeles" : 2
}
}
This is my code so far, I haven't been able to group by 'group' and then by 'city'
db.Users_Group.aggregate([
{"$unwind":"$Members"},
{"$group":{"_id":{"group":"$Group_Name","City":"$Members.City"},"totalUsers":{"$sum":1}}},
{"$group":{"_id":"$_id.grupo","total":{"$sum":1}}}
] )
With this I get the sum of all members in that group not separated by cities. How can I nest a document of the cities with the total of users of that city within each group? Appreciate any help on this. Thanks in advance.

You need to run one more $group and prepare the data for $arrayToObject which takes an array of k-v pairs:
db.collection.aggregate([
// your current aggregation stages
{
$group: {
_id: "$_id.group",
Cities: { $push: { k: "$_id.city", v: "$totalMembers" } }
}
},
{
$project: {
_id: 1,
Cities: { $arrayToObject: "$Cities" }
}
}
])
Mongo Playground

Related

MongoDB - Get last date of every distinct name

I am starting MongoDB and have problems about how to create a query to filter documents by last date of every distinct name and retrieve the whole document.
I have some data into my collection (students):
{ "_id" : ObjectId("61479d4bc146b1663a8f2b7d"), "city" : "SAO PAULO", "name" : "ANA", "status" : "ACTIVE", "date1" : ISODate("2020-09-01T08:14:30.000Z") }
{ "_id" : ObjectId("61479d88c146b1663a8f2b7e"), "city" : "SAO PAULO", "name" : "MARIA", "status" : "ACTIVE", "date1" : ISODate("2020-08-01T04:16:00.000Z") }
{ "_id" : ObjectId("61479dc2c146b1663a8f2b7f"), "city" : "RIO DE JANEIRO", "name" : "MARIA", "status" : "ACTIVE", "date1" : ISODate("2021-02-01T11:10:00.000Z") }
{ "_id" : ObjectId("61479df1c146b1663a8f2b80"), "city" : "SAO PAULO", "name" : "MARIA", "status" : "INACTIVE", "date1" : ISODate("2021-02-01T11:15:00.000Z") }
{ "_id" : ObjectId("61479e60c146b1663a8f2b81"), "city" : "BRASILIA", "name" : "JOHH", "status" : "ACTIVE", "date1" : ISODate("2021-06-01T01:18:00.000Z") }
I'm creating a query to filter status "ACTIVE" and show only most recent data for each student, showing only "city", "name", "date" and I'm trying this one using $MAX or $LAST into the GROUP:
db.getCollection('students').aggregate([
{ $match: { status: "ACTIVE" } },
{ $group: { _id: { name : "$name"},
date1 : { $max : "$date1" } ,
city : { $max : "$city" } } }
])
The wanted result:
{ "city" : "SAO PAULO", "name" : "ANA", "date1" : ISODate("2020-09-01T08:14:30.000Z") }
{ "city" : "RIO DE JANEIRO", "name" : "MARIA", "date1" : ISODate("2021-02-01T11:10:00.000Z") }
{ "city" : "BRASILIA", "name" : "JOHH", "date1" : ISODate("2021-06-01T01:18:00.000Z") }
But the result is this:
{ "city" : "SAO PAULO", "name" : "ANA", "date1" : ISODate("2020-09-01T08:14:30.000Z") }
{ "city" : "SAO PAULO", "name" : "MARIA", "date1" : ISODate("2021-02-01T11:10:00.000Z") }
{ "city" : "BRASILIA", "name" : "JOHH", "date1" : ISODate("2021-06-01T01:18:00.000Z") }
It is retrieving wrong data. For ANA and JOHN (only one document each) it's ok. But MARIA has three documents and I need to retrieve all data from her document with the $max date and I'm retrieving "city" : "SAO PAULO" rather than "city" : "RIO DE JANEIRO" because operator $MAX is applied for this field too. That is applied for all fields and the GROUP operator does not allow removing the MAX operator.
I don't know to fix it.
How to get whole document, filtering by "last date of every distinct name" ?
You can use this aggregation pipeline:
First $match as you have.
Then $sort to get desired values in first position. This is used by next stage.
Into $group aggregation you get the $first value (as the document is sorted, the first value will be the desired one).
And last $project to get desired output.
db.collection.aggregate([
{
"$match": {
"status": "ACTIVE"
}
},
{
"$sort": {
"date1": -1
}
},
{
"$group": {
"_id": {
"name": "$name"
},
"date1": {
"$first": "$date1"
},
"city": {
"$first": "$city"
}
}
},
{
"$project": {
"_id": 0,
"name": "$_id.name",
"city": 1,
"date1": 1
}
}
])
Example here

Combining 2 collections after filtering 2nd one based on ids from first one

I have 2 collections like this:
Batches:
{
"userId": "",
"productId: "",
}
Products:
{
"_id": "",
"name": "",
}
What i want to do is filter the batches by userId first. And then get all products which i get ids in filtered elements of batches. I have seen a lot of examples but mostly goes the opposite way.
My final result i would like to look like this:
[{
name: "product 1",
batches: [...]
}]
You can try lookup with aggregation pipeline,
$lookup in branches collection, pass productId in let, and match expression condition for for productId and filter userId
db.products.aggregate([
{
$lookup: {
from: "branches",
let: { productId: "$_id" },
pipeline: [
{
$match: {
userId: "1", // filter user id here
$expr: { $eq: ["$$productId", "$productId"] }
}
}
],
as: "brnaches"
}
}
])
Playground
Maybe something like this:
mongos> db.Batches.find()
{ "_id" : ObjectId("5ff9a66fda146da4e1359dc9"), "userId" : "User1", "productId" : "product1" }
{ "_id" : ObjectId("5ff9a675da146da4e1359dca"), "userId" : "User2", "productId" : "product1" }
{ "_id" : ObjectId("5ff9a682da146da4e1359dcb"), "userId" : "User3", "productId" : "product2" }
{ "_id" : ObjectId("5ff9a6deda146da4e1359dcc"), "userId" : "User3", "productId" : "product1" }
mongos> db.Products.find()
{ "_id" : "product1", "name" : "product 1" }
{ "_id" : "product2", "name" : "product 2" }
{ "_id" : "product3", "name" : "product 3" }
{ "_id" : "product4", "name" : "product 4" }
mongos> db.Batches.aggregate([ {$match:{"userId":{$in:["User3","User1"]}}}, { $lookup:{ from:"Products" , localField:"productId" , foreignField:"_id" , as:"match" } } , {$unwind:"$match"} , {$project:{ name:"$match.name" , batches :{userId:"$userId" , productId:"$productId"} }} , {$group:{_id:"$name" , batches:{$addToSet:"$batches"}}} , {$project:{_id:0, name:"$_id" , batches:1}} ])
{ "batches" : [ { "userId" : "User1", "productId" : "product1" }, { "userId" : "User3", "productId" : "product1" } ], "name" : "product 1" }
{ "batches" : [ { "userId" : "User3", "productId" : "product2" } ], "name" : "product 2" }
mongos>

Mongo aggregation, project a subfield of the first element in the array

I have a sub collection of elements and I want to project a certain subfield of the FIRST item in this collection. I have the following but it only projects the field for ALL elements in the array.
Items is the subcollection of Orders and each Item object has a Details sub object and an ItemName below that. I want to only return the item name of the FIRST item in the list. This returns the item name of every item in the list.
How can I tweak it?
db.getCollection('Orders').aggregate
([
{ $match : { "Instructions.1" : { $exists : true }}},
{ $project: {
_id:0,
'UserId': '$User.EntityId',
'ItemName': '$Items.Details.ItemName'
}
}
]);
Updated:
{
"_id" : "order-666156",
"State" : "ValidationFailed",
"LastUpdated" : {
"DateTime" : ISODate("2017-09-26T08:54:16.241Z"),
"Ticks" : NumberLong(636420128562417375)
},
"SourceOrderId" : "666156",
"User" : {
"EntityId" : NumberLong(34450),
"Name" : "Bill Baker",
"Country" : "United States",
"Region" : "North America",
"CountryISOCode" : "US",
},
"Region" : null,
"Currency" : null,
"Items" : [
{
"ClientOrderId" : "18740113",
"OrigClientOrderId" : "18740113",
"Quantity" : NumberDecimal("7487.0"),
"TransactDateTime" : {
"DateTime" : Date(-62135596800000),
"Ticks" : NumberLong(0)
},
"Text" : null,
"LocateRequired" : false,
"Details" : {
"ItemName" : "Test Item 1",
"ItemCost" : 1495.20
}
},
{
"ClientOrderId" : "18740116",
"OrigClientOrderId" : "18740116",
"Quantity" : NumberDecimal("241.0"),
"TransactDateTime" : {
"DateTime" : Date(-62135596800000),
"Ticks" : NumberLong(0)
},
"Text" : null,
"LocateRequired" : false,
"Details" : {
"ItemName" : "Test Item 2",
"ItemCost" : 2152.64
}
}
]
}
In case your are using at least MongoDB v3.2 you can use the $arrayElemAt operator for that. The below query does what you want. It will, however, not return any data for the sample you provided because the "Instructions.1": { $exists: true } filter removes the sample document.
db.getCollection('Orders').aggregate([{
$match: {
"Instructions.1": {
$exists: true
}
}
}, {
$project: {
"_id": 0,
"UserId": "$User.EntityId",
"ItemName": { $arrayElemAt: [ "$Items.Details.ItemName", 0 /* first item! */] }
}
}])

MongoDB query with multiple conditions

I have data with multiple documents :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e487"),
"empId" : "2"
"type" : "Admin",
"city" : "Mumbai"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e488"),
"empId" : "3"
"type" : "Admin",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e489"),
"empId" : "4"
"type" : "User",
"city" : "Mumbai"
}
I want to get data according to my multiple conditions :
condition 1:- {"type" : "WebUser", "city" : "Pune"}
condition 2:- {"type" : "WebUser", "city" : "Pune"} & {"type" : "User", "city" : "Mumbai"}
I want below result when run condition 1 :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
When I run second condition :
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e486"),
"empId" : "1"
"type" : "WebUser",
"city" : "Pune"
}
{
"_id" : ObjectId("57b68dbbc19c0bd86d62e489"),
"empId" : "4"
"type" : "User",
"city" : "Mumbai"
}
I want above result by one query,
Currently I am using below aggregate query,
db.emp.aggregate([
{ $match: { '$and': [
{"type" : "WebUser", "city" : "Pune"},
{"type" : "User", "city" : "Mumbai"}
] } },
{ $group: { _id: 1, ids: { $push: "$empId" } } }
])
Above query work for first condition & fails for other. Please help me.
For the second condition, you can use the $in operator in your query as:
db.emp.find({
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
})
If you want to use in aggregation:
db.emp.aggregate([
{
"$match": {
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
}
},
{ "$group": { "_id": null, "ids": { "$push": "$empId" } } }
])
or simply use the distinct() method to return an array of distinct empIds that match the above query as:
var employeeIds = db.emp.distinct("empId", {
"type" : { "$in": ["WebUser", "User"] },
"city" : { "$in": ["Pune", "Mumbai"] }
});
If you are looking for the AND operator
This example checks if a field exists AND is null
db.getCollection('TheCollection').find({
$and: [
{'the_key': { $exists: true }},
{'the_key': null}
]
})
This example checks if a field has 'value1' OR 'value2'
db.getCollection('TheCollection').find({
$or: [
{'the_key': 'value1'},
{`the_key': 'value2'}
]
})
When just checking for null, the return contains non-existing fields plus fields with value null
db.getCollection('TheCollection').find({'the_key': null})
You can use mongo db $or operator.
db.emp.find({ $or: [
{ "type": "WebUser", "city": "Pune" },
{ "type": "user", "city": "Mumbai"}
]})
You can pass conditions in the array.
For more reference see mongo docs
Display the document where in the “StudName” has value “Ajay Rathod”.
db.Student.find({name:"ajay rathod"})
{ "_id" : ObjectId("5fdd895cd2d5a20ee8cea0de"), "
Retrieve only Student Name and Grade.
db.Student.find({},{name:1,grade:1,_id:0})
{ "name" : "dhruv", "grade" : "A" }
{ "name" : "jay", "grade" : "B" }
{ "name" : "abhi", "grade" : "C" }
{ "name" : "aayush", "grade" : "A" }
{ "name" : "sukhdev", "grade" : "B" }
{ "name" : "dhruval", "grade" : "B" }
{ "name" : "ajay rathod", "grade" : "D" }

How to find count of a key in one document using mongoDB?

I have following structure in my collection:
users:[
{
"name":"ABC",
"address":{
"city":"London",
"country":"UK",
}
},
{
"name":"XYZ",
"address":{
"city":"London",
"country":"UK",
}
},
{
"name":"PQR",
"address":{
"city":"NewYork",
"country":"US",
}
}
]
I want count of number of occurrences of 'city' key in 'address' and 'name' as a result.
I want to query above collection and want following output:
[{
"name":"ABC",
"city":"London",
"count":2
},{
"name":"XYZ",
"city":"London",
"count":2
}, {
"name":"PQR",
"city":"NewYork",
"count":1
}
]
I simulated your collection
{
"_id" : ObjectId("547c30ae371ea419f07b9550"),
"users" : [
{
"name" : "ABC",
"address" : {
"city" : "London",
"country" : "UK"
}
},
{
"name" : "XYZ",
"address" : {
"city" : "London",
"country" : "UK"
}
},
{
"name" : "PQR",
"address" : {
"city" : "NewYork",
"country" : "US"
}
}
]
}
And then I use aggregate framework
db.coll.aggregate([
{
$unwind:"$users"
},
{
$group:{
_id:"$users.address.city",
name:{$push:"$users.name"},
city:{$first:"$users.address.city"},
count:{$sum:1}
}
},{
$unwind:"$name"
},{
$project:{
_id:0,
"city":"$_id",
"name":1,
"city":1,
"count":1
}
}])
result:
{
"result" : [
{
"name" : "PQR",
"city" : "NewYork",
"count" : 1
},
{
"name" : "ABC",
"city" : "London",
"count" : 2
},
{
"name" : "XYZ",
"city" : "London",
"count" : 2
}
],
"ok" : 1
}
UPDATE AFTER QUESTION
I added a new Document
{
"_id" : ObjectId("547c394c371ea419f07b9551"),
"users" : [
{
"address" : {
"city" : "Livorno",
"country" : "LI"
}
},
{
"address" : {
"city" : "Livorno",
"country" : "LI"
}
},
{
"address" : {
"city" : "NewYork",
"country" : "US"
}
}
]
}
and new Query
db.coll.aggregate([
{
$unwind:"$users"
},
{
$group:{
_id:"$users.address.city",
"name": {
$push:{"$ifNull": ["$users.name","$_id"]}
},
city:{$first:"$users.address.city"},
count:{$sum:1}
}
},{
$unwind:"$name"
},{
$project:{
_id:0,
"city":"$_id",
"name":1,
"city":1,
"count":1
}
}])
Result:
{
"result" : [
{
"name" : "PQR",
"city" : "NewYork",
"count" : 2
},
{
"name" : ObjectId("547c394c371ea419f07b9551"),
"city" : "NewYork",
"count" : 2
},
{
"name" : ObjectId("547c394c371ea419f07b9551"),
"city" : "Livorno",
"count" : 2
},
{
"name" : ObjectId("547c394c371ea419f07b9551"),
"city" : "Livorno",
"count" : 2
},
{
"name" : "ABC",
"city" : "London",
"count" : 2
},
{
"name" : "XYZ",
"city" : "London",
"count" : 2
}
],
"ok" : 1
}