According to https://www.arangodb.com/2014/07/13/arangodb-2-2-0-released it shall be possible to use statements like this:
LET sum = 0
FOR v IN values
SORT v.year
LET sum = sum + v.value
RETURN { year: v.year, value: v.value, sum: sum }
I currently use version 2.4 but am not able to use it, e.g. in such a statement:
LET sum = 0
FOR i in memoryColl
LET sum = sum + 1
// sum = sum + 1
RETURN { "i": i, "sum": sum }
I got the error
[1511] variable 'sum' is assigned multiple times (while parsing)
Can somebody tell me if such a statemtn should in principle work, and how exactly?
As explained in the upgrading docs for 2.3, it's no longer possible to update variables in queries:
Previous versions of ArangoDB allowed the modification of variables inside AQL
queries [...]
While this is admittedly a convenient feature, the new query optimizer design did not
allow to keep it.
Additionally, updating variables inside a query would prevent a lot
of optimizations to queries that we would like the optimizer to make. Additionally,
updating variables in queries that run on different nodes in a cluster would like cause
non-deterministic behavior because queries are not executed linearly.
To enumerate documents, you could do
LET range = 0..LENGTH(memoryColl)-1
FOR i IN range
RETURN {i: i+1, doc: memoryColl[i]}
but it looks like a really bad idea to me. Better return the documents and let the client enumerate them.
If you actually want to count the number of documents, you may use a sub-query:
LET result = (
FOR doc IN memoryColl
FILTER True // add some condition here for instance
RETURN doc
)
RETURN LENGTH(result)
In 2.4, it is also possible to count more efficiently:
http://jsteemann.github.io/blog/2014/12/12/aql-improvements-for-24/
On arango 3.7 in 2020 you could do something like described here
LET values = [
{ year: 2019, value: 35 },
{ year: 2017, value: 8 },
{ year: 2018, value: 17 },
{ year: 2020, value: 84 }
]
LET sortedValues = (FOR v IN values SORT v.year RETURN v)
FOR i IN 0..LENGTH(sortedValues)-1
LET v = sortedValues[i]
LET sum = sortedValues[i].value + SUM(SLICE(sortedValues, 0, i)[*].value)
RETURN {year:v.year,value:v.value,sum:sum}
This returned
[
{
"year": 2017,
"value": 8,
"sum": 8
},
{
"year": 2018,
"value": 17,
"sum": 25
},
{
"year": 2019,
"value": 35,
"sum": 60
},
{
"year": 2020,
"value": 84,
"sum": 144
}
]
Related
Take:
let array = [ ['whatever', 3], ['1st', 1], ['2nd', 1] ]
Now if I sort it using:
array.sort((a,b) => a[1] - b[1])
Will I always end up with:
[ [ '1st', 1 ], [ '2nd', 1 ], [ 'whatever', 3 ] ]
Meaning, is 1st encounter of value of 1 always guaranteed to end up before 2nd encounter which would itself end up before 3rd encounter etc?
What you're after is a 'stable' sort, and no, Array.prototype.sort does not support this. Good news though, it's just around the corner in ECMAScript 2020. See the introduction here. Namely:
Other updates include requiring that Array.prototype.sort be a stable sort
As for how long it will take to implement in node.js after specification is released, I cannot find any documentation to that effect.
Edit: Even more good news.
Thank you #d9ngle for mentioning https://v8.dev/blog/array-sort. This shows that the v8 engine, as of version 7, supports stable sorting. Node.js runs on v8. So as long as your version of node is up to date enough, Array.prototype.sort will be stable.
To find out which v8 version you are running, see here.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behavior, thus, not all browsers respect this.
The answer is: it depends on where you are running the code.
See Array sort description
Yes, Array sort() method always keep the first encountered value before the second encountered value.
you can run this snippet on all browsers
https://jsbin.com/pesuwed/edit?js,console
var items = [
{ name: 'Edward', value: 21 },
{ name: 'Sharpe', value: 37 },
{ name: 'And', value: 45 },
{ name: 'The', value: -12 },
{ name: 'Magnetic', value: 13 },
{ name: 'Zeros', value: 37 },
{ name: 'Sharp', value: 37 },
];
var items1 = [
['Edward', 21],
['Sharpe', 37],
['And', 45],
['The', -12],
['Magnetic', 13],
['Zeros', 37],
['Sharp', 37],
];
// sort by value
var sortedArray = items.sort(function (a, b) {
return a.value - b.value;
});
var sortedArray1 = items1.sort(function (a, b) {
return a[1] - b[1];
});
console.log(sortedArray);
console.log(sortedArray1);
i have a mongoose document that contains two ages. The minimum age to go to the attraction and the maximum one.
I need to query my database to get all attractions between 0 and 2 years old.
But this won't work :
"ageMin": {
"$lte": 2
},
"ageMax": {
"$gte": 0
},
Because most of my attraction are between 0 and 99.
To help you visualise i made an example:
attractionOne: {
name: "Deadly Looping",
ageMin: 12,
ageMax: 99
}
attractionTwo: {
name: "Easy Ladybug",
ageMin: 0,
ageMax: 12
}
Thank you.
If you need to match if those ranges are overlapping with 0-2 range then you're close, just need to check if 0-2 range is between min and max age. Try:
db.attractions.find({ ageMin: { $lte: 0 }, ageMax: { $gte: 2 } })
I have a program that runs every 5 minutes and checks the last time a users data was updated. If it's been greater than 4 hours an update routine is called but as the service grows, I've seen some spikes in the number of calls at given times. I want to start spreading out the update times. Since I know each time the program updated each users data last, I was wondering if there was an elegant way to find the largest gap between times and set the new users update time to that?
Here's an example. Given the following data:
{
"_id": "1",
"updatedAt": "2018-01-17T01:12:33.807Z"
},{
"_id": "2",
"updatedAt": "2018-01-17T03:17:33.807Z"
},{
"_id": "3",
"updatedAt": "2018-01-17T02:22:33.807Z"
},{
"_id": "4",
"updatedAt": "2018-01-17T02:37:33.807Z"
}
The largest time between the given updates is 1 hour and 10 minutes between id: 1 and id: 3. I want a function that can find that largest gap of time and returns the a suggested update time for the next item added to the database of '2018-01-17T01:47:33.807Z'. Which was calculated by taking the 1 hour and 10 minutes and dividing it by 2 and then adding it to id: 1's date.
I would also like to spread out all the existing users update time but I suppose that would be a different function.
You can't use aggregation framework for a difference style comparison. However you can use map reduce to get the largest time diff between documents.
Something like
db.col.mapReduce(
function () {
if (typeof this.updatedAt != "undefined") {
var date = new Date(this.updatedAt);
emit(null, date);
}
},
function(key, dates) {
result = {"prev":dates[0].getTime(), "last":dates[0].getTime(), "diff":0}
for (var ix = 1; ix < dates.length; ix++) {
value = dates[ix].getTime();
curdiff = value - result.prev;
olddiff = result.diff;
if(olddiff < curdiff)
result = {"prev":value, "diff":curdiff, "last":result.prev};
}
return result;
},
{
"sort":{"updatedAt":1},
"out": { "inline": 1 },
"finalize":function(key, result) {
return new Date(result.last + result.diff/2);
}
}
)
Aggregation query:
db.col.aggregate([
{"$match":{"updatedAt":{"$exists":true}}},
{"$sort":{"updatedAt":1}},
{"$group":{
"_id":null,
"dates":{"$push":"$updatedAt"}
}},
{"$project":{
"_id":0,
"next":{
"$let":{
"vars":{
"result":{
"$reduce":{
"input":{"$slice":["$dates",1,{"$subtract":[{"$size":"$dates"},1]}]},
"initialValue":{"prev":{"$arrayElemAt":["$dates",0]},"last":{"$arrayElemAt":["$dates",0]},"diff":0},
"in":{
"$cond":[
{"$lt":["$$value.diff",{"$subtract":["$$this","$$value.prev"]}]},
{"prev":"$$this","last":"$$value.prev","diff":{"$subtract":["$$this","$$value.prev"]}},
"$$value"
]
}
}
}
},
"in":{
"$add":["$$result.last",{"$divide":["$$result.diff",2]}]
}
}
}
}}
])
I have a view with documents in the form of {key:[year,month,day,string],value:int}:
{
rows:[
{
key: [
2016,
4,
30,
"String1"
],
value: 20
},
{
key: [
2016,
4,
30,
"String2"
],
value: 7
},
{
key: [
2016,
4,
30,
"String3"
],
value: 13
},{
key: [
2016,
5,
1,
"String1"
],
value: 10
},
{
key: [
2016,
5,
1,
"String4"
],
value: 12
},{
key: [
2016,
5,
2,
"String1"
],
value: 3
},
]}
From this I use startkey and endkey to get a range of values by date. My issue is then grouping the documents I get returned by the key string, and summing the value int. The rest of the key may or may not be present it does not matter. So far with group levels I have only been able to sum values per date key.
When rendered in a table I get something like:
What I want is:
So I ended up reducing in my controller with javascript like:
$scope.reduceMap = function (rows) {
var reducedMap = {};
var sortableArray = [];
for (var i = 0; i < rows.length; i++) {
var key = rows[i].key[3];
if (!reducedMap.hasOwnProperty(key)) {
reducedMap[key] = {key: key, value: rows[i].value};
} else {
reducedMap[key] = {key: key, value: rows[i].value + reducedMap[key].value};
}
}
for (var k in reducedMap) {
sortableArray.push(reducedMap[k]);
}
return sortableArray;
};
Since I asked for a CouchDB answer, I will leave this here but not accept it.
If you emit view's key as: string, year, month, day and use a built in reduce function _sum, then the following URL example gives you the desired result:
http://localhost:5984/text/_design/search/_view/by_text?startkey=["",2016,1,1]&endkey=[{},2016,1,1]&group_level=1
Your date search criteria is specified as normal, but the first part of the key is basically any string. Then grouping level 1 and reducing using sum gives you the count of string occurrences withing date range grouped by string.
Is there a direct way to project a new field if a value matches one in a huge sub array. I know i can use the $elemMatch or $ in the $match condition, but that would not allow me to get the rest of the non matching values (users in my case).
Basically i want to list all type 1 items and show all the users while highlighting the subscribed user. The reason i want to do this through mongodb is to avoid iterating over multiple thousand users for every item. Infact that is the part 2 of my question, can i limit the number of user's array that would be returned, i just need around 10 array values to be returned not thousands.
The collection structure is
{
name: "Coke",
type: 2,
users:[{user: 13, type:1},{ user:2: type:2}]
},
{
name: "Adidas",
type: 1,
users:[{user:31, type:3},{user: 51, type:1}]
},
{
name: "Nike",
type: 1,
users:[{user:21, type:3},{user: 31, type:1}]
}
Total documents are 200,000+ and growing...
Every document has 10,000~50,000 users..
expected return
{
isUser: true,
name: "Adidas",
type: 1,
users:[{user:31, type:3},{user: 51, type:1}]
},
{
isUser: false,
name: "Nike",
type: 1,
users:[{user:21, type:3},{user: 31, type:1}]
}
and i've been trying this
.aggregate([
{$match:{type:1}},
{$project:
{
isUser:{$elemMatch:["users.user",51]},
users: 1,
type:1,
name: 1
}
}
])
this fails, i get an error "Maximum Stack size exceeded". Ive tried alot of combinations and none seem to work. I really want to avoid running multiple calls to mongodb. Can this be done in a single call?
I've been told to use unwind, but i am bit worried that it might lead to memory issues.
If i was using mysql, a simple subquery would have done the job... i hope i am overlooking a similar simple solution in mongodb.
Process the conditions for the array elements and match the result by using a combination of the $anyElementTrue which evaluates an array as a set and returns true if any of the elements are true and false otherwise, the $ifNull operator will act as a safety net that evaluates the following $map expression and returns the value of the expression if the expression evaluates to a non-null value. The $map in the $ifNull operator is meant to apply the conditional statement expression to each item in the users array and returns an array with the applied results. The resulting array will then be used evaluated by the $anyElementTrue and this will ultimately calculate and return the isUser field for each document:
db.collection.aggregate([
{ "$match": { "type": 1} },
{
"$project": {
"name": 1, "type": 1,
"isUser": {
"$anyElementTrue": [
{
'$ifNull': [
{
"$map": {
"input": "$users",
"as": "el",
"in": { "$eq": [ "$$el.user",51] }
}
},
[false]
]
}
]
}
}
}
])