1

After reading MongoDB update data in nested field I set a nested element using
db.users.update ({_id: '123'}, { '$set': {"friends.0.emails.0.email" : '2222'} });

How do I access that element? (just the sub element not the whole document)
e.g. db.users.findOne({_id: '123'}, {'$elem':"friends.0.emails.0.email"});

For example:
If it were a JavaScript object it'd be
db.users["123"].friends[0].emails[0].email
If it were a Python dict it'd be
db.users["123"]["friends"][0]["emails"][0]["email"]
If it were a Ruby Hash it'd also be
db.users["123"]["friends"][0]["emails"][0]["email"]

Ideally the answer would be a function that accepts a list of keys (e.g. [ "friend", 0, "emails", 0 , "email" ] ) and returns the value of that nested element (e.g. 2222).

I'm a Computer Science student and I work as a full stack dev: none of my peers, professors, co-workers or bosses can answer this question. I'm trying to learn mongo and accessing an element is one of the first things I thought to learn. I've spent days reading related stack overflow questions and Mongo documentation about $filter, $elem, the dot operator, and the $ operator, but nothing seems to explain how to simply access a nested element.

Here are some of the related questions I found that are not what I'm looking for
MongoDB - how to query for a nested item inside a collection?
^Talks about a range of elements (I only want 1)
MongoDB nested array query
^Checking membership of arrays-of-arrays (I just want to access an elem not check membership)
Get particular element from mongoDB array
^Filters using a query (color='Red') but I don't want to filter by a query, I want to retrieve an exact-unique index (e.g the 0th email)
retrieve a nested document with mgo
^this is kind of close, but the user never posted the mongo answer and instead referenced a go-Lang post. It also doesn't cover the possibility of arrays.

Jeff Hykin
  • 1,846
  • 16
  • 25
  • You are in serious problems. Terminology matters. Plain and simple, there is no notion of an embedded document in MongoDB. A document is a BSON object with an `_id` field. See [the BSON specification](http://bsonspec.org/spec.html) for details. Second, without seeing anything else, I can tell you that you overembedded your data. How to identify that? You have to ask how to answer the question you have on your data. In MongoDB (and most NoSQL in general), you model your data so that you get your answers in the mosz efficient way. Not for storage, not by entity. – Markus W Mahlberg Sep 05 '18 at 05:19
  • 1
    Thank you @Markus.1. You're right I am inexperienced with Mongo and I'm likely butchering the terminology, and that could be part of the problem. If there's any recommendations, I'll definitely change the question to make sense. I'll read up on BSON. 2. Sadly it's not my database, and I've told the owner the data is overembedded, but he's asking me to work with it anyways as-is. – Jeff Hykin Sep 05 '18 at 12:50
  • @MarkusWMahlberg That's not correct - mongodb does have the concept of embedded documents. https://docs.mongodb.com/manual/core/data-model-design/#embedded-data-models – RSax Jul 30 '20 at 07:56
  • @RSax Terminology matters: It is a sub-document, as even if you'd put an `_id` into it, you could not do a `db.yourcoll.find({"_id": })` on this `_id` field. And it would not be indexed by default. Basically, you'd just have a field that by chance is named `_id`. – Markus W Mahlberg Jul 30 '20 at 10:16

2 Answers2

0

In case anyone is still wondering about this, the solution seems to be in the MongoDB Docs:

The basic syntax is:

db.document.find({[YOUR SEARCH PARAMETER HERE]},{[THE FIELD(S) YOU WANT RETURNED HERE]}

In the docs they lay out an example, so I've pasted it here along with the SQL equivalent, which should help to clarify things.

Here's the set of documents and document structure:

db.inventory.insertMany( [
  { item: "journal", status: "A", size: { h: 14, w: 21, uom: "cm" }, instock: [ { warehouse: "A", qty: 5 } ] },
  { item: "notebook", status: "A",  size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "C", qty: 5 } ] },
  { item: "paper", status: "D", size: { h: 8.5, w: 11, uom: "in" }, instock: [ { warehouse: "A", qty: 60 } ] },
  { item: "planner", status: "D", size: { h: 22.85, w: 30, uom: "cm" }, instock: [ { warehouse: "A", qty: 40 } ] },
  { item: "postcard", status: "A", size: { h: 10, w: 15.25, uom: "cm" }, instock: [ { warehouse: "B", qty: 15 }, { warehouse: "C", qty: 35 } ] }
]);

Here's the query to get the fields "item" and "status" from the entries where "status = A"

db.inventory.find( { status: "A" }, { item: 1, status: 1 } )

The SQL equivalent is:

SELECT _id, item, status from inventory WHERE status = "A"

It's trivial to reduce this to selecting a single entry, rather than all entries where "status = A".

Chris
  • 1
  • This explains how to get a field, but it does not explain how to get neatest fields. I know how to use projections, with queries, but I need a projection that only returns a deeply nested value. For example `db.inventory.find( { item: "paper" }, **some code that makes this only return qty from inside the instock array** )` – Jeff Hykin Feb 04 '19 at 01:45
0

Two years later, I've found the answer myself. Chris's answer was very close, and I appreciate it being well-written with an example, but it didn't cover nested data which was the whole point.

If you have an object with an id of '123', and you want to get thatObject.friends[0].emails[0].email
run the following command:

// NOTE this string method does work for EVERY case (see explanation below) 
object = db.document.findOne({ _id: '123' }, {projection:{ "friends.0.emails.0.email": 1 }})
// DONT actually use eval, see `get` from `good-js` on npm for an example of non-eval retrieval
return eval("object.friends.0.emails.0.email")

This was unintuitive to me as, since there was a $set, I was looking for a $get. Most of time people want to get more than one thing though, which is why MongoDB has projections. The example above uses a positive projection (1 means: include-only-this), but you can also use negative projections (use 0 which means: get everything-except this thing).

My problem with the string "friends.0.emails.0.email"

I would not normally accept this kind answer: what if thatObject.friends[0] was something like thatObject["friends.txt"][0]? (Which is a perfectly valid JSON/Javascript key.) It would result in "friends.txt.0.emails.0.email" which would break!
Turns out, much to my dismay,

  1. periods are not allowed at all in MongoDB keys
  2. starting a key with a $ also isn't allowed
  3. Everything else in UTF-8 is allowed

This results in some very strange looking possible queries
"i am a key with spaces.im a key as well: ))(@U%@#%(*%)*%#^_%#*@$_"
Those are two valid keys separated by a period, and much to my discomfort, you can retrieve them like normal.

Jeff Hykin
  • 1,846
  • 16
  • 25