How to get sum of values in grid? - ruby-on-rails

For example I have grid.
//grid for answers_for_online
var answersGridForOnline5 = new Ext.grid.GridPanel({
id : 'grid_for_stats',
store : storez3,
columns : answers_columns5,
});
my column:
var answers_columns5 = [{
id: "idz",
header: 'idz',
dataIndex: "idz",
renderer: fun_f
}];
and renderer function
function fun(n, j, k, m, h, i) {
var count = store.snapshot ? store.snapshot.length : store.getCount()
var cez = k.get("scale")
var ce = ( 2 / count ) * 100
return ce + " % "
}
Question: In database I have for example: scales (that user answered on scale-question)
id | scale
1 | 4
2 | 4
3 | 1
4 | 2
How i can sum scales (and group them of course) and put this in my grid?
For example in my grid i should get:
scale | scale %
1 | 25%
2 | 25%
4 | 50%

I advise you don't attempt to do it inside Grid/Store. Instead process the data before loading it to store - for example do it in database with GROUP BY statement.

To get the sum of values in a store, you can use Store.sum()

Related

How can I create a multiple dependent dropdown in Google Sheets? [duplicate]

How do you get a sub-category column to populate a drop down based on the value selected in the main category drop down in google sheets?
I googled around and couldn't find any good solutions, therefore I wanted to share my own. Please see my answer below.
You can start with a google sheet set up with a main page and drop down source page like shown below.
You can set up the first column drop down through the normal Data > Validations menu prompts.
Main Page
Drop Down Source Page
After that, you need to set up a script with the name onEdit. (If you don't use that name, the getActiveRange() will do nothing but return cell A1)
And use the code provided here:
function onEdit() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.getActiveSheet();
var myRange = SpreadsheetApp.getActiveRange();
var dvSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Categories");
var option = new Array();
var startCol = 0;
if(sheet.getName() == "Front Page" && myRange.getColumn() == 1 && myRange.getRow() > 1){
if(myRange.getValue() == "Category 1"){
startCol = 1;
} else if(myRange.getValue() == "Category 2"){
startCol = 2;
} else if(myRange.getValue() == "Category 3"){
startCol = 3;
} else if(myRange.getValue() == "Category 4"){
startCol = 4;
} else {
startCol = 10
}
if(startCol > 0 && startCol < 10){
option = dvSheet.getSheetValues(3,startCol,10,1);
var dv = SpreadsheetApp.newDataValidation();
dv.setAllowInvalid(false);
//dv.setHelpText("Some help text here");
dv.requireValueInList(option, true);
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).setDataValidation(dv.build());
}
if(startCol == 10){
sheet.getRange(myRange.getRow(),myRange.getColumn() + 1).clearDataValidations();
}
}
}
After that, set up a trigger in the script editor screen by going to Edit > Current Project Triggers. This will bring up a window to have you select various drop downs to eventually end up at this:
You should be good to go after that!
Caution! The scripts have a limit: it handles up to 500 values in a single drop-down list.
Multi-line, multi-Level, multi-List, multi-Edit-Line Dependent Drop-Down Lists in Google Sheets. Script
More Info
Article
Video
Last version of the script on GitHub
This solution is not perfect, but it gives some benefits:
Let you make multiple dropdown lists
Gives more control
Source Data is placed on the only sheet, so it's simple to edit
First of all, here's working example, so you can test it before going further.
Installation:
Prepare Data
Make the first list as usual: Data > Validation
Add Script, set some variables
Done!
Prepare Data
Data looks like a single table with all possible variants inside it. It must be located on a separate sheet, so it can be used by the script. Look at this example:
Here we have four levels, each value repeats. Note that 2 columns on the right of data are reserved, so don't type/paste there any data.
First simple Data Validation (DV)
Prepare a list of unique values. In our example, it is a list of Planets. Find free space on sheet with data, and paste formula: =unique(A:A)
On your mainsheet select first column, where DV will start. Go to Data > Validation and select range with a unique list.
Script
Paste this code into script editor:
function onEdit(event)
{
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data1'; // name of sheet with data
var NumOfLevels = 4; // number of levels of data validation
var lcol = 2; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,2]; // offsets for levels
// ^ means offset column #4 on one position right.
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets);
// Change Settings:
//--------------------------------------------------------------------------------------
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data2'; // name of sheet with data
var NumOfLevels = 7; // number of levels of data validation
var lcol = 9; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,1,1,1,1]; // offsets for levels
// =====================================================================================
SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets);
}
function SmartDataValidation(event, TargetSheet, LogSheet, NumOfLevels, lcol, lrow, offsets)
{
//--------------------------------------------------------------------------------------
// The event handler, adds data validation for the input parameters
//--------------------------------------------------------------------------------------
var FormulaSplitter = ';'; // depends on regional setting, ';' or ',' works for US
//--------------------------------------------------------------------------------------
// =================================== key variables =================================
//
// ss sheet we change (TargetSheet)
// br range to change
// scol number of column to edit
// srow number of row to edit
// CurrentLevel level of drop-down, which we change
// HeadLevel main level
// r current cell, which was changed by user
// X number of levels could be checked on the right
//
// ls Data sheet (LogSheet)
//
// ======================================================================================
// Checks
var ts = event.source.getActiveSheet();
var sname = ts.getName();
if (sname !== TargetSheet) { return -1; } // not main sheet
// Test if range fits
var br = event.range;
var scol = br.getColumn(); // the column number in which the change is made
var srow = br.getRow() // line number in which the change is made
var ColNum = br.getWidth();
if ((scol + ColNum - 1) < lcol) { return -2; } // columns...
if (srow < lrow) { return -3; } // rows
// Test range is in levels
var columnsLevels = getColumnsOffset_(offsets, lcol); // Columns for all levels
var CurrentLevel = getCurrentLevel_(ColNum, br, scol, columnsLevels);
if(CurrentLevel === 1) { return -4; } // out of data validations
if(CurrentLevel > NumOfLevels) { return -5; } // last level
/*
ts - sheet with validation, sname = name of sheet
NumOfLevels = 4
offsets = [1,1,1,2] - last offset is 2 because need to skip 1 column
columnsLevels = [4,5,6,8] - Columns of validation
Columns 7 is skipped
|
1 2 3 4 5 6 7 8 9
|----+----+----+----+----+----+----+----+----+
1 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
2 | | | | v | V | ? | x | ? | | lrow = 2 - number of row where validation starts
|----+----+----+----+----+----+----+----+----+
3 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
4 | | | | | | | x | | |
|----+----+----+----+----+----+----+----+----+
| | | | |
| | | | Currentlevel = 3 - the number of level to change
| | | |
| | | br - cell, user changes: scol - column, srow - row,
| | ColNum = 1 - width
|__|________ _.....____|
| v
| Drop-down lists
|
| lcol = 4 - number of column where validation starts
*/
// Constants
var ReplaceCommas = getDecimalMarkIsCommaLocals(); // // ReplaceCommas = true if locale uses commas to separate decimals
var ls = SpreadsheetApp.getActive().getSheetByName(LogSheet); // Data sheet
var RowNum = br.getHeight();
/* Adjust the range 'br'
??? !
xxx x
xxx x
xxx => x
xxx x
xxx x
*/
br = ts.getRange(br.getRow(), columnsLevels[CurrentLevel - 2], RowNum);
// Levels
var HeadLevel = CurrentLevel - 1; // main level
var X = NumOfLevels - CurrentLevel + 1; // number of levels left
// determine columns on the sheet "Data"
var KudaCol = NumOfLevels + 2;
var KudaNado = ls.getRange(1, KudaCol); // 1 place for a formula
var lastRow = ls.getLastRow();
var ChtoNado = ls.getRange(1, KudaCol, lastRow, KudaCol); // the range with list, returned by a formula
// ============================================================================= > loop >
var CurrLevelBase = CurrentLevel; // remember the first current level
for (var j = 1; j <= RowNum; j++) // [01] loop rows start
{
// refresh first val
var currentRow = br.getCell(j, 1).getRow();
loopColumns_(HeadLevel, X, currentRow, NumOfLevels, CurrLevelBase, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts);
} // [01] loop rows end
}
function getColumnsOffset_(offsets, lefColumn)
{
// Columns for all levels
var columnsLevels = [];
var totalOffset = 0;
for (var i = 0, l = offsets.length; i < l; i++)
{
totalOffset += offsets[i];
columnsLevels.push(totalOffset + lefColumn - 1);
}
return columnsLevels;
}
function test_getCurrentLevel()
{
var br = SpreadsheetApp.getActive().getActiveSheet().getRange('A5:C5');
var scol = 1;
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(1, br, scol, [1,2,3])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxx|
dv range |xxxxx| |xxxxx| |xxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(2, br, scol, [1,3,5])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxx| |xxxxxxxxxxx|
levels 1 2 3
level 2
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,5,6])); // 2
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxx| |xxxxx|
levels 1 2 3
level 3
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,2,8])); // 3
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels 1 2 3
level 4 (error)
*/
Logger.log(getCurrentLevel_(3, br, scol, [1,2,3]));
/*
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
range |xxxxxxxxxxxxxxxxx|
dv range |xxxxxxxxxxxxxxxxx|
levels
level 1 (error)
*/
Logger.log(getCurrentLevel_(3, br, scol, [5,6,7])); // 1
}
function getCurrentLevel_(ColNum, br, scol, columnsLevels)
{
var colPlus = 2; // const
if (ColNum === 1) { return columnsLevels.indexOf(scol) + colPlus; }
var CurrentLevel = -1;
var level = 0;
var column = 0;
for (var i = 0; i < ColNum; i++ )
{
column = br.offset(0, i).getColumn();
level = columnsLevels.indexOf(column) + colPlus;
if (level > CurrentLevel) { CurrentLevel = level; }
}
return CurrentLevel;
}
function loopColumns_(HeadLevel, X, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts)
{
for (var k = 1; k <= X; k++)
{
HeadLevel = HeadLevel + k - 1;
CurrentLevel = CurrLevelBase + k - 1;
var r = ts.getRange(currentRow, columnsLevels[CurrentLevel - 2]);
var SearchText = r.getValue(); // searched text
X = loopColumn_(X, SearchText, HeadLevel, HeadLevel, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts);
}
}
function loopColumn_(X, SearchText, HeadLevel, HeadLevel, currentRow, NumOfLevels, CurrentLevel, lastRow, FormulaSplitter, CurrLevelBase, columnsLevels, br, KudaNado, ChtoNado, ReplaceCommas, ts)
{
// if nothing is chosen!
if (SearchText === '') // condition value =''
{
// kill extra data validation if there were
// columns on the right
if (CurrentLevel <= NumOfLevels)
{
for (var f = 0; f < X; f++)
{
var cell = ts.getRange(currentRow, columnsLevels[CurrentLevel + f - 1]);
// clean & get rid of validation
cell.clear({contentsOnly: true});
cell.clear({validationsOnly: true});
// exit columns loop
}
}
return 0; // end loop this row
}
// formula for values
var formula = getDVListFormula_(CurrentLevel, currentRow, columnsLevels, lastRow, ReplaceCommas, FormulaSplitter, ts);
KudaNado.setFormula(formula);
// get response
var Response = getResponse_(ChtoNado, lastRow, ReplaceCommas);
var Variants = Response.length;
// build data validation rule
if (Variants === 0.0) // empty is found
{
return;
}
if(Variants >= 1.0) // if some variants were found
{
var cell = ts.getRange(currentRow, columnsLevels[CurrentLevel - 1]);
var rule = SpreadsheetApp
.newDataValidation()
.requireValueInList(Response, true)
.setAllowInvalid(false)
.build();
// set validation rule
cell.setDataValidation(rule);
}
if (Variants === 1.0) // // set the only value
{
cell.setValue(Response[0]);
SearchText = null;
Response = null;
return X; // continue doing DV
} // the only value
return 0; // end DV in this row
}
function getDVListFormula_(CurrentLevel, currentRow, columnsLevels, lastRow, ReplaceCommas, FormulaSplitter, ts)
{
var checkVals = [];
var Offs = CurrentLevel - 2;
var values = [];
// get values and display values for a formula
for (var s = 0; s <= Offs; s++)
{
var checkR = ts.getRange(currentRow, columnsLevels[s]);
values.push(checkR.getValue());
}
var LookCol = colName(CurrentLevel-1); // gets column name "A,B,C..."
var formula = '=unique(filter(' + LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84
var mathOpPlusVal = '';
var value = '';
// loop levels for multiple conditions
for (var i = 0; i < CurrentLevel - 1; i++) {
formula += FormulaSplitter; // =unique(filter(A2:A84;
LookCol = colName(i);
value = values[i];
mathOpPlusVal = getValueAndMathOpForFunction_(value, FormulaSplitter, ReplaceCommas); // =unique(filter(A2:A84;B2:B84="Text"
if ( Array.isArray(mathOpPlusVal) )
{
formula += mathOpPlusVal[0];
formula += LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84;ROUND(B2:B84
formula += mathOpPlusVal[1];
}
else
{
formula += LookCol + '2:' + LookCol + lastRow; // =unique(filter(A2:A84;B2:B84
formula += mathOpPlusVal;
}
}
formula += "))"; //=unique(filter(A2:A84;B2:B84="Text"))
return formula;
}
function getValueAndMathOpForFunction_(value, FormulaSplitter, ReplaceCommas)
{
var result = '';
var splinter = '';
var type = typeof value;
// strings
if (type === 'string') return '="' + value + '"';
// date
if(value instanceof Date)
{
return ['ROUND(', FormulaSplitter +'5)=ROUND(DATE(' + value.getFullYear() + FormulaSplitter + (value.getMonth() + 1) + FormulaSplitter + value.getDate() + ')' + '+'
+ 'TIME(' + value.getHours() + FormulaSplitter + value.getMinutes() + FormulaSplitter + value.getSeconds() + ')' + FormulaSplitter + '5)'];
}
// numbers
if (type === 'number')
{
if (ReplaceCommas)
{
return '+0=' + value.toString().replace('.', ',');
}
else
{
return '+0=' + value;
}
}
// booleans
if (type === 'boolean')
{
return '=' + value;
}
// other
return '=' + value;
}
function getResponse_(allRange, l, ReplaceCommas)
{
var data = allRange.getValues();
var data_ = allRange.getDisplayValues();
var response = [];
var val = '';
for (var i = 0; i < l; i++)
{
val = data[i][0];
if (val !== '')
{
var type = typeof val;
if (type === 'boolean' || val instanceof Date) val = String(data_[i][0]);
if (type === 'number' && ReplaceCommas) val = val.toString().replace('.', ',')
response.push(val);
}
}
return response;
}
function colName(n) {
var ordA = 'a'.charCodeAt(0);
var ordZ = 'z'.charCodeAt(0);
var len = ordZ - ordA + 1;
var s = "";
while(n >= 0) {
s = String.fromCharCode(n % len + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
}
function getDecimalMarkIsCommaLocals() {
// list of Locals Decimal mark = comma
var LANGUAGE_BY_LOCALE = {
af_NA: "Afrikaans (Namibia)",
af_ZA: "Afrikaans (South Africa)",
af: "Afrikaans",
sq_AL: "Albanian (Albania)",
sq: "Albanian",
ar_DZ: "Arabic (Algeria)",
ar_BH: "Arabic (Bahrain)",
ar_EG: "Arabic (Egypt)",
ar_IQ: "Arabic (Iraq)",
ar_JO: "Arabic (Jordan)",
ar_KW: "Arabic (Kuwait)",
ar_LB: "Arabic (Lebanon)",
ar_LY: "Arabic (Libya)",
ar_MA: "Arabic (Morocco)",
ar_OM: "Arabic (Oman)",
ar_QA: "Arabic (Qatar)",
ar_SA: "Arabic (Saudi Arabia)",
ar_SD: "Arabic (Sudan)",
ar_SY: "Arabic (Syria)",
ar_TN: "Arabic (Tunisia)",
ar_AE: "Arabic (United Arab Emirates)",
ar_YE: "Arabic (Yemen)",
ar: "Arabic",
hy_AM: "Armenian (Armenia)",
hy: "Armenian",
eu_ES: "Basque (Spain)",
eu: "Basque",
be_BY: "Belarusian (Belarus)",
be: "Belarusian",
bg_BG: "Bulgarian (Bulgaria)",
bg: "Bulgarian",
ca_ES: "Catalan (Spain)",
ca: "Catalan",
tzm_Latn: "Central Morocco Tamazight (Latin)",
tzm_Latn_MA: "Central Morocco Tamazight (Latin, Morocco)",
tzm: "Central Morocco Tamazight",
da_DK: "Danish (Denmark)",
da: "Danish",
nl_BE: "Dutch (Belgium)",
nl_NL: "Dutch (Netherlands)",
nl: "Dutch",
et_EE: "Estonian (Estonia)",
et: "Estonian",
fi_FI: "Finnish (Finland)",
fi: "Finnish",
fr_BE: "French (Belgium)",
fr_BJ: "French (Benin)",
fr_BF: "French (Burkina Faso)",
fr_BI: "French (Burundi)",
fr_CM: "French (Cameroon)",
fr_CA: "French (Canada)",
fr_CF: "French (Central African Republic)",
fr_TD: "French (Chad)",
fr_KM: "French (Comoros)",
fr_CG: "French (Congo - Brazzaville)",
fr_CD: "French (Congo - Kinshasa)",
fr_CI: "French (Côte d’Ivoire)",
fr_DJ: "French (Djibouti)",
fr_GQ: "French (Equatorial Guinea)",
fr_FR: "French (France)",
fr_GA: "French (Gabon)",
fr_GP: "French (Guadeloupe)",
fr_GN: "French (Guinea)",
fr_LU: "French (Luxembourg)",
fr_MG: "French (Madagascar)",
fr_ML: "French (Mali)",
fr_MQ: "French (Martinique)",
fr_MC: "French (Monaco)",
fr_NE: "French (Niger)",
fr_RW: "French (Rwanda)",
fr_RE: "French (Réunion)",
fr_BL: "French (Saint Barthélemy)",
fr_MF: "French (Saint Martin)",
fr_SN: "French (Senegal)",
fr_CH: "French (Switzerland)",
fr_TG: "French (Togo)",
fr: "French",
gl_ES: "Galician (Spain)",
gl: "Galician",
ka_GE: "Georgian (Georgia)",
ka: "Georgian",
de_AT: "German (Austria)",
de_BE: "German (Belgium)",
de_DE: "German (Germany)",
de_LI: "German (Liechtenstein)",
de_LU: "German (Luxembourg)",
de_CH: "German (Switzerland)",
de: "German",
el_CY: "Greek (Cyprus)",
el_GR: "Greek (Greece)",
el: "Greek",
hu_HU: "Hungarian (Hungary)",
hu: "Hungarian",
is_IS: "Icelandic (Iceland)",
is: "Icelandic",
id_ID: "Indonesian (Indonesia)",
id: "Indonesian",
it_IT: "Italian (Italy)",
it_CH: "Italian (Switzerland)",
it: "Italian",
kab_DZ: "Kabyle (Algeria)",
kab: "Kabyle",
kl_GL: "Kalaallisut (Greenland)",
kl: "Kalaallisut",
lv_LV: "Latvian (Latvia)",
lv: "Latvian",
lt_LT: "Lithuanian (Lithuania)",
lt: "Lithuanian",
mk_MK: "Macedonian (Macedonia)",
mk: "Macedonian",
naq_NA: "Nama (Namibia)",
naq: "Nama",
pl_PL: "Polish (Poland)",
pl: "Polish",
pt_BR: "Portuguese (Brazil)",
pt_GW: "Portuguese (Guinea-Bissau)",
pt_MZ: "Portuguese (Mozambique)",
pt_PT: "Portuguese (Portugal)",
pt: "Portuguese",
ro_MD: "Romanian (Moldova)",
ro_RO: "Romanian (Romania)",
ro: "Romanian",
ru_MD: "Russian (Moldova)",
ru_RU: "Russian (Russia)",
ru_UA: "Russian (Ukraine)",
ru: "Russian",
seh_MZ: "Sena (Mozambique)",
seh: "Sena",
sk_SK: "Slovak (Slovakia)",
sk: "Slovak",
sl_SI: "Slovenian (Slovenia)",
sl: "Slovenian",
es_AR: "Spanish (Argentina)",
es_BO: "Spanish (Bolivia)",
es_CL: "Spanish (Chile)",
es_CO: "Spanish (Colombia)",
es_CR: "Spanish (Costa Rica)",
es_DO: "Spanish (Dominican Republic)",
es_EC: "Spanish (Ecuador)",
es_SV: "Spanish (El Salvador)",
es_GQ: "Spanish (Equatorial Guinea)",
es_GT: "Spanish (Guatemala)",
es_HN: "Spanish (Honduras)",
es_419: "Spanish (Latin America)",
es_MX: "Spanish (Mexico)",
es_NI: "Spanish (Nicaragua)",
es_PA: "Spanish (Panama)",
es_PY: "Spanish (Paraguay)",
es_PE: "Spanish (Peru)",
es_PR: "Spanish (Puerto Rico)",
es_ES: "Spanish (Spain)",
es_US: "Spanish (United States)",
es_UY: "Spanish (Uruguay)",
es_VE: "Spanish (Venezuela)",
es: "Spanish",
sv_FI: "Swedish (Finland)",
sv_SE: "Swedish (Sweden)",
sv: "Swedish",
tr_TR: "Turkish (Turkey)",
tr: "Turkish",
uk_UA: "Ukrainian (Ukraine)",
uk: "Ukrainian",
vi_VN: "Vietnamese (Vietnam)",
vi: "Vietnamese"
}
var SS = SpreadsheetApp.getActiveSpreadsheet();
var LocalS = SS.getSpreadsheetLocale();
if (LANGUAGE_BY_LOCALE[LocalS] == undefined) {
return false;
}
//Logger.log(true);
return true;
}
/*
function ReplaceDotsToCommas(dataIn) {
var dataOut = dataIn.map(function(num) {
if (isNaN(num)) {
return num;
}
num = num.toString();
return num.replace(".", ",");
});
return dataOut;
}
*/
Here's set of variables that are to be changed, you'll find them in script:
var TargetSheet = 'Main'; // name of sheet with data validation
var LogSheet = 'Data2'; // name of sheet with data
var NumOfLevels = 7; // number of levels of data validation
var lcol = 9; // number of column where validation starts; A = 1, B = 2, etc.
var lrow = 2; // number of row where validation starts
var offsets = [1,1,1,1,1,1,1]; // offsets for levels
I suggest everyone, who knows scripts well, send your edits to this code. I guess, there's simpler way to find validation list and make script run faster.
Here you have another solution based on the one provided by #tarheel
function onEdit() {
var sheetWithNestedSelectsName = "Sitemap";
var columnWithNestedSelectsRoot = 1;
var sheetWithOptionPossibleValuesSuffix = "TabSections";
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var activeSheet = SpreadsheetApp.getActiveSheet();
// If we're not in the sheet with nested selects, exit!
if ( activeSheet.getName() != sheetWithNestedSelectsName ) {
return;
}
var activeCell = SpreadsheetApp.getActiveRange();
// If we're not in the root column or a content row, exit!
if ( activeCell.getColumn() != columnWithNestedSelectsRoot || activeCell.getRow() < 2 ) {
return;
}
var sheetWithActiveOptionPossibleValues = activeSpreadsheet.getSheetByName( activeCell.getValue() + sheetWithOptionPossibleValuesSuffix );
// Get all possible values
var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues.getSheetValues( 1, 1, -1, 1 );
var possibleValuesValidation = SpreadsheetApp.newDataValidation();
possibleValuesValidation.setAllowInvalid( false );
possibleValuesValidation.requireValueInList( activeOptionPossibleValues, true );
activeSheet.getRange( activeCell.getRow(), activeCell.getColumn() + 1 ).setDataValidation( possibleValuesValidation.build() );
}
It has some benefits over the other approach:
You don't need to edit the script every time you add a "root option". You only have to create a new sheet with the nested options of this root option.
I've refactored the script providing more semantic names for the variables and so on. Furthermore, I've extracted some parameters to variables in order to make it easier to adapt to your specific case. You only have to set the first 3 values.
There's no limit of nested option values (I've used the getSheetValues method with the -1 value).
So, how to use it:
Create the sheet where you'll have the nested selectors
Go to the "Tools" > "Script Editor…" and select the "Blank project" option
Paste the code attached to this answer
Modify the first 3 variables of the script setting up your values and save it
Create one sheet within this same document for each possible value of the "root selector". They must be named as the value + the specified suffix.
Enjoy!
Edit: The answer below may be satisfactory, but it has some drawbacks:
There is a noticeable pause for the running of the script. I'm on a 160 ms latency, and it's enough to be annoying.
It works by building a new range each time you edit a given row. This gives an 'invalid contents' to previous entries some of the time
I hope others can clean this up somewhat.
Here's another way to do it, that saves you a ton of range naming:
Three sheets in the worksheet: call them Main, List, and DRange (for dynamic range.)
On the Main sheet, column 1 contains a timestamp. This time stamp is modified onEdit.
On List your categories and subcategories are arranged as a simple list. I'm using this for plant inventory at my tree farm, so my list looks like this:
Group | Genus | Bot_Name
Conifer | Abies | Abies balsamea
Conifer | Abies | Abies concolor
Conifer | Abies | Abies lasiocarpa var bifolia
Conifer | Pinus | Pinus ponderosa
Conifer | Pinus | Pinus sylvestris
Conifer | Pinus | Pinus banksiana
Conifer | Pinus | Pinus cembra
Conifer | Picea | Picea pungens
Conifer | Picea | Picea glauca
Deciduous | Acer | Acer ginnala
Deciduous | Acer | Acer negundo
Deciduous | Salix | Salix discolor
Deciduous | Salix | Salix fragilis
...
Where | indicates separation into columns.
For convenience I also used the headers as names for named ranges.
DRrange A1 has the formula
=Max(Main!A2:A1000)
This returns the most recent timestamp.
A2 to A4 have variations on:
=vlookup($A$1,Inventory!$A$1:$E$1000,2,False)
with the 2 being incremented for each cell to the right.
On running A2 to A4 will have the currently selected Group, Genus and Species.
Below each of these, is a filter command something like this:
=unique(filter(Bot_Name,REGEXMATCH(Bot_Name,C1)))
These filters will populate a block below with matching entries to the contents of the top cell.
The filters can be modified to suit your needs, and to the format of your list.
Back to Main: Data validation in Main is done using ranges from DRange.
The script I use:
function onEdit(event) {
//SETTINGS
var dynamicSheet='DRange'; //sheet where the dynamic range lives
var tsheet = 'Main'; //the sheet you are monitoring for edits
var lcol = 2; //left-most column number you are monitoring; A=1, B=2 etc
var rcol = 5; //right-most column number you are monitoring
var tcol = 1; //column number in which you wish to populate the timestamp
//
var s = event.source.getActiveSheet();
var sname = s.getName();
if (sname == tsheet) {
var r = event.source.getActiveRange();
var scol = r.getColumn(); //scol is the column number of the edited cell
if (scol >= lcol && scol <= rcol) {
s.getRange(r.getRow(), tcol).setValue(new Date());
for(var looper=scol+1; looper<=rcol; looper++) {
s.getRange(r.getRow(),looper).setValue(""); //After edit clear the entries to the right
}
}
}
}
Original Youtube presentation that gave me most of the onEdit timestamp component:
https://www.youtube.com/watch?v=RDK8rjdE85Y
Continuing the evolution of this solution I've upped the ante by adding support for multiple root selections and deeper nested selections. This is a further development of JavierCane's solution (which in turn built on tarheel's).
/**
* "on edit" event handler
*
* Based on JavierCane's answer in
*
* http://stackoverflow.com/questions/21744547/how-do-you-do-dynamic-dependent-drop-downs-in-google-sheets
*
* Each set of options has it own sheet named after the option. The
* values in this sheet are used to populate the drop-down.
*
* The top row is assumed to be a header.
*
* The sub-category column is assumed to be the next column to the right.
*
* If there are no sub-categories the next column along is cleared in
* case the previous selection did have options.
*/
function onEdit() {
var NESTED_SELECTS_SHEET_NAME = "Sitemap"
var NESTED_SELECTS_ROOT_COLUMN = 1
var SUB_CATEGORY_COLUMN = NESTED_SELECTS_ROOT_COLUMN + 1
var NUMBER_OF_ROOT_OPTION_CELLS = 3
var OPTION_POSSIBLE_VALUES_SHEET_SUFFIX = ""
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var activeSheet = SpreadsheetApp.getActiveSheet()
if (activeSheet.getName() !== NESTED_SELECTS_SHEET_NAME) {
// Not in the sheet with nested selects, exit!
return
}
var activeCell = SpreadsheetApp.getActiveRange()
// Top row is the header
if (activeCell.getColumn() > SUB_CATEGORY_COLUMN ||
activeCell.getRow() === 1 ||
activeCell.getRow() > NUMBER_OF_ROOT_OPTION_CELLS + 1) {
// Out of selection range, exit!
return
}
var sheetWithActiveOptionPossibleValues = activeSpreadsheet
.getSheetByName(activeCell.getValue() + OPTION_POSSIBLE_VALUES_SHEET_SUFFIX)
if (sheetWithActiveOptionPossibleValues === null) {
// There are no further options for this value, so clear out any old
// values
activeSheet
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)
.clearDataValidations()
.clearContent()
return
}
// Get all possible values
var activeOptionPossibleValues = sheetWithActiveOptionPossibleValues
.getSheetValues(1, 1, -1, 1)
var possibleValuesValidation = SpreadsheetApp.newDataValidation()
possibleValuesValidation.setAllowInvalid(false)
possibleValuesValidation.requireValueInList(activeOptionPossibleValues, true)
activeSheet
.getRange(activeCell.getRow(), activeCell.getColumn() + 1)
.setDataValidation(possibleValuesValidation.build())
} // onEdit()
As Javier says:
Create the sheet where you'll have the nested selectors
Go to the "Tools" > "Script Editor…" and select the "Blank project"
option
Paste the code attached to this answer
Modify the constants at the top of the script setting up your values
and save it
Create one sheet within this same document for each possible value of
the "root selector". They must be named as the value + the specified
suffix.
And if you wanted to see it in action I've created a demo sheet and you can see the code if you take a copy.

How to return a count of fields with a given value in a record?

I have a database table with the following fields :
---------------------
FIELDS : | H1 | H2 | H3 | H4
---------------------
VALUES : | A | B | A | C
---------------------
For a given record (row), I would like to count the number of fields with a value of A. In the above, for example, there are two fields with a value of A, so the expected result would be : 2
How can I achieve this?
I am trying to answer the question from a database point of view.
You have a table with one or more rows and every row has in the four columns either an 'A' or something else. For a given row (or for many rows) you want to get the number of columns that have an 'A' in it.
As one commenter pointed out you can't sum letters but you can check whether or not a value is the one you are looking for and then count this occurence as a 1 or 0. Finally sum those values and return the sum.
SELECT (CASE H1 WHEN 'A' THEN 1 ELSE 0 END) +
(CASE H2 WHEN 'A' THEN 1 ELSE 0 END) +
(CASE H3 WHEN 'A' THEN 1 ELSE 0 END) +
(CASE H4 WHEN 'A' THEN 1 ELSE 0 END) AS number_of_a
FROM name_of_your_table;
For your example row this will return:
NUMBER_OF_A
===========
2
If you have more than one row you'll get the number of As for every row.
I test this it work Thanx for help.
SELECT count(H1) + count(H2) + count(H3) + count(H4) + count(H5) +
count(H6) + count(H7) + count(H8) as TOT
from Table T
where T.H1 = 'A' or T.H2 = 'A' or T.H3 = 'A' or T.H4 = 'A'
or T.H5 = 'A' or T.H6 = 'A' or T.H7 = 'A' or T.H8 = 'A'
group by T.ID
order by 1 DESC
Other solution ...

How to automatically format data to create a treemap in Google Sheets?

I want to create a treemap from dynamic data in google spreadsheets. So far, I succeeded to have a table in a format that Excel can use, but I don't know how to transform this table in a table that Google sheet can use to create this treemap
Excel can use this data. Not Google sheet.
My data looks like this:
Categories Item Value
__________ ______ _____
category_1 item_1 5
category_1 item_2 20
category_1 item_3 1
category_2 item_4 0
category_2 item_5 5
category_2 item_6 18
category_3 item_7 16
category_4 item_8 7
category_4 item_9 16
I would like to find a way to transform this data into something like the table below, which is usable by Google sheet.
Item Parent Value
__________ __________ _____
Categories 88
category_1 Categories 26
item_1 category_1 5
item_2 category_1 20
item_3 category_1 1
category_2 Categories 23
item_4 category_2 0
item_5 category_2 5
item_6 category_2 18
category_3 Categories 16
item_7 category_3 16
category_4 Categories 23
item_8 category_4 7
item_9 category_4 16
I did not find a way to do that yet and was wondering if anyone had faced the same problem.
Probably you can use this simple script function as an example:
function makeTree() {
var srcRange = SpreadsheetApp.getActiveSheet().getRange('A2:C10'),
tree = {'.Categories': 0}, key;
// Fill tree object with source data
srcRange.getValues().forEach(function(rowValues) {
// Add row value to the root
tree['.Categories'] += rowValues[2];
// Add it to "Category" level
key = 'Categories.' + rowValues[0];
if (tree[key] == undefined) {
tree[key] = rowValues[2];
} else {
tree[key] += rowValues[2];
}
// Add it to "Item" level too
key = rowValues[0] + '.' + rowValues[1];
if (tree[key] == undefined) {
tree[key] = rowValues[2];
} else {
tree[key] += rowValues[2];
}
});
// Format tree rows for output
var values = [];
for (key in tree) {
var subKeys = key.split('.');
values.push([subKeys[1], subKeys[0], tree[key]]);
}
// Fill target data rows
var targetRange = srcRange.offset(0, 4, values.length);
targetRange.setValues(values);
}
Here we collect all data in a single JS object, using composite string keys with a dot delimiter. Ready object is converted to the 2D-array before a target range filling. As a result we have both ranges on the same sheet:

how to group a date column based on date range in oracle

I have a table which contains a feedback about a product.It has feedback type (positive ,negative) which is a text column, date on which comments made. I need to get total count of positive ,negative feedback for particular time period . For example if the date range is 30 days, I need to get total count of positive ,negative feedback for 4 weeks , if the date range is 6 months , I need to get total count of positive ,negative feedback for each month. How to group the count based on date.
+------+------+----------+----------+---------------+--+--+--+
| Slno | User | Comments | type | commenteddate | | | |
+------+------+----------+----------+---------------+--+--+--+
| 1 | a | aaaa | positive | 22-jun-2016 | | | |
| 2 | b | bbb | positive | 1-jun-2016 | | | |
| 3 | c | qqq | negative | 2-jun-2016 | | | |
| 4 | d | ccc | neutral | 3-may-2016 | | | |
| 5 | e | www | positive | 2-apr-2016 | | | |
| 6 | f | s | negative | 11-nov-2015 | | | |
+------+------+----------+----------+---------------+--+--+--+
Query i tried is
SELECT type, to_char(commenteddate,'DD-MM-YYYY'), Count(type) FROM comments GROUP BY type, to_char(commenteddate,'DD-MM-YYYY');
Here's a kick at the can...
Assumptions:
you want to be able to switch the groupings to weekly or monthly only
the start of the first period will be the first date in the feedback data; intervals will be calculated from this initial date
output will show feedback value, time period, count
time periods will not overlap so periods will be x -> x + interval - 1 day
time of day is not important (time for commented dates is always 00:00:00)
First, create some sample data (100 rows):
drop table product_feedback purge;
create table product_feedback
as
select rownum as slno
, chr(65 + MOD(rownum, 26)) as userid
, lpad(chr(65 + MOD(rownum, 26)), 5, chr(65 + MOD(rownum, 26))) as comments
, trunc(sysdate) + rownum + trunc(dbms_random.value * 10) as commented_date
, case mod(rownum * TRUNC(dbms_random.value * 10), 3)
when 0 then 'positive'
when 1 then 'negative'
when 2 then 'neutral' end as feedback
from dual
connect by level <= 100
;
Here's what my sample data looks like:
select *
from product_feedback
;
SLNO USERID COMMENTS COMMENTED_DATE FEEDBACK
1 B BBBBB 2016-08-06 neutral
2 C CCCCC 2016-08-06 negative
3 D DDDDD 2016-08-14 positive
4 E EEEEE 2016-08-16 negative
5 F FFFFF 2016-08-09 negative
6 G GGGGG 2016-08-14 positive
7 H HHHHH 2016-08-17 positive
8 I IIIII 2016-08-18 positive
9 J JJJJJ 2016-08-12 positive
10 K KKKKK 2016-08-15 neutral
11 L LLLLL 2016-08-23 neutral
12 M MMMMM 2016-08-19 positive
13 N NNNNN 2016-08-16 neutral
...
Now for the fun part. Here's the gist:
find out what the earliest and latest commented dates are in the data
include a query where you can set the time period (to "WEEKS" or "MONTHS")
generate all of the (weekly or monthly) time periods between the min/max dates
join the product feedback to the time periods (commented date between start and end) with an outer join in case you want to see all time periods whether or not there was any feedback
group the joined result by feedback, period start, and period end, and set up a column to count one of the 3 possible feedback values
x
with
min_max_dates -- get earliest and latest feedback dates
as
(select min(commented_date) min_date, max(commented_date) max_date
from product_feedback
)
, time_period_interval
as
(select 'MONTHS' as tp_interval -- set the interval/time period here
from dual
)
, -- generate all time periods between the start date and end date
time_periods (start_of_period, end_of_period, max_date, time_period) -- recursive with clause - fun stuff!
as
(select mmd.min_date as start_of_period
, CASE WHEN tpi.tp_interval = 'WEEKS'
THEN mmd.min_date + 7
WHEN tpi.tp_interval = 'MONTHS'
THEN ADD_MONTHS(mmd.min_date, 1)
ELSE NULL
END - 1 as end_of_period
, mmd.max_date
, tpi.tp_interval as time_period
from time_period_interval tpi
cross join
min_max_dates mmd
UNION ALL
select CASE WHEN time_period = 'WEEKS'
THEN start_of_period + 7 * (ROWNUM )
WHEN time_period = 'MONTHS'
THEN ADD_MONTHS(start_of_period, ROWNUM)
ELSE NULL
END as start_of_period
, CASE WHEN time_period = 'WEEKS'
THEN start_of_period + 7 * (ROWNUM + 1)
WHEN time_period = 'MONTHS'
THEN ADD_MONTHS(start_of_period, ROWNUM + 1)
ELSE NULL
END - 1 as end_of_period
, max_date
, time_period
from time_periods
where end_of_period <= max_date
)
-- now put it all together
select pf.feedback
, tp.start_of_period
, tp.end_of_period
, count(*) as feedback_count
from time_periods tp
left outer join
product_feedback pf
on pf.commented_date between tp.start_of_period and tp.end_of_period
group by tp.start_of_period
, tp.end_of_period
, pf.feedback
order by pf.feedback
, tp.start_of_period
;
Output:
negative 2016-08-06 2016-09-05 6
negative 2016-09-06 2016-10-05 7
negative 2016-10-06 2016-11-05 8
negative 2016-11-06 2016-12-05 1
neutral 2016-08-06 2016-09-05 6
neutral 2016-09-06 2016-10-05 5
neutral 2016-10-06 2016-11-05 11
neutral 2016-11-06 2016-12-05 2
positive 2016-08-06 2016-09-05 17
positive 2016-09-06 2016-10-05 16
positive 2016-10-06 2016-11-05 15
positive 2016-11-06 2016-12-05 6
-- EDIT --
New and improved, all in one easy to use procedure. (I will assume you can configure the procedure to make use of the query in whatever way you need.) I made some changes to simplify the CASE statements in a few places and note that for whatever reason using a LEFT OUTER JOIN in the main SELECT results in an ORA-600 error for me so I switched it to INNER JOIN.
CREATE OR REPLACE PROCEDURE feedback_counts(p_days_chosen IN NUMBER, p_cursor OUT SYS_REFCURSOR)
AS
BEGIN
OPEN p_cursor FOR
with
min_max_dates -- get earliest and latest feedback dates
as
(select min(commented_date) min_date, max(commented_date) max_date
from product_feedback
)
, time_period_interval
as
(select CASE
WHEN p_days_chosen BETWEEN 1 AND 10 THEN 'DAYS'
WHEN p_days_chosen > 10 AND p_days_chosen <=31 THEN 'WEEKS'
WHEN p_days_chosen > 31 AND p_days_chosen <= 365 THEN 'MONTHS'
ELSE '3-MONTHS'
END as tp_interval -- set the interval/time period here
from dual --(SELECT p_days_chosen as days_chosen from dual)
)
, -- generate all time periods between the start date and end date
time_periods (start_of_period, end_of_period, max_date, tp_interval) -- recursive with clause - fun stuff!
as
(select mmd.min_date as start_of_period
, CASE tpi.tp_interval
WHEN 'DAYS'
THEN mmd.min_date + 1
WHEN 'WEEKS'
THEN mmd.min_date + 7
WHEN 'MONTHS'
THEN mmd.min_date + 30
WHEN '3-MONTHS'
THEN mmd.min_date + 90
ELSE NULL
END - 1 as end_of_period
, mmd.max_date
, tpi.tp_interval
from time_period_interval tpi
cross join
min_max_dates mmd
UNION ALL
select CASE tp_interval
WHEN 'DAYS'
THEN start_of_period + 1 * ROWNUM
WHEN 'WEEKS'
THEN start_of_period + 7 * ROWNUM
WHEN 'MONTHS'
THEN start_of_period + 30 * ROWNUM
WHEN '3-MONTHS'
THEN start_of_period + 90 * ROWNUM
ELSE NULL
END as start_of_period
, start_of_period
+ CASE tp_interval
WHEN 'DAYS'
THEN 1
WHEN 'WEEKS'
THEN 7
WHEN 'MONTHS'
THEN 30
WHEN '3-MONTHS'
THEN 90
ELSE NULL
END * (ROWNUM + 1)
- 1 as end_of_period
, max_date
, tp_interval
from time_periods
where end_of_period <= max_date
)
-- now put it all together
select pf.feedback
, tp.start_of_period
, tp.end_of_period
, count(*) as feedback_count
from time_periods tp
inner join -- currently a bug that prevents the procedure from compiling with a LEFT OUTER JOIN
product_feedback pf
on pf.commented_date between tp.start_of_period and tp.end_of_period
group by tp.start_of_period
, tp.end_of_period
, pf.feedback
order by tp.start_of_period
, pf.feedback
;
END;
Test the procedure (in something like SQLPlus or SQL Developer):
var x refcursor
exec feedback_counts(10, :x)
print :x

Random values with different weights

Here's a question about entity framework that has been bugging me for a bit.
I have a table called prizes that has different prizes. Some with higher and some with lower monetary values. A simple representation of it would be as such:
+----+---------+--------+
| id | name | weight |
+----+---------+--------+
| 1 | Prize 1 | 80 |
| 2 | Prize 2 | 15 |
| 3 | Prize 3 | 5 |
+----+---------+--------+
Weight is this case is the likely hood I would like this item to be randomly selected.
I select one random prize at a time like so:
var prize = db.Prizes.OrderBy(r => Guid.NewGuid()).Take(1).First();
What I would like to do is use the weight to determine the likelihood of a random item being returned, so Prize 1 would return 80% of the time, Prize 2 15% and so on.
I thought that one way of doing that would be by having the prize on the database as many times as the weight. That way having 80 times Prize 1 would have a higher likelihood of being returned when compared to Prize 3, but this is not necessarily exact.
There has to be a better way of doing this, so i was wondering if you could help me out with this.
Thanks in advance
Normally I would not do this in database, but rather use code to solve the problem.
In your case, I would generate a random number within 1 to 100. If the number generated is between 1 to 80 then 1st one wins, if it's between 81 to 95 then 2nd one wins, and if between 96 to 100 the last one win.
Because the random number could be any number from 1 to 100, each number has 1% of chance to be hit, then you can manage the winning chance by giving the range of what the random number falls into.
Hope this helps.
Henry
This can be done by creating bins for the three (generally, n) items and then choose a selected random to be dropped in one of those bins.
There might be a statistical library that could do this for you i.e. proportionately select a bin from n bins.
A solution that does not limit you to three prizes/weights could be implemented like below:
//All Prizes in the Database
var allRows = db.Prizes.ToList();
//All Weight Values
var weights = db.Prizes.Select(p => new { p.Weight });
//Sum of Weights
var weightsSum = weights.AsEnumerable().Sum(w => w.Weight);
//Construct Bins e.g. [0, 80, 95, 100]
//Three Bins are: (0-80],(80-95],(95-100]
int[] bins = new int[weights.Count() + 1];
int start = 0;
bins[start] = 0;
foreach (var weight in weights) {
start++;
bins[start] = bins[start - 1] + weight.Weight;
}
//Generate a random number between 1 and weightsSum (inclusive)
Random rnd = new Random();
int selection = rnd.Next(1, weightsSum + 1);
//Assign random number to the bin
int chosenBin = 0;
for (chosenBin = 0; chosenBin < bins.Length; chosenBin++)
{
if (bins[chosenBin] < selection && selection <= bins[chosenBin + 1])
{
break;
}
}
//Announce the Prize
Console.WriteLine("You have won: " + allRows.ElementAt(chosenBin));

Resources