CouchDB group view to keep string key and value - couchdb

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.

Related

Set a formated date cell for Google sheet api with nodeJS

I'm trying to update my google sheet by inserting a date. I want to have different format for different cells.
Using nodeJS api I have a class Sheet with is used in another class SpreadSheet witch contains the spreadsheet info to make batchupdates
import {sheets_v4} from "googleapis";
type DatePattern = "dd-mm-yyyy" | "hh:mm:ss" | "dd-mm-yyyy hh:mm:ss" | string
type DateType = "DATE" | "TIME" | "DATE_TIME"
export class Sheet {
private request: sheets_v4.Schema$Request[] = []
/**
*
* #param columnIndex {number} Index of the column (Starting from 0)
* #param rowIndex {number} Index of the row (Starting from 0)
* #param value {string}
*/
setPercentFormula(columnIndex: number, rowIndex: number, value: string) {
this.request.push({
updateCells: {
fields: 'userEnteredValue,userEnteredFormat',
range: {
sheetId: this.sheetId,
startColumnIndex: columnIndex,
endColumnIndex: columnIndex + 1,
startRowIndex: rowIndex,
endRowIndex: rowIndex + 1,
},
rows: [
{
values: [
{
userEnteredValue: {
formulaValue: value
},
userEnteredFormat: {
numberFormat: {
type: "PERCENT",
pattern: "#0.00%;-#0.00%;0%"
}
}
},
]
},
]
}
})
return this
}
/**
*
* #param columnIndex {number} Index of the column (Starting from 0)
* #param rowIndex {number} Index of the row (Starting from 0)
* #param value {string}
* #param type {DateType}
* #param pattern {DatePattern} Pattern format
*/
setDateWithPatternCell(columnIndex: number, rowIndex: number, value: string, type: DateType, pattern: DatePattern) {
this.request.push({
updateCells: {
fields: 'userEnteredValue,userEnteredFormat',
range: {
sheetId: this.sheetId,
startColumnIndex: columnIndex,
endColumnIndex: columnIndex + 1,
startRowIndex: rowIndex,
endRowIndex: rowIndex + 1,
},
rows: [
{
values: [
{
userEnteredValue: {
stringValue: value,
},
userEnteredFormat: {
numberFormat: {
type: type,
pattern: pattern
}
}
},
]
},
]
}
})
return this
}
}
I have other utility function, for boolean value or formula for example. All of they put the actions in the request array, then this array is use in batchUpdate call.
For example, the use of setPercentFormula work perfectly well.
The result in my sheet is a cell with a starting coma and then the date as string given in the value parameter of my function.
It looks like my formatting isn't accounted.
As it is a number format, I also tried to use userEnteredValue: { numberValue: value } and give it a timestamp as input but resulting in setting the number in the cell and no date.
I also tried to make to separate call by putting two value in my request array. One setting the string and another one setting the formatting. with the same result.
I would like as a result to have my sheet with my date value as if I manually got in the file and clicked Format > Number > Date for example. Without the starting coma and a value recognized as a date.
Here are some input I tried :
Sheet.setDateWithPatternCell(0, 0, '2022-08-10', 'DATE', 'yyyy-mm-dd')
Sheet.setDateWithPatternCell(0, 0, Date.now(), 'DATE', 'yyyy-mm-dd')
Sheet.setDateWithPatternCell(0, 0, Date.now(), 'DATE_TIME', 'yyyy-mm-dd hh:mm:ss')
I expect the output in my google sheet to be the date with the format is specified.
Not the string (as I see in my sheet it's starting with a ' in the value)
I believe your goal is as follows.
You want to put a date object to a cell with your expected format using Sheets API.
Unfortunately, from your question, I couldn't find your tested values of columnIndex, rowIndex, value, type, pattern. So, in this answer, I would like to propose a sample request body for achieving your goal.
When "updateCells" request with the method of spreadsheets.batchUpdate is used, and a sample request body is as follows.
Sample request body:
{
"requests": [
{
"updateCells": {
"fields": "userEnteredValue,userEnteredFormat",
"range": {
"sheetId": 0,
"startColumnIndex": 0,
"endColumnIndex": 1,
"startRowIndex": 0,
"endRowIndex": 1
},
"rows": [
{
"values": [
{
"userEnteredValue": {
"numberValue": 44803
},
"userEnteredFormat": {
"numberFormat": {
"type": "DATE_TIME",
"pattern": "dd-mm-yyyy hh:mm:ss"
}
}
}
]
}
]
}
}
]
}
44803 is the serial number. Please be careful about this.
When this request body is used with the method of spreadsheets.batchUpdate, 30-08-2022 00:00:00 is put into the cell "A1" of sheet ID of 0 as the date object.
For example, when the pattern, type and value are dd-mm-yyyy, DATE, and 44803, respectively, 30-08-2022 is put to the cell.
As another approach, when the method of spreadsheets.values.update is used, you can also the following script. In this case, 30-08-2022 00:00:00 is put to the cell "A1" of "Sheet1" as the date object.
sheets.spreadsheets.values.update({
spreadsheetId: "###", // Please set your Spreadsheet ID.
range: "Sheet1!A1",
valueInputOption: "USER_ENTERED",
resource: { values: [["30-08-2022 00:00:00"]] },
});
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.values.update

Groovy to round all array decimal places down and return unique number count?

Thank you aspok for your help!
My goal is to get my list to be [3, 3, 4] and then get a count of unique values within it. Can anyone point me in the right direction for doing this?
My script consumes a JSON and puts all F4211_LNID values into a list. [3.1, 3.9, 4]. I need to now round all decimal places down.
I'm not sure if it's doable, but I am trying to use Math.floor(intListItems) to round my array values down. When I try this I receive the following error: Exception No signature of method: static java.lang.Math.floor() is applicable for argument types: (ArrayList) values: [[3.1, 3.9, 4]] Possible solutions: floor(double), log(double), find(), macro(groovy.lang.Closure), acos(double), cos(double)
I see my simplified list in the error, but I can't get it to round down and not sure what the error means.
(UPDATED) My Working Groovy
// Read Input Values
String aInputJson = aInputMap.InputJson ?: "{}"
// Initialize Output Values
def intListItems = []
def uniqueCount = 0
// Parse JSON
def json = new JsonSlurper().parseText( aInputJson )
// Determine Row Numbers
def rowset = json?.fs_DATABROWSE_F4211?.data?.gridData?.rowset
intListItems = rowset.collect{ Math.floor(it.F4211_LNID) }
intListItems.unique()
uniqueCount = intListItems.size()
JSON I am using.
{
"fs_DATABROWSE_F4211": {
"title": "Data Browser - F4211 [Sales Order Detail File]",
"data": {
"gridData": {
"id": 58,
"fullGridId": "58",
"rowset": [
{
"F4211_LNTY": "S",
"F4211_CPNT": 0,
"F4211_MCU": " 114000",
"F4211_DSC2": "NAS133N3EK166",
"F4211_NXTR": "580",
"F4211_LNID": 3.1,
"F4211_DOCO": 2845436
},
{
"F4211_LNTY": "S",
"F4211_CPNT": 0,
"F4211_MCU": " 114000",
"F4211_DSC2": "NAS133N3EK166",
"F4211_NXTR": "580",
"F4211_LNID": 3.9,
"F4211_DOCO": 2845436
},
{
"F4211_LNTY": "S",
"F4211_CPNT": 0,
"F4211_MCU": " 114000",
"F4211_DSC2": "NAS133N3EK166",
"F4211_NXTR": "580",
"F4211_LNID": 4,
"F4211_DOCO": 2845436
}
],
"summary": {
"records": 1,
"moreRecords": false
}
}
},
"errors": [],
"warnings": []
},
"currentApp": "DATABROWSE_F4211",
"timeStamp": "2000-06-01:09.42.02",
"sysErrors": []
}
You are getting the error Exception No signature of method: static java.lang.Math.floor() is applicable for argument types: (ArrayList) because there is no version of Math.floor() that accepts a List as a parameter.
Instead, you need to call Math.floor() on each individual item in the list. The easiest way to do this is in the collect { } call you are already doing.
def flooredList = rowset.collect { Math.floor(it.F4211_LNID) }
assert flooredList == [3.0, 3.0, 4.0]

Simpler way to get duplicates from a list in groovy

Given the list:
list: [
object1: {
id: 22,
name: "Tom"
},
object2: {
id: 12,
name: "Mary"
},
object3: {
id: 44,
name: "Tom"
}
]
Instead of using nested loops, is there any other one-liner that would get ONLY duplicated names from this list ? So the return list would be ["Tom"]
Another way would be to group them with a count, and find all the ones where the count is greater than one:
list.name.countBy { it }.findResults { it.value > 1 ? it.key : null }
Or indeed as #Daniel says:
list.values().name.countBy { it }.findResults { it.value > 1 ? it.key : null }
Depending on your structure...
Close to a oneliner is the below code. The idea is to create a list of unique named elements, and remove them from the original list.
def list = [
[ id: 22, name: "Tom"],
[ id: 12, name: "Mary"],
[ id: 44, name: "Tom"]
]
def unique = list.toUnique { a, b -> a.name <=> b.name }
list.removeAll(unique)
list*.name
removeAll returns booleon, so that complicates it if you need 1 line of code, the unique variable can be foldet into the method call.
Notice, .toUnique() returns a new collection .unique() removes from the collection called on.

Use AQL Variables e.g. for counting (LET sum = sum + 1)

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
}
]

node_redis get zrange withscores

Does anybody know how can I get members with scores by node redis?
I tried something like this:
client.ZRANGE(key, 0, -1, withscores, function(err, replies) {
});
Thanks.
This code looks good. Check out the following link for retrieving what you want :
http://ricochen.wordpress.com/2012/02/28/example-sorted-set-functions-with-node-js-redis/
Added the code here from that link example in case it is ever removed.
var rc=require('redis').createClient();
var _=require('underscore');
rc.zincrby('myset', 1, 'usera');
rc.zincrby('myset', 5, 'userb');
rc.zincrby('myset', 3, 'userc');
rc.zrevrange('myset', 0, -1, 'withscores', function(err, members) {
// the resulting members would be something like
// ['userb', '5', 'userc', '3', 'usera', '1']
// use the following trick to convert to
// [ [ 'userb', '5' ], [ 'userc', '3' ], [ 'usera', '1' ] ]
// learned the trick from
// http://stackoverflow.com/questions/8566667/split-javascript-array-in-chunks-using-underscore-js
var lists=_.groupBy(members, function(a,b) {
return Math.floor(b/2);
});
console.log( _.toArray(lists) );
});
rc.quit();
Seems your code is right. The following is the syntax to get zrange.
without score:
redisClient.zrange(keyName,start,stop,function(err,result){
//result is array
// every index will give you member name
})
Ex :
redisClient.zrange("mySortedset",-1,-1,function(err,result){
//result is array
// every index will give you member name
})
with score:
redisClient.zrange(keyName,start,stop,'withscores',function(err,result){
//result is array
// here even index will hold member
// odd index will hold its score
})
Ex :
redisClient.zrange("mySortedset",-1,-1,'withscores',function(err,result){
//result is array
// here even index will hold member
// odd index will hold its score
})
I tried with the prior accepted answers but i could not get the result i want and later i tried with the following code and got appropriate result,
Original output:
[ 'player:522',
'19685',
'player:164',
'19594',
'player:807',
'19171',
'player:694',
'19165',
'player:905',
'19108',
'player:859',
'19087',
'player:432',
'18973',
'player:515',
'18831',
'player:163',
'18750',
'player:4',
'18552' ]
Expected output:
{
"player:522": "19685",
"player:164": "19594",
"player:807": "19171",
"player:694": "19165",
"player:905": "19108",
"player:859": "19087",
"player:432": "18973",
"player:515": "18831",
"player:163": "18750",
"player:4": "18552"
}
Solution:
redisClient.ZREVRANGE('daily', 1, 10, 'WITHSCORES', function(err, result) {
result = _.fromPairs(_.chunk(result, 2));
return res.status(200).json(result);
});
The right approach for versions ^2.0,
var args = [ key,to, from ];
redisClient.zrevrangebyscore(args,function(err,data){
Vanilla JS Solution
Redis call:
redisClient.zrange(keyName, start, stop, 'withscores', function(err, result) {
// result.reduce ... (See below)
}
Here is a Vanilla-JS solution that I came up with pretty quickly.
For me, personally, it does not make sense to import underscore or any other library to perform such an easy task:
result.reduce(function (a, c, i) {
var idx = i / 2 | 0;
if (i % 2) {
a[idx].score = c;
} else {
a[idx] = { id: c };
}
return a;
}, []);
Assuming this input:
['player1', 13, 'player2', 11, 'player4', 7, 'player3', 3, 'player5', 0]
This function yields:
[
{ id: 'player1', score: 13 },
{ id: 'player2', score: 11 },
{ id: 'player4', score: 7 },
{ id: 'player3', score: 3 },
{ id: 'player5', score: 0 }
]
Here is another one to transform the result into a two-dimensional array:
result.reduce(function (a, c, i) {
var idx = i / 2 | 0;
if (i % 2) {
a[idx].push(c);
} else {
a[idx] = [c];
}
return a;
}, []);
which produces the following array:
[
[ 'player1', 13 ],
[ 'player2', 11 ],
[ 'player4', 7 ],
[ 'player3', 3 ],
[ 'player5', 0 ]
]
One line lambda version:
result.reduce((a, c, i) => i % 2 ? (a[i / 2 | 0].data = c, a) : (a[i / 2 | 0] = { id: c }, a), []);
Vanilla JS reduce works well here.
const result = [
'player:522',
'19685',
'player:164',
'19594',
'player:807',
'19171',
'player:694',
'19165',
'player:905',
'19108',
'player:859',
'19087',
'player:432',
'18973',
'player:515',
'18831',
'player:163',
'18750',
'player:4',
'18552'
]
const map = result.reduce((map, k, i, res) => {
if (i % 2 !== 0) {
map[res[i - 1]] = Number(k);
}
return map;
}, {})
map is now:
{
'player:522': 19685,
'player:164': 19594,
'player:807': 19171,
'player:694': 19165,
'player:905': 19108,
'player:859': 19087,
'player:432': 18973,
'player:515': 18831,
'player:163': 18750.65468,
'player:4': 18552
}
var data = []
results.map((result, index) => {
if (index % 2 == 0) {
data.push(results[index] = { player: results[index], score: results[index+1] })
}
})
console.log(data)

Resources