Mongo: Selecting X elements from an array placed in an object - mongodb

I have the following collection for a user in a MongoDB:
{
"_id" : 1,
"facebook_id" : XX,
"name": "John Doe",
"points_snapshot" : [{
"unix_timestamp" : 1312300552,
"points" : 115
}, {
"unix_timestamp" : 1312330380,
"points" : 110
}, {
"unix_timestamp" : 1312331610,
"points" : 115
}]
}
Is it possible to write one query which by which I can get the user for id 1 along with the snapshots after a particular day. eg: 1312330300?
Basically limit the snapshots to X numbers matching some criteria?
So far, I have tried in C#:
Query.And(
Query.EQ("facebook_id", XX),
Query.GTE("points_snapshot.unix_timestamp", sinceDateTimeStamp)))
.SetFields("daily_points_snapshot", "facebook_id")
which I soon realised will not work for what I want.
Any help will be appreciated!
Thanks!
EDIT: My Solution to this
If anyone is looking to get a quick fix to this, this is what I ended up doing:
var points = MyDatabase[COLLECTION_NAME]
.Find(Query.EQ("facebook_id", XX))
.SetFields("points_snapshot", "facebook_id")
.FirstOrDefault();
if (points != null) {
var pointsArray = points.AsBsonDocument["points_snapshot"].AsBsonArray
.Where(x => x.AsBsonDocument["unix_timestamp"].AsInt32 > sinceDateTimeStamp)
.ToList();
return pointsArray;
}

Is it possible to write one query which by which I can get the user for id 1 along with the snapshots after a particular day. eg: 1312330300?
Not possible.
The problem here is that MongoDB queries return all matching Documents. Your Document contains an array of objects, but MongoDB does not treat these as "Documents".
Now, you can limit the fields that are returned, but in your case you actually want to limit the "portions of an array" that are returned.
This is a long-outstanding issue, the JIRA ticket is here. The ticket is dated about 18 months ago and it continues to get pushed back. So if this type of query is critical to your architecture, you may have to re-design the architecture.

Related

How do I update values in a nested array?

I would like to preface this with saying that english is not my mother tongue, if any of my explanations are vague or don't make sense, please let me know and I will attempt to make them clearer.
I have a document containing some nested data. Currently product and customer are arrays, I would prefer to have them as straight up ObjectIDs.
{
"_id" : ObjectId("5bab713622c97440f287f2bf"),
"created_at" : ISODate("2018-09-26T13:44:54.431Z"),
"prod_line" : ObjectId("5b878e4c22c9745f1090de66"),
"entries" : [
{
"order_number" : "123",
"product" : [
ObjectId("5ba8a0e822c974290b2ea18d")
],
"customer" : [
ObjectId("5b86a20922c9745f1a6408d4")
],
"quantity" : "14"
},
{
"order_number" : "456",
"product" : [
ObjectId("5b878ed322c9745f1090de6c")
],
"customer" : [
ObjectId("5b86a20922c9745f1a6408d5")
],
"quantity" : "12"
}
]
}
I tried using the following query to update it, however that proved unsuccessful as Mongo didn't behave quite as I had expected.
db.Document.find().forEach(function(doc){
doc.entries.forEach(function(entry){
var entry_id = entry.product[0]
db.Document.update({_id: doc._id}, {$set:{'product': entry_id}});
print(entry_id)
})
})
With this query it sets product in the root of the object, not quite what I had hoped for. What I was hoping to do was to iterate through entries and change each individual product and customer to be only their ObjectId and not an array. Is it possible to do this via the mongo shell or do I have to look for another way to accomplish this? Thanks!
In order to accomplish your specified behavior, you just need to modify your query structure a bit. Take a look here for the specific MongoDB documentation on how to accomplish this. I will also propose an update to your code below:
db.Document.find().forEach(function(doc) {
doc.entries.forEach(function(entry, index) {
var productElementKey = 'entries.' + index + '.product';
var productSetObject = {};
productSetObject[productElementKey] = entry.product[0];
db.Document.update({_id: doc._id}, {$set: productSetObject});
print(entry_id)
})
})
The problem that you were having is that you were not updating the specific element within the entries array, but rather adding a new key to the top-level of the document named product. Generally, in order to set the value of an inner document within an array, you need to specify the array key first (entries in this case) and the inner document key second (product in this case). Since you are trying to set specific elements within the entries array, you need to also specify the index in your query object, I have specified above.
In order to update the customer key in the inner documents, simply switch out the product for customer in my above code.
You're trying to add a property 'product' directly into your document with this line
db.Document.update({_id: doc._id}, {$set:{'product': entry_id}});
Try to modify all your entries first, then update your document with this new array of entries.
db.Document.find().forEach(function(doc){
let updatedEntries = [];
doc.entries.forEach(function(entry){
let newEntry = {};
newEntry["order_number"] = entry.order_number;
newEntry["product"] = entry.product[0];
newEntry["customer"] = entry.customer[0];
newEntry["quantity"] = entry.quantity;
updatedEntries.push(newEntry);
})
db.Document.update({_id: doc._id}, {$set:{'entries': updatedEntries}});
})
You'll need to enumerate all the documents and then update the documents one and a time with the value store in the first item of the array for product and customer from each entry:
db.documents.find().snapshot().forEach(function (elem) {
elem.entries.forEach(function(entry){
db.documents.update({
_id: elem._id,
"entries.order_number": entry.order_number
}, {
$set: {
"entries.$.product" : entry.product[0],
"entries.$.customer" : entry.customer[0]
}
}
);
});
});
Instead of doing 2 updates each time you could possibly use the filtered positional operator to do all updates to all arrays items within one update query.

Mongo DB query difference

Can someone please tell me the difference b/w following two queries. Both works for me and seems to be giving correct results, but i am not sure if really there is any difference or not
Retrieve all students having scores between 80 and 95
var query1 = { 'grade' : {"$gt":80}, 'grade' : {"$lt":95} };
var query2 = { 'grade' : {"$gt":80,"$lt":95} };
I think that you will find that your first form does not actually work, even if it did for you on a minimal sample. And it will fail for a very good reason. Consider the following documents:
{ "grade" : 90 }
{ "grade" : 96 }
{ "grade" : 80 }
If you issue your first query form you will get this for a result:
{ "grade" : 90 }
{ "grade" : 80 }
The reason being that you cannot have the "same key" in a document like this, and one will negate the other. In this case the "right" side key takes precedence over the left key and overwrites. This is common behavior for hash or dictionary structures.
This is why the second form is required and will of course return only the document that matches the conditions that are intended to be specified.
When you have an actual case to use the same field and probably using different conditions you can use the $and operator. Not the best example, but just to clarify:
db.collection.find({ "$and": [
{ "grade": { "$gt": 80 } },
{ "grade": { "$lt": 95 } },
]})
It's real purpose is to combine different conditions on the same field.
For your case though, use the second form you specified.
You should use the second query for the following reason: you query is actually a JSON. And in your first query you are providing a duplicate key (grade).
Nonetheless they are permitted by the JSON RFC, however even Doug Crockford mentioned that he regrets leaving this ambiguity in the spec because it inevitably leads to all kinds of confusion.
Maybe Mongo parses it correctly right now (or may be rather in your case), but you do not know for how long will it be this way (and some other json parsers tells you that you have an error).
So the best way is to think about the first query as bad and only use second one.

Unable to get some info in a subdocument

I am trying to get a value in my mongoDB collection. I would like to get the title of a movie and the sales (nbSold) of this movie for the current month.
Here is how my data are stored :
"_id" : ObjectId("52e6a1aacf0b3b522a8a157a"),
"title" : "Pulp Fiction",
"sales" : [
{
"date" : ISODate("2013-11-01T00:00:00Z"),
"nbSold" : 6
},
{
"date" : ISODate("2013-12-01T00:00:00Z"),
"nbSold" : 2
}
]
I'm using mongoose and this is how I build my query for the december of 2013 :
var query = Movie.find({"title":"Pulp Fiction"}, "title sales.nbSold")
.where("sales.date")
.equals(new Date("2013-12-01"));
However, this is the output that I am receiving :
{ title: 'Pulp Fiction', sales: [ { nbSold: 6 }, { nbSold: 2 } ] }
I would like to have only the title associated with the nbSold of the current month (2 in my case). What is the correct way to do this ?
Thanks a lot for your help.
First off, you should close your where call. You should call .exec(callback) when you're done comparing, and you should be using select instead of equals along with $elemMatch. Try this:
var query = Movie.find({"title":"Pulp Fiction"}, "title sales.nbSold")
.where("sales.date")
.select({ sales: { $elemMatch: new Date("2013-12-01") }})
.exec(function(err, doc) {
return console.dir(doc);
});
You should also have a callback. I ran this on my machine, and it definitely works. I posted code earlier that wasn't quite right, but this does the trick. See the documentation for an example.
Also, I'd be concerned as to how you're seeing if the date matches. If the time is off in your Date object but the date matches, Mongo won't find a match. And I don't know exactly how it works in Mongo, but in JavaScript, you can't compare two Date objects directly for equality as they are both different objects with the same value, so you may come across that problem as well. See this post for an example on how to do it.

Mongodb - Update at multiple places in the same document

I have a document something like this :
myDoc : {
_id : a101
name : John,
batch : [{
_id : batch101,
value : physics
},{
_id : batch102,
value : chemistry
},{
_id : batch103,
value : maths
}]
}
I want to update the "value" to "computers" where the batch._id is either "batch101" or "batch102" (not batch103).
Please help! Thanks in advance.
-Manish :)
As noted in comments on the orignal question, the best solution here might be to work with the document on the client side. I will break this down into a few steps:
1) Query for the document. You mentioned you are working in node, so this will be represented in JSON. Lets call this variable x
2) Go through your document finding the elements you want to update, this might look like:
for(i = 0; i < x.batch.length; i++) {
if(x.batch[i]._id == 'batch101') {
//do something
}
}
(this code is not complete, obviously, but gives you an idea of what you want)
3) Now, using this changed document, update the old document in mongoDB.
This process should allow you to achieve your goal of updating certain elements in the batch list.

Get position of selected document in collection [mongoDB]

How to get position (index) of selected document in mongo collection?
E.g.
this document: db.myCollection.find({"id":12345})
has index 3 in myCollection
myCollection:
id: 12340, name: 'G'
id: 12343, name: 'V'
id: 12345, name: 'A'
id: 12348, name: 'N'
If your requirement is to find the position of the document irrespective of any order, that is not
possible as MongoDb does not store the documents in specific order.
However,if you want to know the index based on some field, say _id , you can use this method.
If you are strictly following auto increments in your _id field. You can count all the documents
that have value less than that _id, say n , then n + 1 would be index of the document based on _id.
n = db.myCollection.find({"id": { "$lt" : 12345}}).count() ;
This would also be valid if documents are deleted from the collection.
As far as I know, there is no single command to do this, and this is impossible in general case (see Derick's answer). However, using count() for a query done on an ordered id value field seems to work. Warning: this assumes that there is a reliably ordered field, which is difficult to achieve in a concurrent writer case. In this example _id is used, however this will only work with a single writer case.:
MongoDB shell version: 2.0.1
connecting to: test
> use so_test
switched to db so_test
> db.example.insert({name: 'A'})
> db.example.insert({name: 'B'})
> db.example.insert({name: 'C'})
> db.example.insert({name: 'D'})
> db.example.insert({name: 'E'})
> db.example.insert({name: 'F'})
> db.example.find()
{ "_id" : ObjectId("4fc5f040fb359c680edf1a7b"), "name" : "A" }
{ "_id" : ObjectId("4fc5f046fb359c680edf1a7c"), "name" : "B" }
{ "_id" : ObjectId("4fc5f04afb359c680edf1a7d"), "name" : "C" }
{ "_id" : ObjectId("4fc5f04dfb359c680edf1a7e"), "name" : "D" }
{ "_id" : ObjectId("4fc5f050fb359c680edf1a7f"), "name" : "E" }
{ "_id" : ObjectId("4fc5f053fb359c680edf1a80"), "name" : "F" }
> db.example.find({_id: ObjectId("4fc5f050fb359c680edf1a7f")})
{ "_id" : ObjectId("4fc5f050fb359c680edf1a7f"), "name" : "E" }
> db.example.find({_id: {$lte: ObjectId("4fc5f050fb359c680edf1a7f")}}).count()
5
>
This should also be fairly fast if the queried field is indexed. The example is in mongo shell, but count() should be available in all driver libs as well.
This might be very slow but straightforward method. Here you can pass as usual query. Just I am looping all the documents and checking if condition to match the record. Here I am checking with _id field. You can use any other single field or multiple fields to check it.
var docIndex = 0;
db.url_list.find({},{"_id":1}).forEach(function(doc){
docIndex++;
if("5801ed58a8242ba30e8b46fa"==doc["_id"]){
print('document position is...' + docIndex);
return false;
}
});
There is no way that MongoDB can return this as it does not keep documents in order in the database, just like MySQL f.e. doesn't name row numbers.
The ObjectID trick from jhonkola will only work if only one client creates new elements, as the ObjectIDs are generated on the client side, with the first part being a timestamp. There is no guaranteed order if different clients talk to the same server. Still, I would not rely on this.
I also don't quite understand what you are trying to do though, so perhaps mention that in your question? I can then update the answer.
Restructure your collection to include the position of any entry i.e {'id': 12340, 'name': 'G', 'position': 1} then when searching the database collection(myCollection) using the desired position as a query
The queries I use that return the entire collection all use sort to get a reproducible order, find.sort.forEach works with the script above to get the correct index.