2014년 12월 13일 토요일

Combination of Elements Satisfies the Criteria

How can I formulate a query to return elements that match multiple criteria of a embedded document? 

I've got data that looks like this:


{
  _id: 100,
  type: "food",
  item: "xyz",
  qty: 25,
  price: 2.5,
  ratings: [ 5, 8, 9 ],
  memos: [ { memo: "on time", by: "shipping", "subject": "warehouse" } ]
}

I've got a query that looks like this:

db.inventory.find(

  {'memos':
     {'memo': 'on time',
      'by': 'shipping'
     }
  }
);

This returns 0 records.

How can I formulate my query to return the element above?   I want a combination of elements but it doesn't have to match all elements (i.e, query doesn't need to include subject='warehouse') to satisfy the match.

This doesn't quite do what I want either:

db.inventory.find(
  {
    'memos.memo': 'on time',
    'memos.by': 'shipping'
  }
)

Because this will queries for documents where the memos array contains elements that in some combination satisfy the query conditions; e.g. one element satisfies the field memo equal to 'on time' condition and another element satisfies the field by equal to 'shipping' condition, or a single element can satisfy both criteria.   I need the query to satisfy the criteria all within the same element/embedded document.



You're looking for $elemMatch.

// matches parts of a single document in array
db.test.drop()
db.test.insert({ "memos" : [{ "memo" : "on time", "by" : "shipping", "subject" : "warehouse" }] })
db.test.count({ "memos" : { "$elemMatch" : { "memo" : "on time", "by" : "shipping" } } })
1

// doesn't match if all parts present in separate documents in array but not all in same document
db.test.drop()
db.test.insert({ "memos" : [{ "memo" : "on time" }] })
db.test.insert({ "memos" : [{ "by" : "shipping" }] })
db.test.count({ "memos" : { "$elemMatch" : { "memo" : "on time", "by" : "shipping" } } })
0



Thank you!   It is a lot closer to what I'm looking for, but not quite.   

1) My data isn't setup in a array.     So I need something to match on the partial data, even if it isn't in an array:

Consider:
db.test.drop();
db.test.insert({"document": {"name": "x", "notes": { "memo" : "on time", "by" : "shipping", "subject" : "warehouse" }}});
db.test.insert({"document": {"name": "y", "notes": { "memo" : "on time", "by" : "shipping", "subject" : "lot" }}});

So I'm looking for a query something like this to work:

db.test.count({"document": {"notes": { "$elemMatch": {"memo" : "on time", "by" : "shipping"}}}});

Right now returns 0... From the docs, $elemMatch only works on array data, my data isn't in an array. Also, notice in this particular use case, the criteria I'm looking for is a bit deeper embedded in the document, so I'm looking for the results to include both document with name=x and name=y.



I think you actually just need dot notation, if there's no array.

> db.test.count({ "document.notes.memo" : "on time", "document.notes.by" : "shipping" })
2



Ok yeah you are right...   But how do you do it if it is in an array (original case) though, consider:

db.test.drop();
db.test.insert({"document": {"name": "x", "notes": [{ "memo" : "on time", "by" : "fedex", "subject" : "warehouse" }, { "memo" : "ahead", "by" : "shipping", "subject" : "warehouse2" }]}});
db.test.insert({"document": {"name": "y", "notes": [{ "memo" : "past due", "by" : "shipping", "subject" : "lot" }, { "memo" : "past due", "by" : "shipping", "subject" : "lot2" }]}});

db.test.findOne({ "document.notes.memo" : "on time", "document.notes.by" : "shipping" }) - notice below doesn't have "on time" and "shipping" together.

{
"_id" : ObjectId("5489fb64da68aca8e62fefe7"),
"document" : {
"name" : "x",
"notes" : [
{
"memo" : "on time",
"by" : "fedex",
"subject" : "warehouse"
},
{
"memo" : "ahead",
"by" : "shipping",
"subject" : "warehouse2"
}
]
}

}

That won't work above.   This won't work either:

db.test.count({"document": {"notes": { "$elemMatch": {"memo" : "on time", "by" : "shipping"}}}});

Is there a way to do the $elemMatch within a embedded document so it doesn't consider "name" in the search, the criteria could be deep so would just like to ignore all the values up to the criteria, like dotted notation does in the case above.



Combine the two: you want multiple conditions satisfied by a single array element in an array whose field path is "document.notes".

db.test.count({ "document.notes" : { "$elemMatch" : { "memo" : "on time", "by" : "shipping" } } })



That is exactly what I was looking for!  And seems so obvious too, very much appreciate your help on this.


댓글 없음:

댓글 쓰기