MongoDB - How to use a field value in a $slice projection? - javascript

Given a simple document such as:
{
myArray : [12, 10, 40, -1, -1, ..., -1], //'-1' is a int placeholder
latestUpdatedIndex : 2
}
I know how many values I will put in myArray so I preallocate space with the -1 values to make updates more efficient. I keep track of the latest value I updated in latestUpdatedIndex
Can I use the value of latestUpdatedIndex in a $slice projection like the one below?
coll.find({}, {'myArray':{'$slice':[0, <value of latestUpdatedIndex>]}

You can't do that using a normal query but you can do it using aggregation (in MongoDB 3.2):
db.collection.aggregate([
{ $project: { mySlicedArray: { $slice: [ "$myArray", "$latestUpdatedIndex" ] } } }
])

Related

How do I access value of an attribute with an object with no key pairs?

I am working with dynamoose and a query returns me the following output
[ Document { cost: 100 },
lastKey: undefined,
count: 1,
queriedCount: undefined,
timesQueried: 1 ]
When I try to do typeof(output) , it returns Object
When I try to do Object.keys(output) , it returns [ '0', 'lastKey', 'count', 'queriedCount', 'timesQueried' ]
When I do Object.entries(output), it returns [ [ '0', Document { cost: 100 } ],[ 'lastKey', undefined ], [ 'count', 1 ], [ 'queriedCount', undefined ], [ 'timesQueried', 1 ] ]
For my use, I need to get an Object which looks like this {cost: 100} and currently I am using
JSON.parse(JSON.stringify(output))
which gives me
[ { cost: 100 } ]
I havent encountered an object without a key value pair (I'm self taught), so I do not know how to approach this problem. And the current method seems inefficient and kinda wrong. Please advice me how I can do it in a proper way
JSON.stringify(output) //=> "[ { cost: 100 } ]"
What that means is that you have an array (denoted by []) with one item, which is an object (denoted by {}) with a single key cost.
Use [0] to get the first item in an array, and then use .cost to get the cost property.
console.log(output[0].cost) //=> 100
The reason that you are seeing other keys on that array is that it looks like that dynamoose library is adding some additional properties to the array before returning it. It's doing the equivalent of:
// vanilla js, not typescript
const output = [ { cost: 100 } ]
output.count = 1
output.timesQueried = 1
This gives the array properties in addition to the content. The dynamoose library appears to use this method to encode metadata about your queries alongside the returned data.
The default JSON serialization strategy for arrays does not serialize custom array properties, only the array members.

Match within a Range of two fields from an Array of Values

I have an array of numbers [111, 444, 777]
I want to look inside these 2 documents
{
_id: 1,
lookHere: {rangeLow: 333, rangeHigh: 555}
},
{
_id: 2,
lookHere: {rangeLow: 222, rangeHigh: 333}
}
I want to write a query that returns only the 1st document since my array of numbers includes 444 which is between 333 and 555.
Is there a query that can achieve this result in mongo/mongoose ?
You want an $or query. You can .map() the source array into the arguments for $or:
var inputs = [111, 444, 777];
collection.find({
"$or": inputs.map( n => ({
"lookHere.rangeLow": { "$lt": n },
"lookHere.rangeHigh": { "$gt": n }
}) )
})
That is basically looking to see if the Low value is less than and the High is greater than each of the current elements, and returns as true when any match both those conditions.
Note that all MongoDB query arguments are implicitly AND conditions unless stated otherwise.

MongoDB - Complex $slicing with inclusions and exclusions

Given a timeseries document like the one below
{
data:{
'2015':['a', 'b', 'c', ...], //<array of n datapoints>
'2016':['d', 'e', 'f', ...], //<array of n datapoints>
},
otherFieldA: {...}
otherFieldB: {...}
}
To get a slice of 2015 I would use the following projection as found here:
myProjection = {'data':0, 'otherFieldA':0, 'otherFieldB':0, 'data.2015':{'$slice': [3, 5]}}
db.collection.find({}, myProjection)
Now let's suppose I also want to get also all of 2016
Option A: adding 'data.2016':1 in the projection above gets a classic inclusion + exclusion mongo error
Option B: adding another $slice to the projection 'data.2016':{'$slice': <len of data.2016>} works but might be time inefficient as mongo needs to scroll down the data.2016 array rather than just scooping up the entire array. Also, I would need to know the lenght of data.2016, which is not a given
Is there a third option to get a slice of data.2015 and all of data.2016, while excluding all the otherField values?
you can do this with the aggregation framework using $project:
db.collection.aggregate([
{
$project:{
"data.2015":{
$slice:[
"$data.2015",
1,
1
]
},
"data.2016":"$data.2016"
}
}
])
output will be :
{
"_id":ObjectId("58492f23f2e6a23e2168649d"),
"data":{
"2015":[
"b"
],
"2016":[
"d",
"e",
"f"
]
}
}

pass along the field and value while aggregating

I had an output document from a pipeline something like : {upvotes : 1, downvotes : 5}. I just wanted to pass that along to the next part of the pipeline. I did something like this:
{
$group :{
_id : "$_id",
upvotes : {$max : "$upvotes"},
downvotes : {$max : "$downvotes"},
average : {$avg : "$statements.result"},
count : {$max : "$count"}
}
}
I didn't want to use max I don't want the maximum of anything. I just wanted the field and value: {upvotes : 1, downvotes : 5}. lets say they were not numbers. lets say the fields were object I want all the objects for that field to be outputted not the max.
make this question clearer: how can I get the the field and value without using max?
outputs:
[ { _id: 57aea6791016c0b023a71e9d,
upvotes: 3,
downvotes: 1,
average: 7.857142857142857,
count: 5 },
{ _id: 57aed883139c1e702beb471e,
upvotes: 0,
downvotes: 1,
average: 7,
count: 1 } ]
The output is good but I don't want to use max to get it.
For some reason I think I need an Accumulator Operator. That is why I use max.
Since you are grouping by "$_id", you're actually not grouping at all. Your results are going to correlate 1:1 to your initial data set. To accomplish what you are asking, passing along upvotes and downvotes without using an aggregate function like $max, you'd just include them in the _id for your group stage which is where you specify your group by key.
So you would do:
{
$group :{
_id : {
"_id":"$_id",
"upvotes": "$upvotes",
"downvotes": "$downvotes"
}
average : {$avg : "$statements.result"},
count : {$max : "$count"}
}
}
But still, you are going to end up with a 1:1 correlation to your original data.
Following your question thread, your pipeline in question is most probably a preceding pipeline step from an $unwind operation thence the following suggested solution is based on that assumption. Please feel free to suggest otherwise.
To include a field in the $group pipeline using the accumulators, the $first or $last seem most appropriate here since you have just flattened an array and those operators will return a value from the first/last document for each group. What is means is if the field is not part of a flattened array then $first/$last would give you its actual value before the $unwind took place.
{
"$group" :{
"_id": "$_id",
"upvotes": { "$first": "$upvotes"},
"downvotes": { "$first": "$downvotes"},
"average": { "$avg": "$statements.result"},
"count": { "$max": "$count"}
}
}

Mongoose: Sorting

what's the best way to sort the following documents in a collection:
{"topic":"11.Topic","text":"a.Text"}
{"topic":"2.Topic","text":"a.Text"}
{"topic":"1.Topic","text":"a.Text"}
I am using the following
find.(topic:req.body.topic).(sort({topic:1}))
but is not working (because the fields are strings and not numbers so I get):
{"topic":"1.Topic","text":"a.Text"},
{"topic":"11.Topic","text":"a.Text"},
{"topic":"2.Topic","text":"a.Text"}
but i'd like to get:
{"topic":"1.Topic","text":"a.Text"},
{"topic":"2.Topic","text":"a.Text"},
{"topic":"11.Topic","text":"a.Text"}
I read another post here that this will require complex sorting which mongoose doesn't have. So perhaps there is no real solution with this architecture?
Your help is greatly appreciated
i will suggest you make your topic filed as type : Number, and create another field topic_text.
Your Schema would look like:
var documentSchema = new mongoose.Schema({
topic : Number,
topic_text : String,
text : String
});
Normal document would look something like this:
{document1:[{"topic":11,"topic_text" : "Topic" ,"text":"a.Text"},
{"topic":2,"topic_text" : "Topic","text":"a.Text"},
{"topic":1,"topic_text" : "Topic","text":"a.Text"}]}
Thus, you will be able to use .sort({topic : 1}) ,and get the result you want.
while using topic value, append topic_text to it.
find(topic:req.body.topic).sort({topic:1}).exec(function(err,result)
{
var topic = result[0].topic + result[0].topic_text;//use index i to extract the value from result array.
})
If you do not want (or maybe do not even can) change the shape of your documents to include a numeric field for the topic number then you can achieve your desired sorting with the aggregation framework.
The following pipeline essentially splits the topic strings like '11.Topic' by the dot '.' and then prefixes the first part of the resulting array with a fixed number of leading zeros so that sorting by those strings will result in 'emulated' numeric sorting.
Note however that this pipeline uses $split and $strLenBytes operators which are pretty new so you may have to update your mongoDB instance - I used version 3.3.10.
db.getCollection('yourCollection').aggregate([
{
$project: {
topic: 1,
text: 1,
tmp: {
$let: {
vars: {
numStr: { $arrayElemAt: [{ $split: ["$topic", "."] }, 0] }
},
in: {
topicNumStr: "$$numStr",
topicNumStrLen: { $strLenBytes: "$$numStr" }
}
}
}
}
},
{
$project: {
topic: 1,
text: 1,
topicNumber: { $substr: [{ $concat: ["_0000", "$tmp.topicNumStr"] }, "$tmp.topicNumStrLen", 5] },
}
},
{
$sort: { topicNumber: 1 }
},
{
$project: {
topic: 1,
text: 1
}
}
])

Categories

Resources