How to group by one matching element in a subdocument - Mongodb - mongodb

Suppose I have the following collection.
[
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
},
{
"#pid": "61",
"text": "Mango"
},
{
"#pid": "92",
"text": "cherry"
},
{
"#pid": "27",
"text": "grape"
},
{
"#pid": "34",
"text": "dragonfruit"
}
]
},
"type": "A"
},
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
},
{
"#pid": "27",
"text": "grape"
},
{
"#pid": "34",
"text": "dragonfruit"
}
]
},
"type": "B"
},
{
"items": {
"item": [
{
"#pid": "131",
"text": "Apple"
}
]
},
"type": "A"
}
]
I want to get the type in which apple or mango is sold, group by item name. For the above collection, the output would be :
{
"_id": "Apple",
"items" : [
"A",
"B",
"A"
]
},
{
"_id": "Mango",
"items" : [
"A"
]
}
I tried the following query but it return nothing :
db.collection.aggregate([
{
$match : {
'items.item.text' : {$regex : 'Apple|Mango'}
}
},
{
$project : {
type : "$type"
}
},
{
$group : {
_id : '$items.item',
types : {$push : '$type'}
}
}
])
I think that even if this works, it's going to group by the entire 'items.item'. Where am I going wrong?
P.S. : I don't have the liberty to change the format of the document
Thanks a lot in advance.

You were on the right direction. You need to use $unwind operator and you don't need $project stage in your aggregation. The below query will be helpful:
db.collection.aggregate([
{
$unwind: "$items.item"
},
{
$match: {
"items.item.text": {
$regex: "Apple|Mango"
}
}
},
{
$group: {
_id: "$items.item.text",
type: {
$push: "$type"
}
}
}
])
MongoPlayGroundLink

Related

Retrieve highest score for each game using aggregate in MongoDB

I am working on a database of various games and i want to design a query that returns top scorer from each game with specific player details.
The document structure is as follows:
db.gaming_system.insertMany(
[
{
"_id": "01",
"name": "GTA 5",
"high_scores": [
{
"hs_id": 1,
"name": "Harry",
"score": 6969
},
{
"hs_id": 2,
"name": "Simon",
"score": 8574
},
{
"hs_id": 3,
"name": "Ethan",
"score": 4261
}
]
},
{
"_id": "02",
"name": "Among Us",
"high_scores": [
{
"hs_id": 1,
"name": "Harry",
"score": 926
},
{
"hs_id": 2,
"name": "Simon",
"score": 741
},
{
"hs_id": 3,
"name": "Ethan",
"score": 841
}
]
}
]
)
I have created a query using aggregate which returns the name of game and the highest score for that game as follows
db.gaming_system.aggregate(
{ "$project": { "maximumscore": { "$max": "$high_scores.score" }, name:1 } },
{ "$group": { "_id": "$_id", Name: { $first: "$name" }, "Highest_Score": { "$max": "$maximumscore" } } },
{ "$sort" : { "_id":1 } }
)
The output from my query is as follows:
{ "_id" : "01", "Name" : "GTA 5", "Highest_Score" : 8574 }
{ "_id" : "02", "Name" : "Among Us", "Highest_Score" : 926 }
I want to generate output which also provides the name of player and "hs_id" of that player who has the highest score for each game as follows:
{ "_id" : "01", "Name" : "GTA 5", "Top_Scorer" : "Simon", "hs_id": 2, "Highest_Score" : 8574 }
{ "_id" : "02", "Name" : "Among Us", "Top_Scorer" : "Harry", "hs_id": 1, "Highest_Score" : 926 }
What should be added to my query using aggregate pipeline?
[
{
$unwind: "$high_scores" //unwind the high_scores, so you can then sort
},
{
$sort: {
"high_scores.score": -1 //sort the high_scores, irrelevant of game, because we are going to group in next stage
}
},
{
//now group them by _id, take the name and top scorer from $first (which is the first in that group as sorted by score in descending order
$group: {
_id: "$_id",
name: {
$first: "$name"
},
Top_Scorer: {
$first: "$high_scores"
}
}
}
]

Join Same Collection in Mongo

Below is the sample collection document record that i want to join the same collection with different child array elements.
Sample Collection Record :
{
"_id": "052dc2aa-043b-4cd7-a3f2-f3fe6540ae52",
"Details": [
{
"Id": "104b0bb1-d4a5-469b-b1fd-b4822e96dcb0",
"Number": "12345",
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
},
{
"Code": "55333",
"Percentage": "50"
}
]
},
{
"Id": "104b0bb1-d4a5-469b-b1fd-b4822e96dcb0",
"Number": "55555",
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
}
]
}
],
"Payments": [
{
"Id": "61ee1a6f-3334-4f33-ab6c-51c646b75c41",
"Number": "12345"
}
]
}
The mongo Pipeline query which i would like to fetch the Percentages Array with matched conditions whose Details.Number and Payment.Number should be same
Result:
"Percentages": [
{
"Code": "55555",
"Percentage": "45"
},
{
"Code": "55333",
"Percentage": "50"
}]
How to bring the result by joining the same collections child elements using aggregate ?
Following query does what you want:
db.collection.aggregate([
{$unwind : "$Details"},
{$unwind : "$Details.Percentages"},
{$unwind : "$Payments"}, // $unwind all your arrays
{
$addFields : { //This include new `isMatch` field, which is gonna be true, only if Details.Number = Payment.Number
"isMatch" : {$cond: { if: { $eq: [ "$Details.Number", "$Payments.Number" ] }, then: true, else: false }}
}
},
{
$match : { // This ignores all others, for which Details.Number != Payment.Number
"isMatch" : true
}
},
{
$group : { // This will return only the Percentage objects
_id : null,
"Percentages" : {$push : "$Details.Percentages"}
}
},
{
$project : { // To ignore "_id" field
_id : 0,
"Percentages" : 1
}
}
])
Result:
{
"Percentages" : [
{
"Code" : "55555",
"Percentage" : "45"
},
{
"Code" : "55333",
"Percentage" : "50"
}
]
}
Hope this helps!

Retrieve matched document from nested array [duplicate]

This question already has answers here:
Find in Double Nested Array MongoDB
(2 answers)
Closed 4 years ago.
I'm trying to collect all objects in a nested array where the field spec equals unknown.
The structure per document is similar to this:
{
"_id" :"5b1e73786f11e421956023c3",
"subs" : [
{
"name" : "subrepo1",
"files" : [
{
"name" : ".....",
"spec" : "Unknown"
},
{
"name" : ".....",
"spec" : "Unknown"
}
]
},
{
"name" : "subrepo2",
"files" : [
{
"name" : "file2",
"spec" : "Unknown"
},
{
"name" : ".....",
"spec" : "1234"
}
]
}
]
}
I tried the following but it doesn't work. I'm think this is in the right direction but i'm probably missing something important.
db.col.aggregate([
{$match: {'subs.files.spec': 'Unknown'}},
{$project: {
'subs.files': {$filter: {
input: '$subs.files',
//as: 'subs.files',
cond: {$eq: ['this.spec', 'FunSuite']}
}},
//_id: 0
}}
])
The expected output would be: (so ONLY the files that have spec equals to Unknown (NOT the other ones)
{
"_id" : "5b1e73786f11e421956023c3",
"subs" : [
{
"name" : "subrepo1",
"files" : [
{
"name" : ".....",
"spec" : "Unknown"
},
{
"name" : ".....",
"spec" : "Unknown"
}
]
},
{
"name" : "subrepo2",
"files" : [
{
"name" : "file2",
"spec" : "Unknown"
}
]
}
]
}
You need to use $filter aggregation operator which gives only the matched element from the array and escapes the other elements
db.collection.aggregate([
{
$unwind: "$subs"
},
{
$project: {
"subs.name": "$subs.name",
"subs.files": {
$filter: {
input: "$subs.files",
as: "file",
cond: {
$eq: [
"$$file.spec",
"Unknown"
]
}
}
}
}
},
{
$group: {
_id: "$_id",
subs: {
$push: "$subs"
}
}
}
])
Above will give following output
[
{
"_id": ObjectId("5a934e000102030405000000"),
"subs": [
{
"files": [
{
"name": ".....",
"spec": "Unknown"
},
{
"name": ".....",
"spec": "Unknown"
}
],
"name": "subrepo1"
},
{
"files": [
{
"name": "file2",
"spec": "Unknown"
}
],
"name": "subrepo2"
}
]
}
]
You can check the result here
And if you want to get the fields as in array then remove the $unwind and $replaceRoot stage from the pipeline
db.collection.aggregate([
{
$unwind: "$subs"
},
{
$project: {
"subs.name": "$subs.name",
"subs.files": {
$filter: {
input: "$subs.files",
as: "file",
cond: {
$eq: [
"$$file.spec",
"Unknown"
]
}
}
}
}
},
{
$unwind: "$subs.files"
},
{
$replaceRoot: {
newRoot: "$subs.files"
}
}
])
Above will give following output
[
{
"name": ".....",
"spec": "Unknown"
},
{
"name": ".....",
"spec": "Unknown"
},
{
"name": "file2",
"spec": "Unknown"
}
]
Try this way:
db.col.aggregate([
{
$unwind: '$subs'
},
{
$unwind: '$subs.files'
},
{
$match: {
'subs.files.spec': 'Unknown'
}
}
]);

Mongodb unwind array nested in array

This is my collection:
db.questions.insertMany([
{
"content": [
{
"languageId": "en",
"text": "What are you planning to buy today at the supermarket?"
},
{
"languageId": "nl",
"text": "Wat ben je van plan om vandaag in de supermarkt te kopen?"
},
],
"type": "multipleChoice",
"multipleChoice": {
"numAnswers": { "min": 1, "max": 1 },
"possibleAnswers": [
{
"sequence": 1,
"content": [
{
"languageId": "en",
"text": "apples"
},
{
"languageId": "nl",
"text": "appels"
},
],
},
{
"sequence": 2,
"content": [
{
"languageId": "en",
"text": "peers"
},
{
"languageId": "nl",
"text": "peren"
},
],
},
],
}
},
{
"content": [
{
"languageId": "en",
"text": "How do you feel?"
},
{
"languageId": "nl",
"text": "Hoe voel je je?"
},
],
"type": "ranking1to5",
}
]);
I want to transform into one language that are in two content arrays. So I want to have the output:
{
"_id" : ObjectId("5abe4c09d3831890de28ec8f"),
"content" :
{
"languageId" : "en",
"text" : "What are you planning to buy today at the supermarket?"
},
"type" : "multipleChoice",
"multipleChoice" : {
"numAnswers" : {
"min" : 1.0,
"max" : 1.0
},
"possibleAnswers" : [
{
"sequence" : 1.0,
"content" :
{
"languageId" : "en",
"text" : "apples"
}
},
{
"sequence" : 2.0,
"content" :
{
"languageId" : "en",
"text" : "peers"
}
}
]
}
}
/* 2 */
{
"_id" : ObjectId("5abe4c09d3831890de28ec90"),
"content" :
{
"languageId" : "en",
"text" : "How do you feel?"
},
"type" : "ranking1to5"
}
I tried using $unwind, $match and $group to tackle this problem. I have come pretty far only the last piece does not work:
db.getCollection('questions').aggregate([
{ $unwind: "$content" },
{ $match: { "content.languageId": "en" } },
{ $unwind: { path: '$multipleChoice.possibleAnswers', preserveNullAndEmptyArrays: true } },
{ $unwind: { path: '$multipleChoice.possibleAnswers.content', preserveNullAndEmptyArrays: true } },
{ $match: { "multipleChoice.possibleAnswers.content.languageId": "en" } },
{ $group: { _id: "$_id", content: { $first: "$content" }, type: { $first: "$type" }, multipleChoice: { $addToSet: "$multipleChoice" } } }
])
The problem is multipleChoice gets repeated while this should be possibleAnswers. Also the question that has no multipleChoice object should be included.
Any help is particularly appreciated !!
It looks like a good fit for the $redact. Please try the following aggregation:
db.questions.aggregate(
[
{
$redact: {
$cond: {
if: {
$eq: [
{ $ifNull: [ "$languageId", "en" ] },
"en"
]
},
then: "$$DESCEND",
else: "$$PRUNE"
}
}
},
{
$addFields: {
"content": { $arrayElemAt: [ "$content", 0 ] },
"multipleChoice.possibleAnswers": {
$map: {
input: "$multipleChoice.possibleAnswers",
as: "possibleAnswer",
in: {
"sequence": "$$possibleAnswer.sequence",
"content": { $arrayElemAt: [ "$$possibleAnswer.content", 0 ] }
}
}
}
}
},
{
$redact: {
$cond: {
if: { $eq: [ "$possibleAnswers", null ] },
then: "$$PRUNE",
else: "$$DESCEND"
}
}
}
]
);

Match key name and show document in Mongodb?

Json Structure:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"Number" : {
"value" : "1234567",
},
"DeviceID" : {
"value" : "01",
}
"type" : {
"value" : "ce06"}
Now i want to find only those keys document which start from /dev/.
i tried this script:
var cur = db.LIVEDATA.find({"ProductIMEIno.value":"359983007488004"});
cur.forEach(function(doc){
var keynames = Object.keys(doc);
print('the length is '+keynames.length);
for(var i=0;i<keynames.length;i++){
if(keynames[i].match(/Dev/)){
print("the name is "+keynames); }}} )
but this is not working properly.
Desired Output;
Only this document should show on the basis of key name search.
"DeviceID" : {
"value" : "01",
MongoDB isn't designed to find keys dynamically like this; it's much easier to use it to find values dynamically, so you could restructure your data structure to allow this:
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : [
{
"key" : "Number",
"value" : "1234567",
},
{
"key": "DeviceID",
"value" : "01",
},
{
"key" : "type",
"value" : "ce06"
}
]
Then you will be able to query it like this:
db.LIVEDATA.aggregate([
{$match: {"ProductIMEIno.value":"359983007488004"}},
{$unwind: "$data"},
{$match: {"data.key" : /^dev/i }}
]);
That will return data structured like this:
{
"_id" : ObjectId("55d6cb28725f3019a5241781"),
"data" : {
"key" : "DeviceID",
"value" : "01"
}
}
Suppose you have a data collection like this:
[
{
"Number": {
"value": "1234567"
},
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"value": "01",
"name": "abc123"
},
"type": {
"value": "ce06"
}
},
{
"Number": {
"value": "1234568"
},
"DeviceID": {
"value": "02"
},
"type": {
"value": "ce07"
}
}
]
You can use following aggregation:
db.collection.aggregate([
{
"$match": {}
},
{
"$addFields": {
"root_key_value_list": {
"$objectToArray": "$$ROOT"
}
}
},
{
"$unwind": "$root_key_value_list"
},
{
"$match": {
"root_key_value_list.k": {
"$regex": "^Dev"
}
}
},
{
"$group": {
"_id": "$_id",
"root_key_value_list": {
"$push": "$root_key_value_list"
}
}
},
{
"$project": {
"root": {
"$arrayToObject": "$root_key_value_list"
}
}
},
{
"$replaceRoot": {
"newRoot": "$root"
}
}
])
the result will be:
[
{
"DeviceID": {
"value": "01"
},
"DeviceID2": {
"name": "abc123",
"value": "01"
}
},
{
"DeviceID": {
"value": "02"
}
}
]
playground:
https://mongoplayground.net/p/z5EeHALCqzy