Mongodb Aggregation Rows to Columns - mongodb

I have the following dataset. I need to group them by Account, and then turn the Element_Fieldname into a column.
var collection = [
{
Account:12345,
Element_Fieldname:"cars",
Element_Value:true
},
{
Account:12345,
Element_Fieldname:"boats",
Element_Value:false
}
]
This was my attempt to convert rows to columns, but its not working.
db.getCollection('my_collection').aggregate([{
$match : {
Element_Fieldname : {
$in : ["cars", "boats"]
}
}
}, {
$group : {
_id : "$Account",
values : {
$addToSet : {
field : "$Element_Fieldname",
value : "$Element_Value"
}
}
}
}, {
$project : {
Account : "$_id",
cars : {
"$cond" : [{
$eq : ["$Element_Fieldname", "cars"]
}, "$Element_Value", null]
},
boats : {
"$cond" : [{
$eq : ["$Element_Fieldname", "day_before_water_bottles"]
}, "$Element_Value", null]
},
}
}
])
This just gives me null in my cars and boats fields. Any help would be great.
And this is my desired results:
var desiredResult = [
{
Account:12345,
cars:true,
boats:false
}
]

this is a big tricky but you will get what you need :-)
please add $match on the top of aggregation pipeline
db.collection.aggregate([{
$project : {
_id : 0,
"Account" : 1,
car : {
$cond : [{
$eq : ["$Element_Fieldname", "cars"]
}, "$Element_Value", null]
},
boats : {
$cond : [{
$eq : ["$Element_Fieldname", "boats"]
}, "$Element_Value", null]
},
}
},
{
$group : {
_id : "$Account",
carData : {
$addToSet : "$car"
},
boatsData : {
$addToSet : "$boats"
}
}
}, {
$unwind : "$carData"
}, {
$match : {
carData : {
$ne : null
}
}
}, {
$unwind : "$boatsData"
}, {
$match : {
boatsData : {
$ne : null
}
}
},
])
and result
{
"_id" : 12345,
"carData" : true,
"boatsData" : false
}

It is not possible to do the type of computation you are describing with the aggregation framework, however there is a proposed $arrayToObject expression which will give you the functionality to peek into the key names, and create new key/values dynamically.
For example, you could do
db.collection.aggregate([
{
"$match": { "Element_Fieldname":{ "$in": ["cars", "boats"] } }
},
{
"$group": {
"_id": "$Account",
"attrs": {
"$push": {
"key": "$Element_Fieldname",
"val": "$Element_Value"
}
}
}
},
{
"$project": {
"Account": "$_id",
"_id": 0,
"newAttrs": {
"$arrayToObject": {
"$map": {
"input": "$attrs",
"as": "el",
in: ["$$el.key", "$$el.val"]
}
}
}
}
},
{
"$project": {
"Account": 1,
"cars": "$newAttrs.cars",
"boats": "$newAttrs.boats"
}
}
])
Vote for this jira ticket https://jira.mongodb.org/browse/SERVER-23310 to get this feature.
As a workaround, mapreduce seems like the available option. Consider running the following map-reduce operation:
db.collection.mapReduce(
function() {
var obj = {};
obj[this.Element_Fieldname] = this.Element_Value;
emit(this.Account, obj);
},
function(key, values) {
var obj = {};
values.forEach(function(value) {
Object.keys(value).forEach(function(key) {
obj[key] = value[key];
});
});
return obj;
},
{ "out": { "inline": 1 } }
)
Result:
{
"_id" : 12345,
"value" : {
"cars" : true,
"boats" : false
}
}

Related

MongoDB query to replace NULL values

I'm currently working with a MongoDB database and I have fields that have a value of NULL is there a way to run a query that will replace these NULL fields with a value of "Missing" instead?
An example of the document is:
{
"_id" : 1,
"Users" : [
{
"name" : "John Davies",
"age" : null,
"place_of_birth" : "Cardigan"
},
{
"name" : "Edward Jones",
"age" : null,
"place_of_birth" : null
},
{
"name" : "Daniel Rhys",
"age" : NumberLong(63),
"place_of_birth" : "Cardigan"
},
{
"name" : null,
"age" : NumberLong(61),
"place_of_birth" : "Cardigan"
},
{
"name" : "John Davies ",
"age" : null,
"place_of_birth" : "Cardigan"
}
]
}
Demo - https://mongoplayground.net/p/dsI5G6zfbLr
Use $[]
db.collection.update(
{},
{ $set: { "Users.$[u].age": "missing" } },
{ arrayFilters: [ { "u.age": null } ], multi: true}
)
Combine multiple queries into 1 using db.collection.bulkWrite
db.collection.bulkWrite( [
{ updateMany :
{
"filter": {},
"update": { $set: { "Users.$[u].age": "missing" } },
"arrayFilters": [ { "u.age": null } ],
}
},
{ updateMany :
{
"filter": {},
"update": { $set: { "Users.$[u].name": "missing" } },
"arrayFilters": [ { "u.name": null } ],
}
},
{ updateMany :
{
"filter": {},
"update": { $set: { "Users.$[u].place_of_birth": "missing" } },
"arrayFilters": [ { "u.place_of_birth": null } ],
}
}
] )
Update for MongoDB Version 3.2+
while (db.collection.find({$or:[{"Users.age":null},{"Users.name":null},{"Users.place_of_birth":null}]}).count()) {
db.collection.bulkWrite( [
{ updateMany :
{
"filter": { "Users.age": null },
"update": { $set: { "Users.$.age": "missing" } }
}
},
{ updateMany :
{
"filter": { "Users.name": null },
"update": { $set: { "Users.$.name": "missing" } },
}
},
{ updateMany :
{
"filter": { "Users.place_of_birth": null },
"update": { $set: { "Users.$.place_of_birth": "missing" } },
}
}
] )
}
Try update with aggregation pipeline starting from MongoDB 4.2,
$map to iterate loop of Users array
$objectToArray to convert current object in $map to array key-value pair
$map to iterate loop of above converted array
$ifNull to check if value is null then replace Missing otherwise remain same
$arrayToObject convert above key-value array to object format
db.collection.update({},
[{
$set: {
Users: {
$map: {
input: "$Users",
in: {
$arrayToObject: {
$map: {
input: { $objectToArray: "$$this" },
in: {
k: "$$this.k",
v: { $ifNull: ["$$this.v", "Missing"] }
}
}
}
}
}
}
}
}],
{ multi: true }
)
Playground
MongoDB version 3.2 or above:
set default value for replacement in variable nullReplace
find() query to get all documents from your collection and loop through forEach
for loop of user object and check condition if value is null then replace nullReplace variable
return user oibject
updateOne() to update updated Users array in your collection
var nullReplace = "Missing";
db.collection.find({}).forEach(function(doc) {
var Users = doc.Users.map(function(u) {
for (var u in userObj) {
if (userObj[u] === null) userObj[u] = nullReplace;
}
return userObj;
})
db.collection.updateOne({ _id: doc._id }, { $set: { Users: Users } });
});

Filter keys not in collection

How do we find keys which do not exist in collection.
Given an input list of keys ['3321', '2121', '5647'] , i want to return those that do not exist in the collection :
{ "_id" : { "$oid" : "5e2993b61886a22f400ea319" }, "scrip" : "5647" }
{ "_id" : { "$oid" : "5e2993b61886a22f400ea31a" }, "scrip" : "3553" }
So the expected output is ['3321', '2121']
This aggregation gets the desired output (works with MongoDB version 3.4 or later):
INPUT_ARRAY = ['3321', '2121', '5647']
db.test.aggregate( [
{
$match: {
scrip: {
$in: INPUT_ARRAY
}
}
},
{
$group: {
_id: null,
matches: { $push: "$scrip" }
}
},
{
$project: {
scrips_not_exist: { $setDifference: [ INPUT_ARRAY, "$matches" ] },
_id: 0
}
}
] )
The output:
{ "scrips_not_exist" : [ "3321", "2121" ] }

Query the count of sub elements with condition in Mongo DB

I am new to Mongo DB and would appreciate some help with the following query task.
I have a collection of documents that looks like below:
{
"field_1" : {
"subfield_1" : {
"subsubfield_1" : "true",
"subsubfield_2" : "false",
"subsubfield_3" : "true"
},
"subfield_2" : "sf2"
},
"field_2" : {
"subfield_1" : {
"subsubfield_1" : "true",
"subsubfield_2" : "false"
},
"subfield_2" : "sf2"
},
"field_3" : {
"subfield_1" : {
"subsubfield_1" : "true",
"subsubfield_2" : "false",
"subsubfield_3" : "false"
},
"subfield_2" : "sf2"
}
}
And I am trying to query such that I, for each element in the collection (1) specify exactly which fields to return (in this case subfield_1 and subfield_2, and, (2) for subfield_1only return the count of trueelements. So I would like the output to look like:
{
{
"subfield_1" : 2,
"subfield_2" : "sf2"
},
{
"subfield_1" : 1,
"subfield_2" : "sf2"
},
{
"subfield_1" : 1,
"subfield_2" : "sf2"
}
}
I have been trying this code but that only gives me the number of entries in subfield_1for each element:
db.getCollection('myCollection').aggregate(
{
$match: {<some other condition>}
},
{
$project: {
subfield_2: 1,
subfield_1: {'$size': '$subfield_1'}
}
}
)
Thanks in advance!
You need to run $objectToArray to transform your nested structer into an array of keys and values and then use $unwind to get a separate document for every sub-document. Then you can run another $objectToArray along with $filter to get only true values:
db.collection.aggregate([
{
$project: {
doc: { $objectToArray: "$$ROOT" }
}
},
{
$unwind: "$doc"
},
{
$match: { $expr: { $ne: [ "$doc.k", "_id" ] } }
},
{
$project: {
_id: 0,
subfield_1: { $size: { $filter: { input: { $objectToArray: "$doc.v.subfield_1" }, cond: { $eq: [ "$$this.v", "true" ] } } } },
subfield_2: "$doc.v.subfield_2"
}
}
])
Mongo Playground

Cant find duplicate values for array part in mongodb

db.school.find({ "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3" })
this is an example document from school collection of that query:
{
"_id" : ObjectId("57fafasf2323232323232f57682cd42"),
"status" : "wait",
"merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3",
"isValid" : false,
"fields" : { "schoolid" : {
"value" : "2323232",
"detail" : {
"revisedBy" : "teacher",
"revisionDate" : ISODate("2015-06-24T09:22:44.288+0000")
},
"history" : [
]
}}
}
I want to see which has duplcate schoolid. SO i do this:
db.school.aggregate([
{$match:{ "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3"
{ $group: {
_id: { fields.schoolid.value: "$fields.schoolid.value" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
but it gives error.
a lot of errors for a lot of lines
i tried to do like this
_id: { "fields.schoolid.value": "$fields.schoolid.value" },
or
_id: { 'fields.schoolid.value': "$'fields.schoolid.value'" },
but did not work. ow can i use it?
According to the document you provided, there is no fields field, so the group stage can't work. Your query should be :
db.school.aggregate([
{ $match: { "merchant" : "cc8c0421-e7fc-464d-9e1d-37e168b216c3"}},
{ $group: {
_id: { value: "$fields.schoolid.value" },
count: { $sum: 1 }
} },
{ $match: {
count: { $gte: 2 }
} },
{ $sort : { count : -1} },
{ $limit : 10 }
]);
Also note that fields.schoolid.value is not a valid fieldname, you need to enclode it in "" or to remove the "."

mongodb aggregation project objectId with concat

db.test.aggregate({
$match : { "themType" : "SuperTest" , "mType" : { "$in" : [ 1 , 2]}}
},
{ $project : { "_id" : 1, "refTestId" : 1, "avatar" : { $concat : [$refTestId] }
} });
and avatar returns me null, probably its because its objectId, is it possible in this query to make from this objectId string ?
From MongoDB 4.0 and newer, there is a $toString operator which returns the ObjectId value as a hexadecimal string:
db.test.aggregate([
{ "$match": {
"themType": "SuperTest",
"mType": { "$in" : [1 , 2] }
} },
{ "$addFields": {
"avatar": { "$toString": "$refTestId" }
} }
])
or using $convert
db.test.aggregate([
{ "$match": {
"themType": "SuperTest",
"mType": { "$in" : [1 , 2] }
} },
{ "$addFields": {
"avatar": {
"$convert": { "input": "$refTestId", "to": "string" }
}
} }
])
This isn't possible yet. WiP issue see: https://jira.mongodb.org/browse/SERVER-29512