MongoDB Convert sigle object to array of object based on key:value - mongodb

Is there a way by using mongodb queries, convert a sigle object field to an array of object but key:value based?
Before
"fields" : {
"field1" : 1,
"field2" : true,
"field3" : false,
"field4" : "any string value"
}
After
"fields" : [
{
"name" : "field1",
"value": 1
},
{
"name" : "field2",
"value" : true
},
{
"name" : "field3",
"value" : false
},
{
"name" : "field4",
"value" : "any string value"
}
]

You can use $objectToArray in an aggregation pipeline:
db.collection.aggregate([
{
"$set": {
"fields": {
"$objectToArray": "$fields"
}
}
}
])
Example here

Related

MongoDB Grouping and Project Custom Fields

I am stuck with MongoDB grouping and project custom fields. I have the following collection:
{
"DescId" : "1",
"Desc" : "Testing",
"ParentId" : "null",
"Order" : 1.0,
"Type" : "A",
"Parent" : null
}
{
"DescId" : "1.1",
"Desc" : "Testing Child 1",
"ParentId" : "1",
"Order" : 1.0,
"Type" : "B",
"Parent" : "Testing"
}
{
"DescId" : "1.2",
"Desc" : "Testing Child 2",
"ParentId" : "1",
"Order" : 2.0,
"Type" : "B",
"Parent" : "Testing"
}
I have done following grouping based on Type, DescId, Desc fields and projected DescId and Desc
db.getCollection("GenericData").aggregate(
[
{
"$group" : {
"_id" : {
"Type" : "$Type",
"DescId" : "$DescId",
"DescName" : "$Desc"
}
}
},
{
"$project" : {
"_id" : 0.0,
"Id" : "$_id.DescId",
"Name" : "$_id.DescName",
"Type" : "$_id.Type"
}
}
],
{
"allowDiskUse" : false
}
);
This is the output I am getting:
{
"Id" : "1.2",
"Name" : "Testing Child 2",
"Type" : "B"
}
{
"Id" : "1.1",
"Name" : "Testing Child 1",
"Type" : "B"
}
{
"Id" : "1",
"Name" : "Testing",
"Type" : "A"
}
Would it be possible to project fields based on Type field, like concatenating Type value with field name like following:
{
"A" + "Id" : "1",
"A" + "Name" : "Testing"
},
{
"B" + "Id" : "1.1",
"B" + "Name" : "Testing Child 1"
}
{
"B" + "Id" : "1.2",
"B" + "Name" : "Testing Child 2"
}
To rename your object's keys you have to use $objectToArray and $arrayToObject operators. First one can convert your $$ROOT object into an array of keys and values. Then you can apply $map to modify keys (using $concat) and $filter to exclude Type key. Then you can convert that array back to an object using $arrayToObject and promote that object to a root level using $replaceRoot. So you can add below stage to your aggregation pipeline:
db.GenericData.aggregate([
{
$replaceRoot: {
newRoot: {
$arrayToObject: {
$map: {
input: {
$filter: {
input: { $objectToArray: "$$ROOT" },
as: "kv",
cond: { $in: [ "$$kv.k", [ "Id", "Name" ] ] }
}
},
as: "kv",
in: {
k: { $concat: [ "$Type", "$$kv.k" ] },
v: "$$kv.v"
}
}
}
}
}
}
])
outputs:
{ "BId" : "1.2", "BName" : "Testing Child 2" }
{ "BId" : "1.1", "BName" : "Testing Child 1" }
{ "AId" : "1", "AName" : "Testing" }
EDIT:
If you'd like to explicitly specify which properties should be projected you can use $in operator inside $filter

Mongo aggregation, project a subfield of the first element in the array

I have a sub collection of elements and I want to project a certain subfield of the FIRST item in this collection. I have the following but it only projects the field for ALL elements in the array.
Items is the subcollection of Orders and each Item object has a Details sub object and an ItemName below that. I want to only return the item name of the FIRST item in the list. This returns the item name of every item in the list.
How can I tweak it?
db.getCollection('Orders').aggregate
([
{ $match : { "Instructions.1" : { $exists : true }}},
{ $project: {
_id:0,
'UserId': '$User.EntityId',
'ItemName': '$Items.Details.ItemName'
}
}
]);
Updated:
{
"_id" : "order-666156",
"State" : "ValidationFailed",
"LastUpdated" : {
"DateTime" : ISODate("2017-09-26T08:54:16.241Z"),
"Ticks" : NumberLong(636420128562417375)
},
"SourceOrderId" : "666156",
"User" : {
"EntityId" : NumberLong(34450),
"Name" : "Bill Baker",
"Country" : "United States",
"Region" : "North America",
"CountryISOCode" : "US",
},
"Region" : null,
"Currency" : null,
"Items" : [
{
"ClientOrderId" : "18740113",
"OrigClientOrderId" : "18740113",
"Quantity" : NumberDecimal("7487.0"),
"TransactDateTime" : {
"DateTime" : Date(-62135596800000),
"Ticks" : NumberLong(0)
},
"Text" : null,
"LocateRequired" : false,
"Details" : {
"ItemName" : "Test Item 1",
"ItemCost" : 1495.20
}
},
{
"ClientOrderId" : "18740116",
"OrigClientOrderId" : "18740116",
"Quantity" : NumberDecimal("241.0"),
"TransactDateTime" : {
"DateTime" : Date(-62135596800000),
"Ticks" : NumberLong(0)
},
"Text" : null,
"LocateRequired" : false,
"Details" : {
"ItemName" : "Test Item 2",
"ItemCost" : 2152.64
}
}
]
}
In case your are using at least MongoDB v3.2 you can use the $arrayElemAt operator for that. The below query does what you want. It will, however, not return any data for the sample you provided because the "Instructions.1": { $exists: true } filter removes the sample document.
db.getCollection('Orders').aggregate([{
$match: {
"Instructions.1": {
$exists: true
}
}
}, {
$project: {
"_id": 0,
"UserId": "$User.EntityId",
"ItemName": { $arrayElemAt: [ "$Items.Details.ItemName", 0 /* first item! */] }
}
}])

Project only some fields of array items in sub document

How can I project only particular fields of items in array in sub document?
Consider the following (simplified) example:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"name" : "Altima",
"body" : {
"type" : 2,
"maxprice" : 31800.00,
"minprice" : 21500.00
}
},
{
"name" : "Maxima",
"body" : {
"type" : 2,
"maxprice" : 39200.00,
"minprice" : 28800.00
}
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"name" : "Accord",
"body" : {
"type" : 2,
"maxprice" : 34100.00,
"minprice" : 20400.00
}
},
{
"name" : "Civic",
"body" : {
"type" : 3,
"maxprice" : 27900.00,
"minprice" : 19800.00
}
}
]
}
After aggregation, I'd like to get the following output:
{
"_id" : ObjectId("573d70df080cc2cbe8bf3222"),
"name" : "Nissan",
"models" : [
{
"type" : 2,
"minprice" : 21500.00
},
{
"type" : 2,
"minprice" : 28800.00
}
]
},
{
"_id" : ObjectId("80cc2cbe8bf3222573d70df0"),
"name" : "Honda",
"models" : [
{
"type" : 2,
"minprice" : 20400.00
},
{
"type" : 3,
"minprice" : 19800.00
}
]
}
So it basically gets all documents, all fields of documents, all items in models array, BUT only some fields of the array items in models. Please help.
You need to $project the "models" field using the $map operator.
db.collection.aggregate([
{ "$project": {
"name": 1,
"models": {
"$map": {
"input": "$models",
"as": "m",
"in": {
"type": "$$m.body.type",
"minprice": "$$m.body.minprice"
}
}
}
}}
])
$unwind is your friend
First you can basically filter the (non nested) fields you want.
var projection = {$project:{name:'$name', models:'$models'}};
db.dum.aggregate(projection)
Foreach of your models, you issue a document
var unwindModels = {$unwind:{'$models'}}
db.dum.aggregate(projection, unwindModels)
The idea is that every document issued from your models field will be regrouped later on via the _id field.
Foreach document, you only keep the (sub)fields you want
var keepSubFields = {$project:{name:'$name', type:'$models.body.type', minprice:'$models.body.minprice'}}
db.dum.aggregate(projection, unwindModels, keepSubFields)
Then you reaggregate your models as an array (thanks to the _id of each record which tracks the original record)
var aggregateModels = {$group:{_id:'$_id', name:{$last:'$name'}, models:{$push:{type:'$type', minprice:'$minprice'}}}}
db.dum.aggregate(projection, unwindModels, keepSubFields, aggregateModels)
note1: Here we can use $last because our primary key is not _id but <_id, name>. ($first would be good too)
note2: we refer type by $type, because when you iterate the collection on the aggregateModels stage, your record is of the form
<_id, name, type, minprice>

Change value in nested document in MongoDB-array

I have document:
{
"_id" : ObjectId("5152ba0708c164359d5ca616"),
"info": "Some interesting information.",
"elements" : [
{
"status" : "accepted",
"id" : "10"
},
{
"status" : "waiting",
"id" : "20"
},
{
"status" : "accepted",
"id" : "30"
}
]
}
How can I modify "status" to "accepted" where "status" is "waiting" and "id" is 20?
use positional ("$") operator.
// single
db.collection.update({ _id: ..., "elements.id": "20" }, { $set: { "elements.$.status": "status" } })
// all
db.collection.update({ "elements.id": "20" }, { $set: { "elements.$.status": "status" } }, { multi: true })
You have to use elemMatch (http://docs.mongodb.org/manual/reference/projection/elemMatch/) . To match both the items of the array like status and id 20 , then you can use the $set command. The reason behind using elemMatch is that it will make sure that both the criteria is matched on the same subdocument before returning the result
db.CollectionName.update({"_id" : ObjectId("5152ba0708c164359d5ca616") , "elements" : { "$elemMatch : {"status" : "waiting" , "Id" : "20"}}},{ $set : {"elements.$.status" : "accepted"}});

In MongoDb, how to apply sort internal fields present in document?

My document looks like this
{
field1: somevalue,
name:xtz
nested_documents: [ // array of nested document
{ x:"1", y:"2" }, // first nested document
{ x:"2", y:"3" }, // second nested document
{ x:"-1", y:"3" }, // second nested document
// ...many more nested documents
]
}
How one can sort the data present in nested_documents?
Expected answer is shown below:
nested_documents: [ { x:"-1", y:"3" },{ x:"1", y:"2" },{ x:"2", y:"3" }]
To do this you would have to use the aggregation framework
db.test.aggregate([{$unwind:'$nested_documents'},{$sort:{'nested_documents.x':
1}}])
this returns
"result" : [
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "-1",
"y" : "3"
}
},
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "1",
"y" : "2"
}
},
{
"_id" : ObjectId("5139ba3dcd4e11c83f4cea12"),
"field1" : "somevalue",
"name" : "xtz",
"nested_documents" : {
"x" : "2",
"y" : "3"
}
}
],
"ok" : 1
Hope this helps