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

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

Related

Create 4 level nested object aggregate output from a linear mongo collection

I have following schema
{
question: String,
answer: Number,
option1: String,
option2: String,
option3: String,
option4: String,
subject: String,
chapter: String,
topic: String,
subtopic: String,
tags: { type: Array, default: [] },
difficulty: String,
media: { type: String, default: "" }
}
I want to create a index from this question schema, where a unique entry in index can be identified by tuple (subject, chapter, topic, subtopic)
{
"subject": {
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
},
"subject": {
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
"chapter": {
"topic": ["subtopics"],
"topic": ["subtopics"],
},
},
}
To achieve above result I have been trying some aggregations and so far I am able to get upto
{
"subject": ["chapters"],
"subject": ["chapters"],
"subject": ["chapters"],
"subject": ["chapters"],
}
using following pipeline in mongoose
{
$group: {
_id: "$subject",
chapters: { $push: "$chapter" }
}
},
{
$group: {
_id: null,
chapters: { $push: { "k": "$_id", "v": "$chapters" } }
}
},
{
$replaceRoot: { "newRoot": { "$arrayToObject": "$chapters" } }
}
When I tried to extend my above logic to include topic ans subtopics using
{
$group: {
_id: "$subject",
chapters: { $push: { "k": "$chapter", "v": { topic: "$topic", subtopic: "$subtopic" } } }
}
},
{
$group: {
_id: null,
chapters: { $push: { "k": "$_id", "v": { "$arrayToObject": "$chapters" } } }
}
},
{
$replaceRoot: { "newRoot": { "$arrayToObject": "$chapters" } }
}
I get following output
{
"subject": {
"chapter": {
"topic": "value",
"subtopic": "value"
},
"chapter": {
"topic": "value",
"subtopic": "value"
},
},
"subject": {
"chapter": {
"topic": "value",
"subtopic": "value"
},
"chapter": {
"topic": "value",
"subtopic": "value"
},
},
}
Problem is that chapter is a object of last topic and subtopic only I know where I am making mistake but I don;t know how to fix it.
You can use this aggregation that mainly utilizes $objectToArray, the data restructuring is not so elegant though:
db.collection.aggregate(
[
{
"$group" : {
"_id" : {
"subject" : "$subject",
"chapter" : "$chapter",
"topic" : "$topic"
},
"topics" : {
"$push" : "$subtopic"
}
}
},
{
"$addFields" : {
"topic" : {
"k" : "$_id.topic",
"v" : "$topics"
}
}
},
{
"$group" : {
"_id" : {
"subject" : "$_id.subject",
"chapter" : "$_id.chapter"
},
"topics" : {
"$push" : "$topic"
}
}
},
{
"$addFields" : {
"topics" : {
"$arrayToObject" : "$topics"
}
}
},
{
"$addFields" : {
"chapters" : [
{
"k" : "$_id.chapter",
"v" : "$topics"
}
]
}
},
{
"$addFields" : {
"chapters" : {
"$arrayToObject" : "$chapters"
}
}
},
{
"$group" : {
"_id" : null,
"subject" : {
"$push" : {
"k" : "$_id.subject",
"v" : "$chapters"
}
}
}
},
{
"$project" : {
"final" : {
"$arrayToObject" : "$subject"
}
}
},
{
"$replaceRoot" : {
"newRoot" : "$final"
}
}
]);

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

Query datevalue of a inner Array element

Need help with some MongoDB query:
The document I have is below and I am trying to search based on 2 conditions
The meta.tags.code = "ABC"
Its LastSyncDateTime should
meta.extension.value == "" (OR)
the meta.extension.value is less than meta.lastUpdated
Data :
{
"meta" : {
"extension" : [
{
"url" : "LastSyncDateTime",
"value" : "20190206-00:49:25.694"
},
{
"url" : "RetryCount",
"value" : "0"
}
],
"lastUpdate" : "20190207-01:21:41.095",
"tags" : [
{
"code" : "ABC",
"system" : "type"
},
{
"code" : "XYZ",
"system" : "SourceSystem"
}
]
}
}
Query:
db.proc_patients_service.find({
"meta.tags.code": "ABC",
$or: [{
"meta.extension.value": ""
}, {
$expr: { "$lt": [{ "mgfunc": "ISODate", "params": [{ "$arrayElemAt": ["$meta.extension.value", 0] }] }, { "mgfunc": "ISODate", "params": ["$meta.lastUpdate"] }] }
}]
})
But it is only fetching ABC Patients whose LastSyncDateTime is empty and ignores the other condition.
Using MongoDB Aggregation, I have converted your string to date with operator $dateFromString and then compare the value as per your criteria.
db.proc_patients_service.aggregate([
{ $match: { "meta.tags.code": "ABC", } },
{ $unwind: "$meta.extension" },
{
$project: {
'meta.tags': '$meta.tags',
'meta.lastUpdate': { '$dateFromString': { 'dateString': '$meta.lastUpdate', format: "%Y%m%d-%H:%M:%S.%L" } },
'meta.extension.url': '$meta.extension.url',
'meta.extension.value': {
$cond: {
if: { $ne: ["$meta.extension.value", "0"] }, then: { '$dateFromString': { 'dateString': '$meta.extension.value', format: "%Y%m%d-%H:%M:%S.%L" } }, else: 0
}
}
}
},
{
$match: {
$or: [
{ "meta.extension.value": 0 },
{ $expr: { $lt: ["$meta.extension.value", "$meta.lastUpdate"] } }
]
}
},
{
$group: { _id: '_id', 'extension': { $push: '$meta.extension' }, "lastUpdate": { $first: '$meta.lastUpdate' }, 'tags': { $first: '$meta.tags' } }
},
{
$project: { meta: { 'extension': '$extension', lastUpdate: '$lastUpdate', 'tags': '$tags' } }
}
])

Aggregate Fields together

I have the following structure as an input from which data needs to be aggregated:
I need to aggregate the data such that I end up with the following structure:
start: A {
tripdetails: [{
destination: B [{
duration: 10,
type: male
},
duration: 12,
type: female
},
duration: 9,
type: female
}]
]}
}
Basically I need to group "type" and "duration" together under the same destination.
I came up with the following query, but this results in a a single field for "type" for each "destination", but not for every "duration".
db.test.aggregate(
{
$group: {
_id: {"StationID": "$start", "EndStationID": "$destination"},
durations: {$addToSet: "$duration" },
usertypes: {$addToSet: "$type" }
}
},
{
$group: {
_id: "$_id.StationID",
Tripcount_out: {$sum: "durations"},
Trips_out: { $addToSet: { EndStationID: "$_id.EndStationID", Tripduration: "$durations", Usertype: "$usertypes"} }
}
}
)
My question is how I can achieve the structure described above.
You could try running the following aggregate pipeline:
db.test.aggregate([
{
"$group": {
"_id": { "StationID": "$start", "EndStationID": "$destination" },
"details": {
"$push": {
"duration": "$duration",
"type": "$type"
}
}
}
},
{
"$group": {
"_id": "$_id.StationID",
"tripdetails": {
"$push": {
"destination": "$_id.EndStationID",
"trips": "$details"
}
}
}
}
])
which yields:
{
"_id" : "A",
"tripdetails" : [
{
"destination" : "B",
"trips" : [
{
"duration" : 10,
"type" : "male"
},
{
"duration" : 9,
"type" : "female"
},
{
"duration" : 12,
"type" : "female"
}
]
}
]
}

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