2

I'm using mongoDB (mongoHQ) in my Flask app (mongoengine). I have a Document that looks like this:

{items: [{id: 1}, {id: 2}, {id: 3}]}

Is there a way to to reach, for example, the dict with id: 1 in a single query?

Currently I'm looping through the items list with a next() statement and I was hoping for a wiser solution. Thanks

Kreutzer
  • 328
  • 4
  • 12

2 Answers2

7

I'm not familiar with MongoEngine, but the $ projection operator can filter an array to show only the matched element. In the Mongo shell:

> db.foo.insert({"items": [{"id": 1}, {"id": 2}, {"id": 3}]})
> db.foo.find({'items.id': 1}, {'items.$': true})
{ "_id" : ObjectId("51ce29b68b178484ff2a01ed"), "items" : [  {  "id" : 1 } ] }

More info: http://docs.mongodb.org/manual/reference/projection/positional/

It seems in MongoEngine you do:

Foo.objects(__raw__={'items.id': 1}).only('items.$')

More: http://mongoengine-odm.readthedocs.org/en/latest/guide/querying.html#raw-queries

A. Jesse Jiryu Davis
  • 23,641
  • 4
  • 57
  • 70
  • yeah I came across this, but sadly I couldn't get it to work with mongoengine. thanks though – Kreutzer Jun 29 '13 at 08:57
  • I've been trying variations of what you suggested all morning, but no avail. It outputs an error about the `'items.$'` part. If I exclude it, I simply get a `` while what I really want to get is the dict. Thanks though – Kreutzer Jun 30 '13 at 07:55
  • this is the error btw: `'str' object has no attribute 'db_field'` – Kreutzer Jun 30 '13 at 08:12
0

In a DB query or python statement?

Using python:

In [21]: d = {"items": [{"id": 1}, {"id": 2}, {"id": 3}]}

In [22]: [i for i in d["items"] if i["id"] == 1]
Out[22]: [{'id': 1}]

(Add [0] to the end of the list comp to just get the dictionary)

Could also use a generator:

In [23]: (i for i in d["items"] if i["id"] == 1)
Out[23]: <generator object <genexpr> at 0x052A1D50>

In [24]: _.next()
Out[24]: {'id': 1}

I'd personally do it the above way, as we're working on a returned record from the DB then.

If you want to extract the field directly (Assuming we only want the first returned document that has {"id":1} in it):

In [35]: class Doc(Document):
    ...:     items = ListField(DictField())
    ...:     

In [36]: newdoc = Doc(items=[{"id":1},{"id":2},{"id":3}])

In [37]: newdoc.save()
Out[37]: <Doc: Doc object>

In [38]: for document in [d for d in Doc.objects(items__id=1).first().items if d["id"]==1]:
    ...:     print document
    ...:     
{u'id': 1}

Extracting just the dictionary that matches is harder, as the field is actually a list of dictionaries. You're not saving any speed by modifying the query to only return the part of the field that matches, so instead return the full field and from python just grab the bit you want. Same speed, and easier to maintain.

TyrantWave
  • 4,583
  • 2
  • 22
  • 25
  • Thanks but I meant in a query. As I mentioned I'm able to get what I need with manipulation in python. – Kreutzer Jun 28 '13 at 09:19
  • Amended with a version that extracts matching records from the db, however splicing a returned field of a record to just display the matching part would be slower to do on the DB side than from python – TyrantWave Jun 28 '13 at 10:10