How to group and a count fields in a mongoDB collection - mongodb

I'm really new to mongodb coming from a sql background and struggling to work out how to run a simple report that will group a value from a nested document with a count and in a sort order with highest count first.
I've tried so many ways from what I've found online but I'm unable to target the exact field that I need for the grouping.
Here is the collection.
{
"_id": {
"$oid": "6005f95dbad14c0308f9af7e"
},
"title": "test",
"fields": {
"6001bd300b363863606a815e": {
"field": {
"_id": {
"$oid": "6001bd300b363863606a815e"
},
"title": "Title Two",
"datatype": "string"
},
"section": "Section 1",
},
"6001bd300b363863423a815e": {
"field": {
"_id": {
"$oid": "6001bd3032453453606a815e"
},
"title": "Title One",
"datatype": "string"
},
"section": "Section 1",
},
"6001bd30453534863423a815e": {
"field": {
"_id": {
"$oid": "6001bd300dfgdfgdf06a815e"
},
"title": "Title One",
"datatype": "string"
},
"section": "Section 1",
}
},
"sections": ["Section 1"]
}
The result I need to get from the above example would be:
"Title One", 2
"Title Two", 1
Can anyone please point me in the right direction? Thank you so much.

Having dynamic field names is usually a poor design.
Try this one:
db.collection.aggregate([
{ $set: { fields: { $objectToArray: "$fields" } } },
{ $unwind: "$fields" },
{ $group: { _id: "$fields.v.field.title", count: { $count: {} } } },
{ $sort: { count: -1 } }
])

Here's another way to do it. The $project throws away everything except for the deep-dive to "title". Then just $unwind and $sortByCount.
db.collection.aggregate([
{
"$project": {
"titles": {
"$map": {
"input": {
"$objectToArray": "$fields"
},
"in": "$$this.v.field.title"
}
}
}
},
{
"$unwind": "$titles"
},
{
"$sortByCount": "$titles"
}
])
Try it on mongoplayground.net.

Related

How to use current field in second $match?

Let's say i have 2 collections
// Post collection:
[
{
"_id": "somepost1",
"author": "firstuser",
"title": "First post"
},
{
"_id": "somepost2",
"author": "firstuser",
"title": "Second post"
},
{
"_id": "somepost3",
"author": "firstuser",
"title": "Third post"
}
]
// User collection:
[
{
"_id": "firstuser",
"nickname": "John",
"posts": {
"voted": []
}
},
{
"_id": "seconduser",
"nickname": "Bob",
"posts": {
"voted": [
{
"_id": "somepost1",
"vote": "1"
},
{
"_id": "somepost3",
"vote": "-1"
}
]
}
}
]
And i need to get this result:
[
{
"_id": "somepost1",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "First post",
"myvote": "1"
},
{
"_id": "somepost2",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "Second post",
"voted": "0"
},
{
"_id": "somepost3",
"author": {
"_id": "firstuser",
"nickname": "John"
},
"title": "Third post",
"myvote": "-1"
}
]
How can i make a request with aggregation, which will display this output with dynamic _id of elements?
I have problem with using current _id of post in second $match and setting "myvote" to 0 if there are no element in "posts.voted" associated with current post.
Here what i've tried: https://mongoplayground.net/p/v70ZUioVSpQ
db.post.aggregate([
{
$match: {
author: "firstuser"
}
},
{
$lookup: {
from: "user",
localField: "author",
foreignField: "_id",
as: "author"
}
},
{
$addFields: {
author: {
$arrayElemAt: [
"$author",
0
]
}
}
},
{
$lookup: {
from: "user",
localField: "_id",
foreignField: "posts.voted._id",
as: "Results"
}
},
{
$unwind: "$Results"
},
{
$unwind: "$Results.posts.voted"
},
{
$match: {
"Results.posts.voted._id": "ID OF CURRENT POST"
}
},
{
$project: {
_id: 1,
author: {
_id: 1,
nickname: 1
},
title: 1,
myvote: "$Results.posts.voted.vote"
}
}
])
From the $match docs:
The query syntax is identical to the read operation query syntax
The query syntax does not allow usage of document values. which is what you're trying to do.
What we can do is use $expr within the $match stage, this allows us to use aggregation oprerators, thus also giving access to the document values. like so:
{
$match: {
$expr: {
$eq: ['$Results.posts.voted._id', '$_id'],
}
},
},

How can i display count of related documents on parent level?

I'm trying to build a voting system where you can have X num of options to vote for on an entry.
The query I'm building now is when retrieving an entry, I would like to get the numbers of votes per option on an entry.
I have a very clear understanding of how I would do this in SQL but grasping to understand the concepts of aggregation, lookup, and group in MongoDB
The model looks like this:
Entries
{
"_id": "5fc2765938401a2308e18ac5",
"options": [
{
"name": "First Option"
"_id": "5fc2765938401a2308e18ac6",
},
{
"name": "Second Option"
"_id": "5fc2765938401a2308e18are",
},
{
"name": "Third Option"
"_id": "5fc2765938401a2308e18aef",
},
],
},
{
"_id": "5fc2766438401a2308e18ac8",
"options": [
{
"name": "Some other option"
"_id": "5fc2766438401a2308e18ac9",
},
{
"_id": "5fc2766438401a2308e18aca",
"name": "This is also an option"
}
],
}
Votes
{
"_id": "5fc2765938401a2308e18ac5",
"entryId": "5fc2765938401a2308e18ac6"
},
{
"_id": "5fc2765938401a2308e18aer",
"entryId": "5fc2765938401a2308e18are"
},
{
"_id": "5fc2765938401a2308e18ek",
"entryId": "5fc2765938401a2308e18ac6"
}
...
And I want the results of Entry to look like this.
{
"_id": "5fc2765938401a2308e18ac5",
"options": [
{
"name": "First Option"
"_id": "5fc2765938401a2308e18ac6",
"votes": 1,
},
{
"name": "Second Option"
"_id": "5fc2765938401a2308e18are",
"votes": 0,
},
{
"name": "Third Option"
"_id": "5fc2765938401a2308e18aef",
"votes": 5,
},
],
},
{
"_id": "5fc2766438401a2308e18ac8",
"options": [
{
"name": "Some other option"
"_id": "5fc2766438401a2308e18ac9",
"votes": 3,
},
{
"_id": "5fc2766438401a2308e18aca",
"name": "This is also an option"
"votes": 10,
}
],
}
$lookup to join votes collection, pass local field optoins._id and foreign field entryId
$project get options votes, $map to iterate loop of options array, $filter to get matching entryId records and $size to get count of element in return array, merge votes field and current object using $mergeObjects
db.entries.aggregate([
{
$lookup: {
from: "votes",
localField: "options._id",
foreignField: "entryId",
as: "votes"
}
},
{
$project: {
options: {
$map: {
input: "$options",
as: "a",
in: {
$mergeObjects: [
"$$a",
{
votes: {
$size: {
$filter: {
input: "$votes",
cond: { $eq: ["$$this.entryId", "$$a._id"] }
}
}
}
}
]
}
}
}
}
}
])
Playground

MongoDB - $lookup in complex nested array

I'm building a new LMS in mongodb and I have the following collections:
Courses
{
"_id": ObjectId("5f6a6b159de1304fb885b194"),
"title": "Course test",
"sections": [
{
"_id": ObjectId("5f6a6b159de1304fb885b195"),
"title": "Section 1 - introduction",
"order": 1,
"modules": [
{
"_id": ObjectId("5f6a6b159de1304fb885b196"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b135"),
"title": "Module 1",
"order": 1
},
{
"_id": ObjectId("5f6a6b159de1304fb885b198"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b14a"),
"title": "Module 2",
"order": 2
},
]
},
{
"_id": ObjectId("5f6a6b149de1304fb885b175"),
"title": "Section 2 - How to do something",
"order": 2,
"modules": [
{
"_id": ObjectId("5f6a6b149de1304fb885b141"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b150"),
"title": "Module 1",
"order": 1
},
{
"_id": ObjectId("5f6a6b149de1304fb885b15f"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b18e"),
"title": "Module 2",
"order": 2
},
]
},
]
}
Modules (only one as example)
{
"_id": ObjectId("5f6a6b149de1304fb885b135"),
"text": "Lorem ipsum...",
"mediaUrl": "urllinkhere"
}
As shown, I choose to have embedded documents for sections and modules titles but I need also a second collection, modules, because each module contains a large amount of text and my course document may get too big quickly.
Now I need to rebuild the entire document as if it was completely embedded.
Here's an example:
{
"_id": ObjectId("5f6a6b159de1304fb885b194"),
"title": "Course test",
"sections": [
{
"_id": ObjectId("5f6a6b159de1304fb885b195"),
"title": "Section 1 - introduction",
"order": 1,
"modules": [
{
"_id": ObjectId("5f6a6b159de1304fb885b196"),
"module_FK_id": ObjectId("5f6a6b149de1304fb885b135"),
"title": "Module 1",
"order": 1
"text": "Lorem ipsum...",
"mediaUrl": "urllinkhere"
},
// last two fields from collection "modules"
I'm trying different combination of aggregation and lookups but I can't obtain the desired result.
Can anybody help me?
You can construct the aggregation pipeline like this below, just remember to $group in the reverse order of $unwind operations
db.courses.aggregate([
{
$unwind: "$sections"
},
{
$unwind: "$sections.modules"
},
{
"$lookup": {
"from": "modules",
"localField": "sections.modules.module_FK_id",
"foreignField": "_id",
"as": "module_details"
}
},
{
$unwind: {
path: "$module_details",
preserveNullAndEmptyArrays: true
}
},
{
"$project": {
title: 1,
sections: {
_id: "$sections._id",
modules: {
_id: "$sections.modules._id",
module_FK_id: "$sections.modules.module_FK_id",
order: "$sections.modules.order",
title: "$sections.modules.title",
mediaUrl: "$module_details.mediaUrl",
text: "$module_details.text"
},
order: "$sections.order",
title: "$sections.title"
}
}
},
{
"$group": {
"_id": "$sections._id",
main_id: {
$first: "$_id"
},
main_title: {
$first: "$title"
},
order: {
$first: "$sections.order"
},
title: {
$first: "$sections.title"
},
modules: {
$push: "$sections.modules"
}
}
},
{
"$project": {
"_id": "$main_id",
"title": "$main_title",
section: {
_id: "$_id",
modules: "$modules",
title: "$title",
order: "$order",
}
}
},
{
$group: {
_id: "$_id",
title: {
$first: "$title"
},
sections: {
$push: "$section"
}
}
}
])
Working Example

MongoDb aggregation with arrays inside an array possible

I am struggling to find some examples of using the mongo aggregation framework to process documents which has an array of items where each item also has an array of other obejects (array containing an array)
In the example document below what I would really like is an example that sums the itemValue in the results array of all cases in the document and accross the collection where the result.decision was 'accepted'and group by the document locationCode
However, even an example that found all documents where the result.decision was 'accepted' to show or that summmed the itemValue for the same would help
Many thanks
{
"_id": "333212",
"data": {
"locationCode": "UK-555-5566",
"mode": "retail",
"caseHandler": "A N Other",
"cases": [{
"caseId": "CSE525666",
"items": [{
"id": "333212-CSE525666-1",
"type": "hardware",
"subType": "print cartridge",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "acme print cartridge",
"quantity": 2,
"weight": "1.5"
},
"result": {
"decision": "rejected",
"decisionDate": "2019-02-02"
},
"isPriority": true
},
{
"id": "333212-CSE525666-2",
"type": "Stationery",
"subType": "other",
"targetDate": "2020-06-15",
"itemDetail": {
"description": "staples box",
"quantity": 3,
"weight": "1.66"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-03",
"itemValue": "23.01"
},
"isPriority": true
}
]
},
{
"caseId": "CSE885655",
"items": [{
"id": "333212-CSE885655-1",
"type": "marine goods",
"subType": "fish food",
"targetDate": "2020-06-04",
"itemDetail": {
"description": "fish bait",
"quantity": 5,
"weight": "0.65"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-03-02"
},
"isPriority": false
},
{
"id": "333212-CSE885655-4",
"type": "tobacco products",
"subType": "cigarettes",
"deadlineDate": "2020-06-15",
"itemDetail": {
"description": "rolling tobbaco",
"quantity": 42,
"weight": "2.25"
},
"result": {
"decision": "accepted",
"decisionDate": "2020-02-02",
"itemValue": "48.15"
},
"isPriority": true
}
]
}
]
},
"state": "open"
}
You're probably looking for $unwind. It takes an array within a document and creates a separate document for each array member.
{ foos: [1, 2] } -> { foos: 1 }, { foos: 2}
With that you can create a flat document structure and match & group as normal.
db.collection.aggregate([
{
$unwind: "$data.cases"
},
{
$unwind: "$data.cases.items"
},
{
$match: {
"data.cases.items.result.decision": "accepted"
}
},
{
$group: {
_id: "$data.locationCode",
value: {
$sum: {
$toDecimal: "$data.cases.items.result.itemValue"
}
}
}
},
{
$project: {
_id: 0,
locationCode: "$_id",
value: "$value"
}
}
])
https://mongoplayground.net/p/Xr2WfFyPZS3
Alternative solution...
We group by data.locationCode and sum all items with this condition:
cases[*].items[*].result.decision" == "accepted"
db.collection.aggregate([
{
$group: {
_id: "$data.locationCode",
itemValue: {
$sum: {
$reduce: {
input: "$data.cases",
initialValue: 0,
in: {
$sum: {
$concatArrays: [
[ "$$value" ],
{
$map: {
input: {
$filter: {
input: "$$this.items",
as: "f",
cond: {
$eq: [ "$$f.result.decision", "accepted" ]
}
}
},
as: "item",
in: {
$toDouble: {
$ifNull: [ "$$item.result.itemValue", 0 ]
}
}
}
}
]
}
}
}
}
}
}
}
])
MongoPlayground

How to create an array containing arrays on MongoDB

I'm trying to make a query to mongodb. I want to get an array containing [location, status] of every document.
This is how my collection looks like
{
"_id": 1,
"status": "OPEN",
"location": "Costa Rica",
"type": "virtual store"
},
{
"_id": 2,
"status": "CLOSED",
"location": "El Salvador"
"type": "virtual store"
},
{
"_id": 3,
"status": "OPEN",
"location": "Mexico",
"type": "physical store"
},
{
"_id": 4,
"status": "CLOSED",
"location": "Nicaragua",
"type": "physical store"
}
I made a query, using the aggregate framework, trying to get all documents that match that specific type of store.
{
{'$match': {
'type': { '$eq': "physical store"}
}
}
What I want is something like this:
{
{
'stores': [
["Mexico", "OPEN"],
["Nicaragua", "CLOSED"]
]
},
}
I tried with the $push but couldn't make it.
Could someone please guide me on how to do it.
Since { $push: ["$location", "$status"] } would give you the error The $push accumulator is a unary operator. You would have to work around it a bit by passing to it a single object that output your desired array. One way to do it would be:
[
{
"$match": {
"type": {
"$eq": "physical store"
}
}
},
{
"$group": {
"_id": null,
"stores": {
"$push": {
"$slice": [["$location", "$status"], 2]
}
}
}
}
]
If the given documents are not sub-documents, then below is the approach:
db.collection.find({
type: {
$eq: "physical store"
}
},
{
location: 1,
status: 1
})
MongoPlayGround link for the above
If, they are the part of a field (means they are sub-documents), then below is the approach:
db.collection.aggregate([
{
$project: {
stores: {
$filter: {
input: "$stores",
as: "store",
cond: {
$eq: [
"$$store.type",
"physical store"
]
}
}
}
}
},
{
$unwind: "$stores"
},
{
$project: {
location: "$stores.location",
status: "$stores.status",
_id: "$stores._id"
}
}
])
MongoPlayGround link for the above