MongoDB: Get all elements of a nested array - arrays

Is it possible to get every element which is saved in a nested array with a find(). I need to get a list of all elements which are saved in the cat-field of the documents.
{
"_id" : "1",
"title" : "title 1",
"cat" : [
{
"element" : "element 1"
},
{
"element" : "element 2"
}
]
},
{
"_id" : "2",
"title" : "title 2",
"cat" : [
{
"element" : "element 3"
},
{
"element" : "element 4"
}
]
}
Result of this example should be - as I also need the id of the document:
1, element 1
1, element 2
2, element 3
2, element 4

You can also try out following query with distinct :
db.collection.distinct("cat.element")
EDIT:
Then you can try out $map as marked in duplicate question like:
You can simply use it like :
db.collection.aggregate({
"$project": {
"cat": {
"$map": {
"input": "$cat",
"as": "el",
"in": "$$el.element"
}
},
"title": 1
}
})
OR
db.collection.aggregate({
"$project": {
"cat": {
"$map": {
"input": "$cat",
"as": "el",
"in": "$$el.element"
}
},
"title": 1
}
}, {
$group: {
_id: "$_id",
"title": {
$first: "$title"
},
"cat": {
"$first": "$cat"
}
}
})

Related

Join collection with array object field with another collection in MongoDB

I'm working in MongoDB and getting stuck at one aggregation case. Let me show you my collection.
First collection (data):
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal" : "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"place": "Chennai"
},
{
"animal" : "fcfdd527-5885-48b0-a91f-03f72f78528f",
"place": "Kolkata"
}
]
}
]
Second collection (Animal):
[
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Dog"
},
{
"_id": "7d44251a-b308-4deb-875a-33ef0a69fe2b",
"name": "Cat"
}
]
I'm using this query:
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$unwind": "$doc"
},
{
"$project": {
"_id": 1,
"data.animal": "$doc.name",
"data.place": 1
}
}
])
and it result me this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Dog",
"place": "Kolkata"
}
]
},
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Cat",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Where I'm expecting like this
[
{
"_id": "8e7b3fa0-4230-448c-8f70-1d7300632834",
"data": [
{
"animal": "Dog",
"place": "Chennai"
},
{
"animal": "Cat",
"place": "Kolkata"
}
]
}
]
Mongo Playground
Also sharing this question in Mongo playgroud. Thanks in advance!!
Solution 1
$unset - Deconstruct the data array into multiple documents.
$lookup - Perform join with animal collection.
$project - Decorate the output document. For data.animal field, get the first value via $first.
$group - Group by _id. Push the data document into the data array.
db.data.aggregate([
{
"$unwind": "$data"
},
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
"$project": {
"_id": 1,
"data.animal": {
$first: "$doc.name"
},
"data.place": 1
}
},
{
$group: {
_id: "$_id",
data: {
$push: "$data"
}
}
}
])
Demo Solution 1 # Mongo Playground
Solution 2
$lookup
$set - Set data field.
2.1. $map - Iterate the data array and returns a new array.
2.1.1. $mergeObjects - Merge current iterated document with place field and the document from 2.1.1.1.
2.1.1.1. $first - Get the first document from the filtered doc arrays by matching the ids via $filter.
$unset - Remove _id and animals._id fields.
db.data.aggregate([
{
"$lookup": {
"from": "animal",
"localField": "data.animal",
"foreignField": "_id",
"as": "doc"
}
},
{
$set: {
data: {
$map: {
input: "$data",
as: "data",
in: {
$mergeObjects: [
{
place: "$$data.place"
},
{
$first: {
$filter: {
input: "$doc",
cond: {
$eq: [
"$$this._id",
"$$data.animal"
]
}
}
}
}
]
}
}
}
}
},
{
$unset: [
"doc",
"data._id"
]
}
])
Demo Solution 2 # Mongo Playground

get the relevant information against the object value in MongoDB

Explanation :
A , B , C object have values. I have to match only those values with dic.data objects name. if its not match the then get the exception "No error found". see the Expected_output.
A B C get the relvant information.
I am using lookup to get dic data.
Note this should be handle dynamically. A B C values may differ, that impact on Expected_output
{
"_id": {
"A": "31",
"B": "40",
"C": "7"
},
"dic": [
{
"_id": "5487",
"data": {
"A": {
"31": {
"name": "NoFile"
},
"32": {
"name": " -- "
}
},
"B": {
"40": {
"label": "Label",
"description": "Error1"
},
"41": {
"label": " Data collection ",
"description": "error"
}
},
"C": {
"4": {
"description": "High problem"
},
"7": {
"description": " Normal"
}
}
}
}
]
}
"Expected_output": {
"A": {
"name" :"NoFile",
"code" : "31"
},
"B":{
"label" : "Label",
"description" : "Error1",
"code" : "40"
},
"C": {
"description" : "Normal",
"code" : "7"
}
}
$arrayElemAt to get first element from dic array
$objectToArray convert A object to array
$reduce to iterate loop of element above converted array and check condition if _id.A matches with data A then return specific field,
do the same process for B and C
db.collection.aggregate([
{
$addFields: {
dic: { $arrayElemAt: ["$dic", 0] }
}
},
{
$project: {
_id: 1,
dic: {
A: {
$reduce: {
input: { $objectToArray: "$dic.data.A" },
initialValue: "Not Found",
in: {
$cond: [
{ $eq: ["$$this.k", "$_id.A"] },
"$$this.v.name",
"$$value"
]
}
}
},
B: {
$reduce: {
input: { $objectToArray: "$dic.data.B" },
initialValue: "Not Found",
in: {
$cond: [
{ $eq: ["$$this.k", "$_id.B"] },
"$$this.v.description",
"$$value"
]
}
}
},
C: {
$reduce: {
input: { $objectToArray: "$dic.data.C" },
initialValue: "Not Found",
in: {
$cond: [
{ $eq: ["$$this.k", "$_id.C"] },
"$$this.v.description",
"$$value"
]
}
}
}
}
}
}
])
Playground

Counting words in the array - How can I query with mongo?

"sourceList": [
{
"source" : "hello world, how are you?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "hello world, I am fine",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "Is it raining?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
}
]
Total words in hello world, how are you? = 5, in hello world, I am fine = 5, and in Is it raining?= 3.
Thus the total number of words = 13
Is there a mongo query to do this calculation? I could do this using javascript, but is there a direct way to query via mongo?
EDIT
Is there a way I can do this query across the documents? For documents obeying specific criteria, I want to run a similar calculation with an added constraint, that words of duplicate sentences are not counted twice. For example,
Document - 1
"sourceList": [
{
"source" : "hello world, how are you?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "hello world, I am fine",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "Is it raining?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
}
]
Document - 2
"sourceList": [
{
"source" : "hello world, how are you?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "hello world, I am fine",
"_id" : ObjectId("5f0eb9946db57c0007841153")
},
{
"source" : "Is it raining?",
"_id" : ObjectId("5f0eb9946db57c0007841153")
}
]
Here the count still remains the same. The reason being, sentences are exactly same in both the documents. But if we combine Document 1 + Document 3 (given as follows)
"sourceList": [
{
"source" : "Look at the beautiful tiger!",
"_id" : ObjectId("5f0eb9946db57c0007841153")
}
]
The count would come as 13 + 5 (document 3) = 18.
Yes, You can do that with the help of powerful aggregate framework.
mongo play-ground
db.collection.aggregate([
{
"$unwind": "$sourceList" //For each array element
},
{
$project: {
"sp": {
$split: [
"$sourceList.source", //split by spaces
" "
]
}
}
},
{
"$project": {
"sizes": {
"$size": "$sp". //count the words in each array
}
}
},
{
"$group": {
"_id": "$_id",
"count": {
"$sum": "$sizes" //group by id to reverse unwind and add the sizes
}
}
}
])
Update:
play
db.collection.aggregate([
{
"$unwind": "$sourceList"
},
{
$project: {
"sp": {
$split: [
"$sourceList.source",
" "
]
}
}
},
{
"$project": {
"sizes": {
"$size": "$sp"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": "$sizes"
}
}
}
])
For huge collections, you may need to use allowDiskUse but it is very heavy operation for larger collections.
Update:
play
db.collection.aggregate([
{
"$unwind": "$sourceList"
},
{
$project: {
"sp": {
$split: [
"$sourceList.source",
" "
]
}
}
},
{
"$group": {
"_id": null,
"elements": {
$addToSet: "$sp"
}
}
},
{
"$unwind": "$elements"
},
{
"$project": {
"sizes": {
"$size": "$elements"
}
}
},
{
"$group": {
"_id": null,
"count": {
"$sum": "$sizes"
}
}
}
])

Aggregate multiple collections based on student Id

I am trying to aggregate 2 collections in MongoDB based on a student's ID. One collection consists of student personal information, another one consists of the students logs. The issue is that the data is in array which is why I think my aggregation is not working. Any help will be appreciated.
student collection
{
"_id" : ObjectId("(Object ID here"),
"data" : [
{
"name" : "John",
"id" : 1
},
{
"name" : "Sandy",
"id" : 2
}
]
}
logs collection
{
"_id" : ObjectId("(Object ID here"),
"logs" : [
{
"studentId" : 1,
"activity" : "11112,334,123"
},
{
"studentId" : 2,
"activity" : "11112,334,123"
}
]
}
Here is what I have tried:
dbo.collection("student").aggregate([
{ "$lookup": {
"localField": "data.id",
"from": "logs",
"foreignField": "logs.studentId",
"as": "studentInfo"
}
}]).toArray(function(err, results) {
console.log(results);
});
Expected result:
studentinfo: {
id: 1,
name: "John",
activity" : "11112,334,123"
}
You can use below aggregation with mongodb 3.6
So basically your foreign field is an array you need to use $lookup with the pipeline to $unwind the foreign array inside the $lookup pipeline and to match the corresponding ids.
db.students.aggregate([
{ "$lookup": {
"from": "logs",
"let": { "dataId": "$data.id" },
"pipeline": [
{ "$unwind": "$logs" },
{ "$match": { "$expr": { "$in": ["$logs.studentId", "$$dataId"] }}},
{ "$replaceRoot": { "newRoot": "$logs" }}
],
"as": "students"
}}
])
or use this to merge both the arrays
db.students.aggregate([
{ "$lookup": {
"from": "logs",
"let": { "dataId": "$data.id" },
"pipeline": [
{ "$unwind": "$logs" },
{ "$match": { "$expr": { "$in": ["$logs.studentId", "$$dataId"] }}},
{ "$replaceRoot": { "newRoot": "$logs" }}
],
"as": "students"
}},
{ "$project": {
"students": {
"$map": {
"input": "$students",
"in": {
"studentId": "$$this.studentId",
"activity": "$$this.activity",
"name": { "$arrayElemAt": ["$data.name", { "$indexOfArray": ["$data.id", "$$this.studentId"] }]}
}
}
}
}}
])
Output
[
{
"students": [
{
"activity": "11112,334,123",
"name": "John",
"studentId": 1
},
{
"activity": "11112,334,123",
"name": "Sandy",
"studentId": 2
}
]
}
]

MongoDB count array in array

How to realize count array in array using aggregate? I have this document structure:
{
"_id" : 1,
"link" : [
{
"linkHistory" : [
{
"_id" : 1,
},
{
"_id" : 2,
}
]
}
]
}
and MongoDB code:
db.emailGroup.aggregate([
{
"$lookup":
{
"from": "link",
"localField": "_id",
"foreignField": "emailGroupId",
"as": "link"
},
},
{
"$unwind": "$link"
},
{
"$match": {
'link.originalLink': ""
}
},
{
"$group" : {
_id: '$_id',
link: {
$push: '$link'
}
}
}
])
I want to get count in other field for linkHistory. Is this possible?

Resources