I thought it would be particularly helpful if you could map keys (aka field names) to be field values and vice versa. This feature did not exist (and still doesn't) so I filed a request for it in MongoDB issue tracking system.
Meanwhile, as I show here it is still possible to do this "projection" via aggregation if you know the names of the fields in advance.
But what if you get the names of fields dynamically, rather than always knowing they will be field1, field2?
I'm going to show how you can generate the appropriate aggregation framework pipeline programmatically based on the set of field names passed in. I'm going to use Javascript in the shell as the most general example, but you can translate this into your language of choice and adjust accordingly.
My sample documents and schema will be like this:
{
"_id" : 1,
"attr" : [
{ "k": "firstName",
"v": "Asya" },
{ "k": "lastName",
"v": "Kamsky" },
{ "k": "employer",
"v": "10gen, the MongoDB company" },
{ "k": "URL",
"v": "http://www.kamsky.org" }
]
}
Now imagine I want to output documents which have this shape:
{
"_id" : 1,
"firstName" : "Asya",
"lastName" : "Kamsky",
"employer" : "10gen, the MongoDB company"
}
/* my array of wanted fields */
fields = [ "firstName", "lastName", "employer"];
/* first I unwind the attributes array in each document */
unwind = {"$unwind" : "$attr"};
/* I only keep the attributes I want to return */
match = { "$match" : { "attr.k" : { "$in" : fields } } };
/* I create new fields by setting correct value if key *
* matches, or some known value I can "skip" later */
project = { "$project" : { } } ;
fields.forEach( function(f) {
project["$project"][f] = { "$cond" :
[ { "$eq" : [ f, "$attr.k" ] },
"$attr.v", " skip"
] };
} );
/* I regroup the original document using $max to *
* trick it into keeping only non-skip value */
group = { "$group" : { "_id" : "$_id" } } ;
fields.forEach( function(f) {
group["$group"][f] = { "$max" : "$" + f };
} );
/* now run the aggregation */
db.collection.aggregate( unwind, match, project, group );
{
"result" : [
{
"_id" : 1,
"firstName" : "Asya",
"lastName" : "Kamsky",
"employer" : "10gen, the MongoDB company"
}
],
"ok" : 1
}
- no duplicate attribute names for a particular document
- all attribute values would be alphanumeric and compare greater than "space" character (ASCII x20)
I hope this is helpful, if not for actual implementation, at least for thinking about how you want to structure your documents. In the future I will take a closer look at advantages and disadvantages of different ways of storing attributes in your schema design.