Retrieving value of an emedded object in mongo - mongodb

Followup Question
Thanks #4J41 for your spot on resolution. Along the same lines, I'd also like to validate one other thing.
I have a mongo document that contains an array of Strings, and I need to convert this particular array of strings into an array of object containing a key-value pair. Below is my curent appraoch to it.
Mongo Record:
Same mongo record in my initial question below.
Current Query:
templateAttributes.find({platform:"V1"}).map(function(c){
//instantiate a new array
var optionsArray = [];
for (var i=0;i< c['available']['Community']['attributes']['type']['values'].length; i++){
optionsArray[i] = {}; // creates a new object
optionsArray[i].label = c['available']['Community']['attributes']['type']['values'][i];
optionsArray[i].value = c['available']['Community']['attributes']['type']['values'][i];
}
return optionsArray;
})[0];
Result:
[{label:"well-known", value:"well-known"},
{label:"simple", value:"simple"},
{label:"complex", value:"complex"}]
Is my approach efficient enough, or is there a way to optimize the above query to get the same desired result?
Initial Question
I have a mongo document like below:
{
"_id" : ObjectId("57e3720836e36f63695a2ef2"),
"platform" : "A1",
"available" : {
"Community" : {
"attributes" : {
"type" : {
"values" : [
"well-known",
"simple",
"complex"
],
"defaultValue" : "well-known"
},
[......]
}
I'm trying to query the DB and retrieve only the value of defaultValue field.
I tried:
db.templateAttributes.find(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
)
as well as
db.templateAttributes.findOne(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
)
But they both seem to retrieve the entire object hirarchy like below:
{
"_id" : ObjectId("57e3720836e36f63695a2ef2"),
"available" : {
"Community" : {
"attributes" : {
"type" : {
"defaultValue" : "well-known"
}
}
}
}
}
The only way I could get it to work was with find and map function, but it seems to be convoluted a bit.
Does anyone have a simpler way to get this result?
db.templateAttributes.find(
{ platform: "A1" },
{ "available.Community.attributes.type.defaultValue": 1 }
).map(function(c){
return c['available']['Community']['attributes']['type']['defaultValue']
})[0]
Output
well-known

You could try the following.
Using find:
db.templateAttributes.find({ platform: "A1" }, { "available.Community.attributes.type.defaultValue": 1 }).toArray()[0]['available']['Community']['attributes']['type']['defaultValue']
Using findOne:
db.templateAttributes.findOne({ platform: "A1" }, { "available.Community.attributes.type.defaultValue": 1 })['available']['Community']['attributes']['type']['defaultValue']
Using aggregation:
db.templateAttributes.aggregate([
{"$match":{platform:"A1"}},
{"$project": {_id:0, default:"$available.Community.attributes.type.defaultValue"}}
]).toArray()[0].default
Output:
well-known
Edit: Answering the updated question: Please use aggregation here.
db.templateAttributes.aggregate([
{"$match":{platform:"A1"}}, {"$unwind": "$available.Community.attributes.type.values"},
{$group: {"_id": null, "val":{"$push":{label:"$available.Community.attributes.type.values",
value:"$available.Community.attributes.type.values"}}}}
]).toArray()[0].val
Output:
[
{
"label" : "well-known",
"value" : "well-known"
},
{
"label" : "simple",
"value" : "simple"
},
{
"label" : "complex",
"value" : "complex"
}
]

Related

Mongo DB aggregation pipeline: convert string to document/object

Have a field of type "String" that contain representation of an object/document
" {"a":35,b:[1,2,3,4]}"
I know is a strange construct but i can't change it.
my goal would be to extract for example the value of "a".
As the document represented by the string are nested and repeated a regex doesnt fit.
So how can i convert in a mongo db aggregation/query this String to object so that i can process it in a following aggregation step?
(could extract string with python make a dict and extract infos, but i'd like to stay inside the aggregation pipeline and so having better performance)
In 4.4 this works
db.target.aggregate([{$project: {
X: "$AD_GRAPHIC",
Y : {
$function : {
body: function(jsonString) {
return JSON.parse(jsonString)
},
args: [ "$AD_GRAPHIC"],
lang: "js"
}
}
}
}])
Basically use the $function operator to invoke the JSON parser. (assumes you have enabled Javascript)
Results
{ "_id" : ObjectId("60093dc8f2c829000e38a8d0"), "X" : "{\"alias\":\"MEDIA_DIR\",\"path\":\"modem.jpg\"}", "Y" : { "alias" : "MEDIA_DIR", "path" : "modem.jpg" } }
{ "_id" : ObjectId("60093dc8f2c829000e38a8d1"), "X" : "{\"alias\":\"MEDIA_DIR\",\"path\":\"monitor.jpg\"}", "Y" : { "alias" : "MEDIA_DIR", "path" : "monitor.jpg" } }
{ "_id" : ObjectId("60093dc8f2c829000e38a8d2"), "X" : "{\"alias\":\"MEDIA_DIR\",\"path\":\"mousepad.jpg\"}", "Y" : { "alias" : "MEDIA_DIR", "path" : "mousepad.jpg" } }
{ "_id" : ObjectId("60093dc8f2c829000e38a8d3"), "X" : "{\"alias\":\"MEDIA_DIR\",\"path\":\"keyboard.jpg\"}", "Y" : { "alias" : "MEDIA_DIR", "path" : "keyboard.jpg" } }
>
There's no native way in the MongoDB engine to parse a blob of JSON from a field. However, I'd recommend just doing it client-side in your language of choice and then if required save it back.
Alternatively, if your data is too big and still needs to aggregate it you could use regex and project out the required fields from the JSON to then use them later to filter etc...
For example if we insert the following document:
> db.test.insertOne({ name: 'test', blob: '{"a":35,b:[1,2,3,4]}' })
{
"acknowledged" : true,
"insertedId" : ObjectId("5ed9fe21b5d91941c9e85cdb")
}
We can then just project out the array with some regex:
db.test.aggregate([
{ $addFields: { b: { $regexFind: { input: "$blob", regex: /\[(((\d+,*))+)\]/ } } } },
{ $addFields: { b: { $split: [ { $arrayElemAt: [ "$b.captures", 0 ] }, "," ] } } }
]);
{
"_id" : ObjectId("5ed9fe21b5d91941c9e85cdb"),
"name" : "test",
"blob" : "{\"a\":35,b:[1,2,3,4]}",
"b" : [
"1",
"2",
"3",
"4"
]
}
This means we can do some filtering, sorting and any of the other aggregation stages.
You could just use JSON.parse()
For example
db.getCollection('system').find({
a: JSON.parse('{"a":35,b:[1,2,3,4]}').a
})

Insert new fields to document at given array index in MongoDB

I have the following document structure in a MongoDB collection :
{
"A" : [ {
"B" : [ { ... } ]
} ]
}
I'd like to update this to :
{
"A" : [ {
"B" : [ { ... } ],
"x" : [],
"y" : { ... }
} ]
}
In other words, I want the "x" and "y" fields to be added to the first element of the "A" array without loosing "B".
Ok as there is only one object in A array you could simply do as below :
Sample Collection Data :
{
"_id" : ObjectId("5e7c3cadc16b5679b4aeec26"),
A:[
{
B: [{ abc: 1 }]
}
]
}
Query :
/** Insert new fields into 'A' array's first object by index 0 */
db.collection.updateOne(
{ "_id" : ObjectId("5e7c3f77c16b5679b4af4caf") },
{ $set: { "A.0.x": [] , "A.0.y" : {abcInY :1 }} }
)
Output :
{
"_id" : ObjectId("5e7c3cadc16b5679b4aeec26"),
"A" : [
{
"B" : [
{
"abc" : 1
}
],
"x" : [],
"y" : {
"abcInY" : 1.0
}
}
]
}
Or Using positional operator $ :
db.collection.updateOne(
{ _id: ObjectId("5e7c3cadc16b5679b4aeec26") , 'A': {$exists : true}},
{ $set: { "A.$.x": [] , "A.$.y" : {abcInY :1 }} }
)
Note : Result will be the same, but functionally when positional operator is used fields x & y are inserted to first object of A array only when A field exists in that documents, if not this positional query would not insert anything (Optionally you can check A is an array condition as well if needed). But when you do updates using index 0 as like in first query if A doesn't exist in document then update would create an A field which is an object & insert fields inside it (Which might cause data inconsistency across docs with two types of A field) - Check below result of 1st query when A doesn't exists.
{
"_id" : ObjectId("5e7c3f77c16b5679b4af4caf"),
"noA" : 1,
"A" : {
"0" : {
"x" : [],
"y" : {
"abcInY" : 1.0
}
}
}
}
However, I think I was able to get anothe#whoami Thanks for the suggestion, I think your first solution should work. However, I think I was able to get another solution to this though I'm not sure if its better or worse (performance wise?) than what you have here. My solution is:
db.coll.update( { "_id" : ObjectId("5e7c4eb3a74cce7fd94a3fe7") }, [ { "$addFields" : { "A" : { "x" : [ 1, 2, 3 ], "y" : { "abc" } } } } ] )
The issue with this is that if "A" has more than one array entry then this will update all elements under "A" which is not something I want. Just out of curiosity is there a way of limiting this solution to only the first entry in "A"?

Mongo remove from nested object by value

I have a Mongo collection the consists of a document and a nested object describing what collections the document is in and when it was added. I would like to remove key-value pairs from a nested object based on a condition, e.g. is the value (a date) before 1-1-2016.
Example:
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : {
"c01" : ISODate("2016-10-27T15:52:04.512Z"),
"c02" : ISODate("2015-11-21T16:06:06.546Z")
}
}
needs to become
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : {
"c01" : ISODate("2016-10-27T15:52:04.512Z"),
}
}
One alternative would be to change the schema to something like this:
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : [
{
"id": "c01",
"date": ISODate("2016-10-27T15:52:04.512Z")
},
{
"id": "c02",
"date" : ISODate("2015-11-21T16:06:06.546Z")
}
]
}
in which case removing a document from a would be easy. I am a bit reluctant to do that because it would complicate some of the other queries I would like to support. Thanks!
I prefer the second structure for your schema
{
"_id" : ObjectId("581214940911ad3de98002db"),
"collections" : [
{
"id": "c01",
"date": ISODate("2016-10-27T15:52:04.512Z")
},
{
"id": "c02",
"date" : ISODate("2015-11-21T16:06:06.546Z")
}
]
}
then able to remove from collections like this
db.collectionName.update(
{ },// if you want can add query for specific Id {"_id" : requestId},
{ $pull: { collections: { date: {$lt: yourDate} } } }, // if need can convert iso date string like: new Date(yourDate).toISOString()
{ multi: true }
)

Script to add one value to array in mongo collection

/* 0 */
{
"_id" : ObjectId("55addc2f8dab32aca87ce0bd"),
"partNum" : "part1",
"dest" : "First Part",
"sales" : [
"sale1",
"sale2",
"sale3"
],
"salesData" : {
"sale1" : {
"mcode" : "mc11",
"dtype" : [
"AAA",
"BBB"
]
}
}
}
/* 1 */
{
"_id" : ObjectId("55addc408dab32aca87ce0be"),
"partNum" : "part2",
"dest" : "Second Part",
"sales" : [
"sale1",
"sale2",
"sale3"
],
"salesData" : {
"sale1" : {
"mcode" : "mc22",
"dtype" : [
"AAA",
"BBB"
]
}
}
}
I am not that much efficient in writing mongo script. My requirement is to append one more value to "dtype" array wherever "mcode" is "mc11" in all of the documents inside the collection. Above is the two document output from my collection. I was using the below script to do it and its not working. Can anyone please help me
db.testingRD.find().forEach( function(myDocument)
{
db.testingRD.update({id: myDocument._id}, {$push : {"salesData.sale1.dtype" : "DDD"}});
});
To append one more value to "dtype" array wherever "mcode" is "mc11", use the following update where the query object is the selection criteria for the update and is the same query selector as in the find() method, the update object has the $push modifications to apply and then the options document which is optional. If that is set to true, it updates multiple documents that meet the query criteria:
var query = { "salesData.sale1.mcode": "mc11" },
update = {
"$push": { "salesData.sale1.dtype": "DDD" }
},
options = { "multi": true };
db.testingRD.update(query, update, options);
You had a typing mistake in the script (you forgot an underscore):
db.testingRD.find().forEach( function(myDocument)
{
db.testingRD.update({_id: myDocument._id}, {$push : {"salesData.sale1.dtype" : "DDD"}});
});
I always use a trick when an update seams to not working: I change the update with a printjson + find so that I can see if it is matching anything:
db.testingRD.find().forEach( function(myDocument) { printjson(db.testingRD.find({_id: myDocument._id})) } );

Is it possible to query MongoDB, using ONLY Array([x][y[x][z]]) Approach? NOT knowing Elements' Content?

This is the first of 7 test/example documents, in collection "SoManySins."
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Treats" : "Sin1 = Gluttony",
"Sin1" : "Gluttony",
"Favourited" : "YES",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
I would like to be able to query to retrieve any info in any position,
just by referring to the position. The following document,
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Sin1" : "Gluttony",
"?????????" : "??????",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
One could retrieve whatever might be in the 3rd key~value
pair. Why should one have to know ahead of time what the
data is, in the key? If one has the same structure for the
collection, who needs to know? This way, you can get
double the efficiency? Like having a whole lot of mailboxes,
and your app's users supply the key and the value; your app
just queries the dbs' documents' arrays' positions.
Clara? finally? I hope?
The sample document you've provided is not saved as an array in BSON:
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"Sin1" : "Gluttony",
"?????????" : "??????",
"RecentActivity" : "YES",
"GoAgain?" : "YeaSure."
}
Depending on the MongoDB driver you are using, the fields here are typically represented in your application code as an associative array or hash. These data structures are not order-preserving so you cannot assume that the 3rd field in a given document will correspond to the same field in another document (or even that the same field ordering will be consistent on multiple fetches). You need to reference the field by name.
If you instead use an array for your fields, you can refer by position or select a subset of the array using the $slice projection.
Example document with an array of fields:
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"fields": [
{ "Sin1" : "Gluttony" },
{ "?????????" : "??????" },
{ "RecentActivity" : "YES" },
{ "GoAgain?" : "YeaSure." }
]
}
.. and query to find the second element of the fields array (a $slice with skip 1, limit 1):
db.SoManySins.find({}, { fields: { $slice: [1,1]} })
{
"_id" : ObjectId("51671bb6a6a02d7812000018"),
"fields" : [
{
"?????????" : "??????"
}
]
}
This is one way to Query and get back data when you may not
know what the data is, but you know the structure of the data:
examples in Mongo Shell, and in PHP
// the basics, setup:
$dbhost = 'localhost'; $dbname = 'test';
$m = new Mongo("mongodb://$dbhost");
$db = $m->$dbname;
$CursorFerWrites = $db->NEWthang;
// defining a set of data, creating a document with PHP:
$TheFieldGenerator = array( 'FieldxExp' => array(
array('Doc1 K1'=>'Val A1','Doc1 K2'=>'ValA2','Doc1 K3'=>'Val A3'),
array('Doc2 K1'=>'V1','Doc2 K2'=>'V2','Doc2 K3'=>'V3' ) ) ) ;
// then write it to MongoDB:
$CursorFerWrites->save($TheFieldGenerator);
NOTE : In the Shell : This produces the same Document:
> db.NEWthang.insert({"FieldxExp" : [
{"Doc1 K1":"Val A1","Doc1 K2":"Val A2","Doc1 K3":"Val A3"},
{"Doc2 K1":"V1", "Doc2 K2":"V2","Doc2 K3":"V3"}
]
})
#
Now, some mongodb Shell syntax:
> db.NEWthang.find().pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc1 K1" : "Val A1",
"Doc1 K2" : "Val A2",
"Doc1 K3" : "Val A3"
},
{
"Doc2 K1" : "V1",
"Doc2 K2" : "V2",
"Doc2 K3" : "V3"
}
]
}
> db.NEWthang.find({}, { "FieldxExp" : { $slice: [1,1]} } ).pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc2 K1" : "V1",
"Doc2 K2" : "V2",
"Doc2 K3" : "V3"
}
]
}
> db.NEWthang.find({}, { "FieldxExp" : { $slice: [0,1]} } ).pretty()
{
"_id" : ObjectId("516c4053baa133464d36e836"),
"FieldxExp" : [
{
"Doc1 K1" : "Val A1",
"Doc1 K2" : "Val A2",
"Doc1 K3" : "Val A3"
}
]
}
Finally, how about write the Query in some PHP ::
// these will be for building the MongoCursor:
$myEmptyArray = array();
$TheProjectionCriteria = array('FieldxExp'=> array('$slice' => array(1,1)));
// which gets set up here:
$CursorNEWthang1 = new MongoCollection($db, 'NEWthang');
// and now ready to make the Query/read:
$ReadomgomgPls=$CursorNEWthang1->find($myEmptyArray,$TheProjectionCriteria);
and the second document will be printed out:
foreach ($ReadomgomgPls as $somekey=>$AxMongoDBxDocFromCollection) {
var_dump($AxMongoDBxDocFromCollection);echo '<br />';
}
Hope this is helpful for a few folks.