I would like to get the very first department created for each company, however, I'm confused with the aggregate query.
Documents:
[
{
_id: "5b7579f2deea1c6e46fd9739",
name: "Sales",
companyId: "123",
},
{
_id: "5c5779f1dffe1c6e45df3973",
name: "Security",
companyId: "123",
},
{
_id: "5d9759f5ceda1c6e64df9772",
name: "Human Resource",
companyId: "789",
},
]
I'm expecting a result like this:
Expected Result:
[
{
_id: "5b7579f2deea1c6e46fd9739",
name: "Sales",
companyId: "123",
},
{
_id: "5d9759f5ceda1c6e64df9772",
name: "Human Resource",
companyId: "789",
},
]
But I'm getting only one result with my query.
Actual Result:
[
{
_id: "5b7579f2deea1c6e46fd9739",
name: "Sales",
companyId: "123",
},
]
Aggregate Query:
db.getCollection('departments').aggregate([
{
$sort:{ item: 1 }
},
{
$group: {
_id:'$item',
companyId: { $first:'$companyId'},
name: { $first:'$name'},
}
}
])
You need to group by companyId field like this:
db.departments.aggregate([
{
$group: {
_id: "$companyId",
doc: {
$first: "$$ROOT"
}
}
},
{
$replaceRoot: {
newRoot: "$doc"
}
}
])
Playground
If you have a natural sort field like a date field, it would be good to apply sort stage on that field before the group stage.
Related
I have below data in my collection:
[
{
"_id":{
"month":"Jan",
"year":"2022"
},
"products":[
{
"product":"ProdA",
"status":"failed",
"count":15
},
{
"product":"ProdA",
"status":"success",
"count":5
},
{
"product":"ProdB",
"status":"failed",
"count":20
},
{
"product":"ProdB",
"status":"success",
"count":10
}
]
},
...//more such data
]
I want to group the elements of products array on the name of the product, so that we have record of how what was the count of failure of success of each product in each month. Every record is guaranteed to have both success and failure count each month. The output should look like below:
[
{
"_id":{
"month":"Jan",
"year":"2022"
},
"products":[
{
"product":"ProdA","status":[{"name":"success","count":5},{"name":"failed","count":15}]
},
{
"product":"ProdB","status":[{"name":"success","count":10},{"name":"failed","count":20}]
}
]
},
...//data for succeeding months
]
I have tried to do something like this:
db.collection.aggregate([{ $unwind: "$products" },
{
$group: {
"_id": {
month: "$_id.month",
year: "$_id.year"
},
products: { $push: { "product": "$product", status: { $push: { name: "$status", count: "$count" } } } }
}
}]);
But above query doesn't work.
On which level I need to group fields so as to obtain above output.
Please help me to find out what I am doing wrong.
Thank You!
Your first group stage needs to group by both the _id and the product name, aggregate a list of status counts and then another group stage which then forms the products list:
db.collection.aggregate([
{$unwind: "$products"},
{$group: {
_id: {
id: "$_id",
product: "$products.product",
},
status: {
$push: {
name: "$products.status",
count: "$products.count"
}
}
}
},
{$group: {
_id: "$_id.id",
products: {
$push: {
product: "$_id.product",
status: "$status"
}
}
}
}
])
Mongo Playground
Considering the following document structure:
{_id: 1, name: 'joe', snapshot: null, age: 30}
{_id: 2, name: 'joe', snapshot: 'snapshot1', age: 30}
{_id: 3, name: 'joe', snapshot: 'snapshot15', age: 30}
{_id: 4, name: 'joe', snapshot: 'snapshot23', age: 30}
How would I perform a query that groups on the name field and adds an additional field that is a count of the remaining records containing subtree: 'additionalinfo'. It would look like this:
{_id: 1, name: 'joe', snapcount: 3, age: 30}
I've been able to group using aggregations but I can't quite get it like this.
My own solution:
I ultimately restructured my data to look like this instead:
{
_id: 1,
name: 'joe',
snapshots: [
{name: 'snap17', id: 1},
{name: 'snap15', id: 2},
{name: 'snap14', id: 3}
],
age: 30
}
This allows me to just check snapshots.length to solve my original problem. However; the answers in this post where very helpful and answered the original question.
Adding another aggregation query to do it: playground link: try it
db.collection.aggregate([
{
$match: {
"snapshot": {
$exists: true,
$ne: null
}
}
},
{
$group: {
_id: "$name",
snapcount: {
$sum: 1
},
age: {
"$first": "$age"
},
name: {
"$first": "$name"
}
}
},
{
"$unset": "_id"
}
])
Based on the comments, the query worked for OP:
db.collection.aggregate([
{
$match: {
"snapshot": {
$exists: true,
$ne: null
}
}
},
{
$group: {
_id: "$name",
snapcount: {
$sum: 1
},
age: {
"$first": "$age"
},
name: {
"$first": "$name"
},
id: {
"$first": "$_id"
}
}
},
{
"$unset": "_id"
}
])
Here's one way you could do it.
db.collection.aggregate([
{
"$group": {
"_id": "$name",
"name": {"$first": "$name"},
"age": {"$first": "$age"},
"snapcount": {
"$sum": {
"$cond": [
{"$eq": [{"$type": "$snapshot"}, "string"]},
1,
0
]
}
}
}
},
{"$unset": "_id"}
])
Try it on mongoplayground.net.
I am using MongoDB 4.2.9 and have the following requirements:
Collection 'A' has multiple documents with a string field 'status' that I need to filter on
Collection 'B' has multiple documents
Collection A
{ _id: "1",
status: "Report",
type: "Academy",
rating: "Excellent",
ReportNo: "A1"
},
{ _id: "2",
status: "Open",
type: "Academy",
rating: "",
ReportNo: ""
},
{ _id: "3",
status: "Draft",
type: "Academy",
rating: "",
ReportNo: ""
},
{ _id: "4",
status: "Report",
type: "Academy",
rating: "Great",
ReportNo: "A4"
}
Collection B
{ _id: "98",
status: "Archived",
type: "Academy",
rating: "So So",
ReportNo: "X2"
},
{ _id: "99",
status: "Archived",
type: "Academy",
rating: "Great",
ReportNo: "X1"
}
Resulting View
{ _id: "1",
status: "Report",
type: "Academy",
rating: "Excellent",
ReportNo: "A1"
},
{ _id: "4",
status: "Report",
type: "Academy",
rating: "Great",
ReportNo: "A4"
},
{ _id: "98",
status: "Archived",
type: "Academy",
rating: "So So",
ReportNo: "X2"
},
{ _id: "99",
status: "Archived",
type: "Academy",
rating: "Great",
ReportNo: "X1"
}
My goal is to create an aggregation view so that I can filter on a status value in Collection 'A' and then merge those results with Collection 'B' and show in the view ?
I can filter on Collection 'A' using the match call, just can't see how to merge resulting documents into Collection 'B'
From my understandings, your "merge" behaviour is actually a union view of filtered view of collection A and collection B.
With MongoDB v4.2, you can use $facet to handle collection A and collection B separately.
simply perform filtering on A
perform uncorrelated $lookup on B
wrangle the result and merge them together to get the union view that you are looking for.
db.createCollection(
"unionView",
{
"viewOn" : "A",
"pipeline" : [
{
"$facet": {
"A": [
{
"$match": {
status: "Report"
}
}
],
"B": [
{
$limit: 1
},
{
"$lookup": {
"from": "B",
"pipeline": [],
"as": "B"
}
},
{
$unwind: "$B"
},
{
"$replaceRoot": {
"newRoot": "$B"
}
}
]
}
},
{
$project: {
all: {
"$setUnion": [
"$A",
"$B"
]
}
}
},
{
$unwind: "$all"
},
{
"$replaceRoot": {
"newRoot": "$all"
}
}
]
}
)
Here is the Mongo Playground for your reference.
With MongoDB v4.4+, you can create a view with $unionWith
db.createCollection(
"unionView",
{
"viewOn" : "A",
"pipeline" : [
{
"$match": {
status: "Report"
}
},
{
"$unionWith": {
"coll": "B"
}
}
]
}
)
Here is the Mongo playground for your reference.
I have the following collection:
{
_id: 1,
type: "Feature",
properties: {
name: "Name 1",
<other properties here>
}
},
{
_id: 2,
type: "Feature",
properties: {
name: "Name 2",
<other properties here>
}
}
How can I write a MongoDB Aggregate query to return the following ?
{
data: {
type: "Feature",
properties: {
_id: 1,
name: "Name 1",
<other properties here>
}
}
},
{
data: {
type: "Feature",
properties: {
_id: 2,
name: "Name 2",
<other properties here>
}
}
}
So, the _id should become an attribute of properties and each document should be returned as an object of data.
Simple structure manipulation will suffice:
db.collection.aggregate([
{
$project: {
data: {
type: "$type",
properties: {
$mergeObjects: [
{_id: "$_id"},
"$properties"
]
}
}
}
}
])
try this
1- If you need only the _id to be inside the properties, and not in data object
db.collection.aggregate([
{
$group: {
_id: "$_id",
type: {
$first: "$type"
},
properties: {
$first: {
_id: "$_id",
name: "$properties.name"
}
}
}
},
{
$project: {
_id: 0,
data: "$$ROOT"
}
},
{
$project: {
"data._id": 0
}
}
])
check this Mongo Playground
2- If it's matter only to get the _id inside the the properties object, and you don't care if it's in data or not
you can use this
db.collection.aggregate([
{
$group: {
_id: "$_id",
type: {
$first: "$type"
},
properties: {
$first: {
_id: "$_id",
name: "$properties.name"
}
}
}
},
{
$project: {
_id: 0,
data: "$$ROOT"
}
}
])
check this Mongo Playground 2
I was unable to utilize your dataset as it was not RFC 8259 formatted.
However assuming your dataset is properly formatted like below. The issued command would be near close provided the fields matched
{
"data":{
"Feature":{
"properties":{
"_id1":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
],
"_id2":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
],
"_id3":[
"object_of_data1",
"object_of_data2",
"object_of_data3"
]
}
}
}
}
I would then use the command:
db.orders.aggregate([
{ $match: { properties: "_id1" } },
{ $group: { _id1: "object_of_data1", "object_of_data2"
"object_of_data3" } } }
])
For further reading please see: Aggregation Commands Comparison
My first collection employeecategory is like below;
[{
name: "GARDENING"
},
{
name: "SECURITY"
},
{
name: "PLUMBER"
}
]
My second collection complaints is like below;
[{
communityId: 1001,
category: "SECURITY",
//other fields
}, {
communityId: 1001,
category: "GARDENING",
//other fields
}]
I am trying to join above tables and get the below result;
[{
"count": 1,
"name": "GARDENING"
}, {
"count": 1,
"name": "SECURITY"
}, {
"count": 0,
"name": "PLUMBER"
}]
Even if there are no records in collection 2 I need count. I tried below aggregation but didn't worked. If I removed match condition it is working but I need to filter on community id. Could some please suggest best way to do achieve this. Mongo DB version is 3.4.0
db.employeecategory.aggregate(
[{
$match: {
"complaints.communityId": 1001
}
}, {
"$lookup": {
from: "complaints",
localField: "name",
foreignField: "category",
as: "embeddedData"
}
}]
)
It was not possible to achieve both filtering for communityId = 1001 and grouping without losing count = 0 category in a single aggregation. The way to do it is first start from complaints collection, and filter the communityId = 1001 objects, and create a temp collection with it. Then from employeecategory collection, $lookup to join with that temp collection, and $group with name, you will have your result at this point, then drop the temp table.
// will not modify complaints document, will create a filtered temp document
db.complaints.aggregate(
[{
$match: {
communityId: 1001
}
},
{
$out: "temp"
}
]
);
// will return the answer that is requested by OP
db.employeecategory.aggregate(
[{
$lookup: {
from: "temp",
localField: "name",
foreignField: "category",
as: "array"
}
}, {
$group: {
_id: "$name",
count: {
$sum: {
$size: "$array"
}
}
}
}]
).pretty();
db.temp.drop(); // to get rid of this temporary collection
will result;
{ _id: "PLUMBER", count: 0},
{ _id: "SECURITY", count: 2},
{ _id: "GARDENING", count: 1}
for the test data I've had;
db.employeecategory.insertMany([
{ name: "GARDENING" },
{ name: "SECURITY" },
{ name: "PLUMBER" }
]);
db.complaints.insertMany([
{ category: "GARDENING", communityId: 1001 },
{ category: "SECURITY", communityId: 1001 },
{ category: "SECURITY", communityId: 1001 },
{ category: "SECURITY", communityId: 1002 }
]);