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

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

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

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

MongoDB unwinding 2 embedded arrays separately

I have a question regarding MongoDB aggregation query which is almost similar to $unwind 2 fields separately in mongodb query.
This is the document:
{
"_id" : "1",
"details" : {
"phonenumber" : [
"1",
"2"
],
"name" : [
"a",
"b"
]
}
}
And I am trying to frame a query which will return me the following result:
{ "_id" : "1", "phonenumber" : "1", "name" : null },
{ "_id" : "1", "phonenumber" : "2", "name" : null },
{ "_id" : "1", "phonenumber" : null, "name" : "a" },
{ "_id" : "1", "phonenumber" : null, "name" : "b" }
Could someone please help me with that?
Closest solution I could figure out is by following query:
db.document.aggregate( [ { $unwind: { path: "$details.name"} }, { $unwind: { path: "$details.phonenumber" } }, { $project: { _id: 1, name: "$details.name", phonenumber: "$details.phonenumber" } } ] )
And the output from above query is:
{ "_id" : "1", "phonenumber" : "1", "name" : "a" },
{ "_id" : "1", "phonenumber" : "1", "name" : "b" },
{ "_id" : "1", "phonenumber" : "2", "name" : "a" },
{ "_id" : "1", "phonenumber" : "2", "name" : "b" }
With MongoDB v3.4, one of the possible solution would be,
db.document.aggregate({
'$facet': {
'phonenumber': [{
'$unwind': '$details.phonenumber'
}, {
'$project': {
phonenumber: '$details.phonenumber',
name: null
}
}],
'name': [{
'$unwind': '$details.name'
}, {
'$project': {
name: '$details.name',
phonenumber: null
}
}]
}
}, {
'$project': {
'combined': {
'$setUnion': ['$phonenumber', '$name']
}
}
}, {
'$unwind': '$combined'
}, {
'$replaceRoot': {
'newRoot': '$combined'
}
})
facet allows us to include multiple aggregation pipelines within a single stage, which is available from version 3.4
Alternate solution for earlier versions of mongodb,
db.document.aggregate([{
$unwind: {
path: "$details.name"
}
}, {
$group: {
_id: "$_id",
nameArr: {
$push: {
name: "$details.name",
phonenumber: {
$ifNull: ["$description", null]
}
}
},
"details": {
$first: "$details"
}
}
}, {
$unwind: "$details.phonenumber"
}, {
$group: {
_id: "$_id",
phoneArr: {
$push: {
phonenumber: "$details.phonenumber",
name: {
$ifNull: ["$description", null]
}
}
},
"nameArr": {
$first: "$nameArr"
}
}
}, {
$project: {
_id: 1,
value: {
$setUnion: ["$nameArr", "$phoneArr"]
}
}
}, {
$unwind: "$value"
}, {
$project: {
name: "$value.name",
phonenumber: "$value.phonenumber"
}
}])