Mongodb unwind array nested in array - mongodb

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"
}
}
}
]
);

Related

MongoDB aggregation get value from path containing variable

I have a collection datas like this:
[
{
"_id": 0,
"languages": { "en": true },
"translated_data": { "en": { "title": "eng title 1" } }
},
{
"_id": 1,
"languages": { "en": true },
"translated_data": { "en": { "title": "eng title 2" } }
}
]
And I need to transform it to a format like this:
[
{
"_id": 0,
"translations": [{ "language": "en", "data": { "title": "eng title 1" } }]
},
{
"_id": 1,
"translations": [{ "language": "en", "data": { "title": "eng title 2" } }]
}
]
I tried this update query but stuck
db.datas.updateMany({}, [
{
$set: {
translations: {
$map: {
input: {
$filter: {
input: {
$objectToArray: "$languages",
},
as: "language",
cond: { $eq: ["$$language.v", true] },
},
},
as: "language",
in: {
language: "$$language.k",
data: "???", // get value from $translated_data[$$language.k]
},
},
},
},
},
{
$unset: ["languages", "translated_data"],
},
]);
Try this:
db.datas.updateMany({},
[
{
$set: {
"translate_ok": {
$first: {
$map: {
input: { $objectToArray: "$languages" },
as: "language",
in: { $eq: ["$$language.v", true] },
}
}
}
}
},
{
$set: {
translations: {
$map: {
input: { $objectToArray: "$translated_data" },
as: "language",
in: {
$cond: [
"$translate_ok",
{
language: "$$language.k",
data: "$$language.v"
},
{
$arrayToObject: [["$$language"]]
}
]
}
}
}
}
},
{
$unset: ["languages", "translated_data", "translate_ok"]
}
]
);
Output:
/* 1 */
{
"_id" : 1,
"translations" : [
{
"language" : "en",
"data" : {
"title" : "eng title 1"
}
}
]
},
/* 2 */
{
"_id" : 2,
"translations" : [
{
"en" : {
"title" : "eng title 2"
}
}
]
}
My test data:
/* 1 */
{
"_id" : 1,
"languages" : {
"en" : true
},
"translated_data" : {
"en" : {
"title" : "eng title 1"
}
}
},
/* 2 */
{
"_id" : 2,
"languages" : {
"en" : false
},
"translated_data" : {
"en" : {
"title" : "eng title 2"
}
}
}

Flatten an object into object.name , object.value in Mongodb aggregation

I have a record in collection like this. In cities, the name of the key or values are not constant
[
{
"id" : "xxx",
"countryName" : "xxx",
"cities" : {
"melbourne" : {
"id" : "xxx",
"cityName" : "xxx",
"population" : 124
},
"brisbane" : {
"column1" : "xxx",
"column2" : "xxx"
}
.....
}
}
]
I need a response like
{
cities.melbourne : {"id" : "xxx", "cityName" : "xxx", "population" : 124 },
cities.brisbane : { "column1" : "xxx", "column2" : "xxx" },
......}
You can do as below
db.collection.aggregate([
{
"$project": { //Converting cities to array
"cities": {
"$objectToArray": "$cities"
}
}
},
{ //Flattening
"$unwind": "$cities"
},
{
"$replaceRoot": {
"newRoot": { //Reshaping to the desired structure
$arrayToObject: [
[
{
"k": {
"$concat": [
"cities",
".",
"$cities.k"
]
},
"v": "$cities.v"
}
]
]
}
}
}
])
play
Output:
[
{
"cities.melbourne": {
"cityName": "xxx",
"id": "xxx",
"population": 124
}
},
{
"cities.brisbane": {
"column1": "xxx",
"column2": "xxx"
}
}
]
You can get the expected output using the aggregation operators $objectToArray and $arrayToObject to transformation the input document.
db.collection.aggregate([
{
$addFields: { cities: { $objectToArray: "$cities" } }
},
{
$unwind: "$cities"
},
{
$addFields: { "cities.k": { $concat: [ "cities", ".", "$cities.k" ] } }
},
{
$group: { _id: "$_id", cities: { $push: "$cities" } }
},
{
$replaceWith: { $arrayToObject: "$cities" }
}
])

How to group by one matching element in a subdocument - 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

Mongodb $unwind removes objects. if $lookup doesn't have reference [duplicate]

This question already has an answer here:
Handling unwind for the non existing embedded document [duplicate]
(1 answer)
Closed 3 years ago.
If country doesn't have reference states and cities. $unwind removes country name from the collections.
Expected Output will be Mongodb should return country name even if the country doesn't any states and cities reference.
Country Collection:
[
{
"_id": "5d052c76df076d23a48d4a3b",
"name": "India"
},
{
"_id": "5d052c76df076d23a48d4b07",
"name": "Indonesia"
},
{
"_id": "5d052c76df076d23a48d22f4",
"name": "Iran"
}
]
State Collection:
[
{
"_id": "5d2236c37ed1112b3cc41397",
"name": "Andaman and Nicobar Islands",
"countryId": "5d052c76df076d23a48d4a3b"
},
{
"_id": "5d2236c37ed1112b3cc41398",
"name": "Andhra Pradesh",
"countryId": "5d052c76df076d23a48d4a3b"
}
]
City Collection:
[
{
"name": "Port Blair",
"stateId": "5d2236c37ed1112b3cc41397"
},
{
"name": "Adoni",
"stateId": "5d2236c37ed1112b3cc41398"
}
]
Query:
Country.aggregate([
{
$lookup:{
from: 'states',
localField:'_id',
foreignField:'countryId',
as:'states'
}
},
{
$unwind: {
path: "$states"
}
},
{
$lookup:{
from: 'cities',
localField:'states._id',
foreignField:'stateId',
as:'states.cities'
}
},
{
$group: {
_id: {
_id: '$_id',
name: '$name'
},
states: {
$push: '$states'
}
}
},
{
$project: {
_id: '$_id._id',
name: '$_id.name',
states: 1
}
}
])
Output:
[
{
"_id":"5d052c76df076d23a48d4a3b",
"name":"India",
"states":[
{
"_id":"5d2236c37ed1112b3cc41397",
"name":"Andaman and Nicobar Islands",
"countryId":"5d052c76df076d23a48d4a3b",
"cities":[
{
"name":"Port Blair",
"stateId":"5d2236c37ed1112b3cc41397"
}
]
},
{
"_id":"5d2236c37ed1112b3cc41398",
"name":"Andhra Pradesh",
"countryId":"5d052c76df076d23a48d4a3b",
"cities":[
{
"name":"Adoni",
"stateId":"5d2236c37ed1112b3cc41398"
}
]
}
]
}
]
Expected Output:
[
{
"_id":"5d052c76df076d23a48d4a3b",
"name":"India",
"states":[
{
"_id":"5d2236c37ed1112b3cc41397",
"name":"Andaman and Nicobar Islands",
"countryId":"5d052c76df076d23a48d4a3b",
"cities":[
{
"name":"Port Blair",
"stateId":"5d2236c37ed1112b3cc41397"
}
]
},
{
"_id":"5d2236c37ed1112b3cc41398",
"name":"Andhra Pradesh",
"countryId":"5d052c76df076d23a48d4a3b",
"cities":[
{
"name":"Adoni",
"stateId":"5d2236c37ed1112b3cc41398"
}
]
}
]
},
{
"_id":"5d052c76df076d23a48d4b07",
"name":"Indonesia",
"states":[
]
},
{
"_id":"5d052c76df076d23a48d22f4",
"name":"Iran",
"states":[
]
}
]
just add " preserveNullAndEmptyArrays: true " to $unwind
Country.aggregate([
{
$lookup:{
from: 'states',
localField:'_id',
foreignField:'countryId',
as:'states'
}
},
{
$unwind: {
path: "$states",
preserveNullAndEmptyArrays: true
}
},
{
$lookup:{
from: 'cities',
localField:'states._id',
foreignField:'stateId',
as:'states.cities'
}
},
{
$group: {
_id: {
_id: '$_id',
name: '$name'
},
states: {
$push: '$states'
}
}
},
{
$project: {
_id: '$_id._id',
name: '$_id.name',
states: 1
}
}
])
output
[
{
"states" : [
{
"cities" : []
}
],
"_id" : "5d052c76df076d23a48d22f4",
"name" : "Iran"
},
{
"states" : [
{
"cities" : []
}
],
"_id" : "5d052c76df076d23a48d4b07",
"name" : "Indonesia"
},
{
"states" : [
{
"_id" : "5d2236c37ed1112b3cc41397",
"name" : "Andaman and Nicobar Islands",
"countryId" : "5d052c76df076d23a48d4a3b",
"cities" : [
{
"_id" : ObjectId("5d38ccb6f9c5fa48bf099027"),
"name" : "Port Blair",
"stateId" : "5d2236c37ed1112b3cc41397"
}
]
},
{
"_id" : "5d2236c37ed1112b3cc41398",
"name" : "Andhra Pradesh",
"countryId" : "5d052c76df076d23a48d4a3b",
"cities" : [
{
"_id" : ObjectId("5d38ccbcf9c5fa48bf09902a"),
"name" : "Adoni",
"stateId" : "5d2236c37ed1112b3cc41398"
}
]
}
],
"_id" : "5d052c76df076d23a48d4a3b",
"name" : "India"
}
]

$push and $sum with the aggregation framework on sub-documents

I've a data as follows:
{
"_id" : ObjectId("55d4410544c96d6f6578f893"),
"executionProject" : "Project1",
"suiteList" : [
{
"suiteName": "Suite1",
"suiteStatus" : "PASS",
},
{
"suiteName": "Suite2",
"suiteStatus" : "FAIL",
},
{
"suiteName": "Suite3",
"suiteStatus" : "PASS",
}
],
"runEndTime" : ISODate("2015-08-19T08:40:47.049Z")
}
{
"_id" : ObjectId("55d4410544c96d6f6578f894"),
"executionProject" : "Project1",
"suiteList" : [
{
"suiteName": "Suite1",
"suiteStatus" : "PASS",
},
{
"suiteName": "Suite2",
"suiteStatus" : "FAIL",
},
{
"suiteName": "Suite3",
"suiteStatus" : "FAIL",
}
],
"runEndTime" : ISODate("2015-08-19T08:50:47.049Z")
}
And I was trying to get the result like this:
{
"executionProject": "Project1",
"data": [
{
"date": "2015-08-19 08:40:47",
"suitePass": 2,
"suiteFail": 1
},
{
"date": "2015-08-19 08:50:47",
"suitePass": 1,
"suiteFail": 2
}
]
}
Here I'm trying to group by executionProject and push the runEndTime and the pass and fail counts of suites to the result.
I tried this, but giving me wrong way of projection:
db.testruns.aggregate([
{
$project: {
executionProject: "$executionProject",
runEndTime: "$runEndTime",
suiteList: "$suiteList"
}
},
{
$unwind: "$suiteList"
},
{
$group: {
_id: "$executionProject",
runEndTime: {
$addToSet: "$runEndTime"
},
suite_pass: {
$sum: {
$cond: {
"if": {
$eq: ["$suiteList.suiteStatus", "PASS"]
},
"then": 1,
"else": 0
}
}
}
}
},
{
$group: {
_id: "$_id",
runEndTime: { $push: {runTime: "$runEndTime", suite_pass: "$suite_pass"} }
}
},
{
$project: {
executionProject: "$_id",
runEndTime: "$runEndTime",
_id: 0
}
}
]);
First you need to group by the document to get the suite totals, then you add to the array as you group on the project. Also don't forget to "sort" if you want things in order:
[
{ "$unwind": "$suiteList" },
{ "$group": {
"_id": "$_id",
"executionProject": { "$first": "$executionProject" },
"suite-pass": {
"$sum": {
"$cond": [
{ "$eq": [ "$suiteList.suiteStatus", "PASS" ] },
1,
0
]
}
},
"suite-fail": {
"$sum": {
"$cond": [
{ "$eq": [ "$suiteList.suiteStatus", "FAIL" ] },
1,
0
]
}
},
"date": { "$first": "$runEndTime" }
}},
{ "$sort": { "executionProject": 1, "date": 1 } },
{ "$group": {
"_id": "$executionProject",
"data": {
"$push": {
"suite-pass": "$suite-pass",
"suite-fail": "$suite-fail",
"date": "$date"
}
}
}}
]
Produces:
{
"_id" : "Project1",
"data" : [
{
"suite-pass" : 2,
"suite-fail" : 1,
"date" : ISODate("2015-08-19T08:40:47.049Z")
},
{
"suite-pass" : 1,
"suite-fail" : 2,
"date" : ISODate("2015-08-19T08:50:47.049Z")
}
]
}