When I have two MongoDB documents like this...
db.test.insert( {"value" : "10123"} );
db.test.insert( {"value" : "160"} );
The result of a query like:
db.test.find({"value" :{$gt : "12"} });
is..
{ "_id" : ObjectId("4c6d1b92304326161b678b89"), "value" : "160" }
It's obvious, that a string comparison is made, so that my first value is not returned.
Is there any way to cast within the query?
Something like:
db.test.find({ (int) "value" :{$gt : 12} });
would be great. A query like
db.test.find({"value" :{$gt : 12} }); // without the quotes around "12"
returns nothing.
You can use the following JavaScript expression:
db.test.find("this.value > 12")
This uses JavaScript's automatic conversion from string to number.
I have a similar workaround, i find that if you can use the mongo shell, you can write an statement to do this in javascript, but capable of using indexes.
var myItems = []
var it = db.test.find({},{value:1})
while (it.hasNext()){
var item = it.next();
if(parseInt(item.value) > 12)
myItems.push(item);
}
If you want this to run faster than previus solution, you have to ensure the index on the value field.
Type casting in MongoDB is available after version >= 4.0. Check MongoDB's aggregation operator $convert and similar operators. Since you wanted to convert string to int you can use $toInt:
db.collection.find({ $expr: { $gt: [ { $toInt: "$value" }, 12 ] } })
Test : mongoplayground
Note :
Here we're converting value which is a string field to int on the fly & Since it got converted to int - we're comparing it to input of type int. Your output documents will still have original type for value field which is string (we're not actually changing type in response docs, if needed use aggregation & it's stage $project to see int values for field value in response docs).
Since we're using aggregation operators in .find() we need to wrap everything in $expr.
Even though this is pretty common nowadays & is easy to do, but remember we're casting string to int on every Read, rather if you can take care of this on Writes or Updates it would be easy & more efficient.
To convert String into int use this
db.test.find({'year': {$type: 2}}).forEach(
function (x) {
x.value=new NumberInt(x.value);
db.test.save(x)}
)
And after that you can directly query like :
db.test.find({"value" :{$gt : 12} });
$gt wasn't set for this use case. You would have to use regex for strings. Probably easier to just create a new field with the number as a number.
Related
I'm new to mongodb.
I have this object in my collection:
{
"_id" : ObjectId("5b549be38d9f1c00160117d3"),
"name" : "Name of object",
"a" : {
"b" : {
"c" : 100
}
}
}
What interests me is the 100 value, I want to fetch it from the object.
When I query the collection like this:
db.getCollection('myCollection').find({}, {'name':1, 'a.b.c':1})
I only get the same object with the inner objects.
Is there a way to query it so that I will get a result like this:
{"Name": "Name of object", "c":100}
By using Mongo aggregate query you can get the result. In $project stage of Mongo aggregate query you can add the conditions as per requirement.
Please try this query, might you will get the result:
db.myCollection.aggregate({
$project: {
"name": "$name",
"c": "$a.b.c",
_id: 0
}
})
As suggested by #Mayuri, You can achieve this by using .aggregate() but if you wanted to look at difference between $project in aggregation (Vs) projection in .find(), check out :
So one first thing with $project is it is much more powerful & can accept a lot more features that are helpful to transform the fields. But projection in .find() is pretty straight forward that can accept only few things : projection i.e; value to a field in projection can be one of these :
1 or true to include the field in the return documents.
0 or false to exclude the field.
Projection Operators : $, $elemMatch, $slice, $meta
Actual Issue :
In your query when you're doing 'a.b.c':1 in projection it means you're returning c field in the output, As field c is nested in b & a the output structure doesn't change & you'll be getting the c value under the same structure, but if you use aggregation { $project: { "c": "$a.b.c" } } it means you're assigning value of "$a.b.c" to a field named c. How ? : So when $ is used against a field in aggregation it will access field's value, which helps for assignment.
I am working on a nodejs/express app with Mongodb on the backend. In one of my API calls, depending on the presence of a particular querystring parameter or the other I want to issue a query to Mongodb with either a $gt or a $lt.
In some cases we want to ask for everything less than the tokenId using $lt, but in other cases we want everything greater than the tokenId using $gt. How do we do that without duplicating the queries?
Here's an example query:
collection.find({'film_id': {$in : genre}, '_id': {$lt: tokenId}}).sort({'_id': -1}).limit(25).toArray(function(error, films)
Is there a way to dynamically create the query without actually doing 2 different queries?
Build up your query object programmatically:
var query = {'film_id': {$in : genre}};
if (param) {
query._id = {$lt: tokenId};
} else {
query._id = {$gt: tokenId};
}
collection.find(query).sort({'_id': -1}).limit(25).toArray(function(error, films);
Update
Now that Node.js 4+ supports computed property names, you can create query in one step as:
var query = {
film_id: {$in: genre},
_id: {[param ? '$lt' : '$gt']: tokenId}
};
The Above code may not work. The above dynamic query will be resolved to following Ex. Token is an integer 35.
collection.find("_id":"{$gt: 35}")
.sort({'_id': -1}).limit(25).toArray(function(error, films);
resulting in Error
The correct syntax should be:
collection.find("_id":{$gt: 35}).
The double "" quotes should not be there.
How to convert double type to number type of field _id in console mongodb.
I have tried many ways but not working.
I tried:
db.videos.find({_id : {$exists : true}})
.forEach( function(obj)
{ obj._id = new NumberInt(obj._id ); db.videos.save(obj); }
)
Try to use explain() in your code. Possible that it gets you more information
I have a collection where the _id is of the form [message_code]-[language_code] and another where the _id is just [message_code]. What I'd like to do is find all documents from the first collection where the message_code portion of the _id does not appear in the second collection.
Example:
> db.colA.find({})
{ "_id" : "TRM1-EN" }
{ "_id" : "TRM1-ES" }
{ "_id" : "TRM2-EN" }
{ "_id" : "TRM2-ES" }
> db.colB.find({})
{ "_id" : "TRM1" }
I want a query that will return TRM2-EN and TRM-ES from colA. Of course in my live data, there are thousands of records in each collection.
According to this question which is trying to do something similar, we have to save the results from a query against colB and use it in an $in condition in a query against colA. In my case, I need to strip the -[language_code] portion before doing this comparison, but I can't find a way to do so.
If all else fails, I'll just create a new field in colA that contains only the message code, but is there a better way do it?
Edit:
Based on Michael's answer, I was able to come up with this solution:
var arr = db.colB.distinct("_id")
var regexs = arr.map(function(elm){
return new RegExp(elm);
})
var result = db.colA.find({_id : {$nin : regexs}}, {_id : true})
Edit:
Upon closer inspection, the above method doesn't work after all. In the end, I just had to add the new field.
Disclaimer: This is a little hack it may not end well.
Get distinct _id using collection.distinct method.
Build a regular expression array using Array.prototype.map()
var arr = db.colB.distinct('_id');
arr.map(function(elm, inx, tab) {
tab[inx] = new RegExp(elm);
});
db.colA.find({ '_id': { '$nin': arr }})
I'd add a new field to colA since you can index it and if you have hundreds of thousands of documents in each collection splitting the strings will be painfully slow.
But if you don't want to do that you could make use of the aggregation framework's $substr operator to extract the [message-code] then do a $match on the result.
I have a collection of documents that have a value that is known to be a number, but is stored as a string. It is out of my control to change the type of the field, but I want to use that field in an aggregation (say, to average it).
It seems that I should be using a projection prior to grouping, and in that projection convert the field as needed. I can't seem to get the syntax just right - everything I try either gives me NaN, or the new field is simply missing from the next step in the aggregation.
$project: {
value: '$value',
valueasnumber: ????
}
Given the very simple example above, where the contents of $value in all documents are string type, but will parse to a number, what do I do to make valueasnumber a new (non-existing) field that is of type double with the parsed version of $value in it?
I've tried things like the examples below (and about a dozen similar things):
{ $add: new Number('$value').valueOf() }
new Number('$value').valueOf()
Am I barking up the wrong tree entirely? Any help would be greatly appreciated!
(To be 100% clear, below is how I would like to use the new field).
$group {
score: {
$avg: '$valueasnumber'
}
}
One of the way which I can think of is to use a mongo shell javascript to modify the document by adding new number field, valuesasnumber (number conversion of existing string 'value' field) in the existing document or in the new doc. Then using this numeric field for further calculations.
db.numbertest.find().forEach(function(doc) {
doc.valueasnumber = new NumberInt(doc.value);
db.numbertest.save(doc);
});
Using the valueasnumber field for numeric calculation
db.numbertest.aggregate([{$group :
{_id : null,
"score" : {$avg : "$valueasnumber"}
}
}]);
The core operation is to convert value from string to number which is unable to handled in aggregate pipeline operation currently.
mapReduce is an alternative as below.
db.c.mapReduce(function() {
emit( this.groupId, {score: Number(this.value), count: 1} );
}, function(key, values) {
var score = 0, count = 0;
for (var i = 0; i < values.length; i++) {
score += values[i].score;
count += values[i].count;
}
return {score: score, count: count};
}, {finalize: function(key, value) {
return {score: value.score / value.count};
}, out: {inline: 1}});
Now there is $toInt conversion operators in aggregation, you can check:
https://jira.mongodb.org/browse/SERVER-11400