Get row values by column name match - google-apps-script

I want to get the value of all the cells of rows. But not from all the rows, only rows having specific name in a specific column.
I attached a screenshot. I want to get all row values which "userID" is "user3". It means row 4 row 6 data.
I used following code.
function getByName(colName, row) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var col = data[0].indexOf(colName);
if (col != -1) {
return data[row-1][col];
}
}
Above I getting only specific cell value in the user ID column. Not all data in the ROW.
function getAllrowData() {
var user3 = getByName("userID",2);
var values = user3.getDisplayValues()
Logger.log(values)
return values
}
getByname giving only cell value.
I want following result so I can display data in html, belongs to only "user 3" in the image. Help me to correct my code.
C 25 30 0 16 user3
E 28 36 6 19 user3

Get Data by Row Number and Column Name:
function getByName(user, colName = 'userID') {
const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getDataRange().getValues();
const hA = data.shift();
const idx = hA.reduce((a, h, i) => (a[h] = i, a), {});
let o = data.map(r => {
if (r[idx[colName]] == user) {
return r;
}
}).filter(r => r );
if (o && o.length > 0) {
return o;
}
}

Remove [col] from return data[row-1][col];
The above because sheet.getDataRange().getValues(); return an Array of Arrays of values. Using two indexes returns a cell value, using only the first index will return an Array having all the row values.

Related

How to Find Empty Rows Using Map() in Google Apps Script

I just renew the example and also add the How I want the code to work
I'm quite new with map() function. I want to change the for and if condition to map() because it takes to long for processing a lot of data. Or you guys have any idea to make my code more faster and efficient to work, it can be really helpful.
How I want my code's work:
1. Find the row that have empty value on Column 3 from Table of Data
2. Concate or merge the value of Column 1 and Column 2 from Table of Data
3. Find the same value with the merged value in Table of Source_data
4. If the merged value is same with the value of Column 1 on Table of Source_data, then Get the data of column 2, Column 3, and Column 4
5. Write the data from Table of Source_data (Column 2, Column 3, Column 4) on the Column 3, Column 4, and Column 5 of Table of Data (Result like The Expected Output)
Thank you!
Table of Data:
Column 1
Column 2
Column 3
Column 4
Column 5
lose
data
data1
data2
data3
Second
row
Second
row
Second
row
data4
data5
data6
Table of Source_Data:
Column 1
Column 2
Column 3
Column 4
losedata
data1
data2
data3
Secondrow
data4
data5
data6
Table of Data: (Expected Output)
Column 1
Column 2
Column 3
Column 4
Column 5
lose
data
data1
data2
data3
Second
row
data4
data5
data6
Second
row
data4
data5
data6
Second
row
data4
data5
data6
function main3() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var data = sheet.getDataRange().getValues();
var source_file = SpreadsheetApp.openById("").getSheetByName("");
var source_data = source_file.getRange(2, 1, source_file.getLastRow() - 1, source_file.getLastColumn()).getValues();
var headerName1 = "Column 1";
var headerName2 = "Column 2";
var header = data.shift();
var header_index1 = header.indexOf(headerName1);
var header_index2 = header.indexOf(headerName2);
// To find empty row with specific number of column
for (var i = 1; i < data.length; i++) {
if (data[i][2] == "") {
// merge 2 column
// column 1: lose and column 2: data
// var concat will generate the merge of those two column -> losedata
var concat = data.map((row, i) => ([data[i][header_index1] + data[i][header_index2]]));
// find the same value with the value of merged columns
// this will find the same data on source_data (Source Spreadsheet) like "losedata"
var matching = concat.map(row => {
var row_match = source_data.find(r => r[0] == row[0])
return row_match ? [row_match[3], row_match[4], row_match[5]] : [null, null, null]
});
// write the value to the table of Data
sheet.getRange(2, 3, matching.length, matching[0].length).setValues(matching);
}
}
}
As far as I can tell the problem is in setValues() inside the loop. It doesn't matter if the loop is for() or map(). If you want to speed up the script you have to reduce calls to the server. I believe in this case you can process all data on client side as a 2d array and set it on the sheet all at once with just one setValues():
function myFunction() {
// get the destination sheet and data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var [header, ...data] = sheet.getDataRange().getValues();
// get the source data
var source_file = SpreadsheetApp.openById('').getSheetByName('');
var [_, ...src_data] = source_file.getDataRange().getValues();
// make source object from the source data {col1:[col2, col3, col4], ...}
var obj = {};
src_data.forEach(x => obj[x[0]] = x.slice(1));
// loop through all the data and add cells from the object
// whenever the object has the key (key is col1 + col2 of current row)
for (let row in data) {
var key = data[row][0] + data[row][1];
if (key in obj) data[row] = [data[row][0], data[row][1], ...obj[key]];
}
// make the table from the updated data and set the table on the sheet
var table = [header, ...data];
sheet.getDataRange().setValues(table);
}
Data (original):
Source:
Data (results):
Update
It turned out the actual data has another columns.
Data (original):
Source:
Data (results):
The updated code to copy the blue columns from source to the data sheet (based on 'keys' in yellow columns) is here:
function myFunction() {
// get the destination sheet and data
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var [header, ...data] = sheet.getDataRange().getValues();
// get the source data
var source_file = ss.getSheetByName('Source');
// var source_file = SpreadsheetApp.openById('').getSheetByName('');
var [_, ...src_data] = source_file.getDataRange().getValues();
// make source object from the source data {col1:[col2, col3, col4], ...}
var obj = {};
src_data.forEach(x => obj[x[0]] = x.slice(3));
var headerName1 = "Column 1";
var headerName2 = "Column 5";
var header_index1 = header.indexOf(headerName1);
var header_index2 = header.indexOf(headerName2);
// loop through all the data and add cells from the object
// whenever the object has the key (key is col1 + col2 of current row)
for (let row in data) {
var key = data[row][header_index1] + data[row][header_index2];
if (key in obj) data[row] = [...data[row].slice(0,5), ...obj[key]];
}
// make the table from the updated data and set the table on the sheet
var table = [header, ...data];
sheet.getDataRange().setValues(table);
}
A better way to look for empty cells is using the Range.getNextDataCell(). This will look for the first empty cell. Assuming there are no empty cells in the column it will find the last empty cell. This is the same as Ctrl+[arrow key]. You can also use it to find empty columns by using Direction.NEXT or PREVIOUS.
Actually it's not the empty cell it returns but the boundary cell containing data of the direction you are looking. So be sure and add 1 or subtract 1 if looking UP.
function getEmptyCell() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet3");
let column = 1;
// In these 2 examples cell A6 is empty then A16. Column B has 20 values, more than A
// This first example finds the first empty cell starting from the 1st row down
let row = sheet.getRange(1,column).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
console.log(row);
// In this example the first empty cell starting from the last row up.
row = sheet.getRange(sheet.getLastRow()+1,column).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
console.log(row);
}
catch(err) {
Logger.log("Error in getEmptyCell: "+err)
}
}
6:51:01 AM Notice Execution started
6:51:03 AM Info 5
6:51:03 AM Info 15
6:51:03 AM Notice Execution completed

Google Spreadsheets | How to get the first and last active cell in a range, and then return a cell offset from that cell

I want to try to automate a very tedious process that I've been stuck with for a while now in my Gantt charts.
I can get the range that I want to pull from, but what I really need are cells offset from that range corresponding with the first and last active cells in the range.
Columns B and C are what I need automated. They represent Row 1's values on the first and last active cell per row.
I'm familiar with how to do the opposite, to populate a range with something based on first and last values of column B/C. "x" however in my sheets is often different values which feed into other sources of code and are often changed by people other than myself, so I don't have many options on changing my workflow like this.
Happy to provide any additional info, a solution in cell formulas or in scripts is perfectly fine, scripts would be actually be preferable but I'd be happy with either. Thanks for reading!
It can be done pretty easy with array.findIndex() method. Here is the example:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var [header, ...rows] = range.getValues();
var new_rows = [];
for (var row of rows) {
var [a, b, c, d, ...etc] = row;
b = get_first_not_empty_cell_from(etc);
c = get_last_not_empty_cell_from(etc);
new_rows.push([a, b, c, d, ...etc]);
}
console.log(new_rows);
range.setValues([header, ...new_rows]);
}
function get_first_not_empty_cell_from(array) {
var index = array.findIndex(x => x != '') + 1;
return index;
}
function get_last_not_empty_cell_from(array) {
var index = array.slice().reverse().findIndex(x => x != '');
return array.length - index;
}
Reference:
array.findIndex()
Update
Here is the updated variant of the code:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var [header, row2, ...rows] = range.getValues(); // <-- skip row 2
var new_rows = [];
for (var row of rows) {
var [a, b, c, d, ...etc] = row;
b = header[get_first_index(etc) + 3]; // <-- value from header
c = header[get_last_index(etc) + 3]; // <-- value from header
new_rows.push([a, b, c, d, ...etc]);
}
console.log(new_rows);
range.setValues([header, row2, ...new_rows]); // <-- add row 2
}
function get_first_index(array) {
var index = array.findIndex(x => x != '') + 1;
return index;
}
function get_last_index(array) {
var index = array.slice().reverse().findIndex(x => x != '');
return array.length - index;
}
Basically this is all the same, but now it puts values from the cells of the first row instead of the column indexes. And it skips the second empty row.
Just in case, the same code in the short 'functional' style:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var [header, row2, ...rows] = range.getValues();
const first = ar => ar.findIndex(x => x != '') + 1;
const last = ar => ar.length - ar.slice().reverse().findIndex(x => x != '');
var new_rows = rows.map(([a, b, c, d, ...etc]) =>
[a, header[first(etc) + 3], header[last(etc) + 3], d, ...etc]);
console.log(new_rows);
range.setValues([header, row2, ...new_rows]);
}

Inserting rows with data upon finding certain value in Column B

My question is somehow complicated . I will try to simplify it as much as possible.
I have added a link to the sheet for simplicity.
Sample Sheet
I have a large table of about 10,000 rows. I want to insert 9 rows after each row containing the word " Test 22" in column B. This is the first stage. The most important part ( stage II) that I want to fill data in these 9 rows as following :
Column A (Product Name) cells will contain the same product name as the first cell adjacent to the cell of value "Test 22"
Column E ( Empty Column 2) cells which are 9 cells will contain these values (Result 1, Result 2 , Result 3, Result 4 , Result 5 , Result 6 , Average, Max, Min). And of course , this process will be repeated through the whole table upon finding the word " Test 22" in column B.
I have managed successfully to perform stage I which is inserting 9 blank rows after each row containing the word " Test 22" in column B, but I couldn't perform stage II. I don't know if this function could be done in 1 step or 2 steps.
Your help will be really appreciated.
Thanks
It can be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var data = sheet.getDataRange().getValues();
var new_data = [];
for (var row in data) {
new_data.push(data[row]);
if (data[row][1] == 'Test 22') {
new_data.push([data[row][0],'','','','Result 1']);
new_data.push([data[row][0],'','','','Result 2']);
new_data.push([data[row][0],'','','','Result 3']);
new_data.push([data[row][0],'','','','Result 4']);
new_data.push([data[row][0],'','','','Result 5']);
new_data.push([data[row][0],'','','','Result 6']);
new_data.push([data[row][0],'','','','Average']);
new_data.push([data[row][0],'','','','Max']);
new_data.push([data[row][0],'','','','Min']);
}
}
sheet.getRange(1,1,new_data.length,new_data[0].length,).setValues(new_data);
}
or, about the same algo with less verbose notation:
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var new_data = [];
var col_e = ['Result 1','Result 2','Result 3','Result 4',
'Result 5','Result 6','Average','Max','Min'];
for (let row of data) {
new_data.push(row);
if (row[1] == 'Test 22') {
col_e.forEach(e => new_data.push([row[0],'','','',e]));
}
}
sheet.getRange(1,1,new_data.length,new_data[0].length,).setValues(new_data);
}
Update
The function to format the column F:
function format_column_F() {
var sheet = SpreadsheetApp.getActiveSheet();
var column = sheet.getRange('E:E').getValues().flat();
var formats = new Array(9).fill(['0.00 %']);
var row = 0;
while (row++ < column.length) {
if (column[row] == 'Result 1') {
let range = `F${row+1}:F${row+6}`;
let formulas = [[`=AVERAGE(${range})`],[`=MAX(${range})`],[`=MIN(${range})`]];
sheet.getRange(`F${row+7}:F${row+9}`).setFormulas(formulas);
sheet.getRange(`F${row+1}:F${row+9}`).setNumberFormats(formats);
row += 9;
}
}
}
Refs:
Date and number formats
Range.setFormulas()
Array.fill()
Template literals `${}`

Google Apps Script to filter unique data in a filtered range

I have a data similar to this one
Screenshot of data
First, I filtered out the range where column N is marked as yes and I success to get that. Then I want to filter out all unique value in column A together with value in column M and return the row number of the unique value. Please help me with that one.
So the result will be a range/array like this:
[[HCMC 1, handoi3648#gmail.com, 1 (/// row 1 of the filtered range)],
[HCMC 4, handoi3648#gmail.com, 3 (/// row 3 of the filtered range)],
[HCMC 5, handoi3648#gmail.com, 4 (/// row 4 of the filtered range)]]
My code upto the first filtered range as follows
function HouseLeaseReminderAtYE(){
var SS = SpreadsheetApp.getActiveSpreadsheet();
var Sheet = SS.getSheetByName("Tax_Master");
var Range = Sheet.getDataRange();
var Values = Range.getDisplayValues();
var hl_to_remind_at_final_range = Values.filter(function(item){return item[13]==="Y"});}
function HouseLeaseReminderAtYE() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sh = ss.getSheetByName("Tax_Master");
const rg = sh.getDataRange();
const vs = rg.getDisplayValues();
let fvs = vs.filter(function (item) { return item[13] == "Y" });
let uO = {pA:[]};
fvs.forEach((r,i) => {
if(!uO.hasOwnProperty(r[0])) {
uO[r[0]] = [r[0],r[12],i+1];
uO.pA.push(r[0]);
}
});
let uA = uO.pA.map( p => [uO[p]]);
Logger.log(uA);
}

Skip blank values when converting wide to long in google sheets

I am using this excellent script to convert a wide table to a long table in google sheets https://stackoverflow.com/a/43681525/2048559.
The Code:
/**
* Unpivot a pivot table of any size.
*
* #param {A1:D30} data The pivot table.
* #param {1} fixColumns Number of columns, after which pivoted values begin. Default 1.
* #param {1} fixRows Number of rows (1 or 2), after which pivoted values begin. Default 1.
* #param {"city"} titlePivot The title of horizontal pivot values. Default "column".
* #param {"distance"[,...]} titleValue The title of pivot table values. Default "value".
* #return The unpivoted table
* #customfunction
*/
function unpivot(data,fixColumns,fixRows,titlePivot,titleValue) {
var fixColumns = fixColumns || 1; // how many columns are fixed
var fixRows = fixRows || 1; // how many rows are fixed
var titlePivot = titlePivot || 'column';
var titleValue = titleValue || 'value';
var ret=[],i,j,row,uniqueCols=1;
// we handle only 2 dimension arrays
if (!Array.isArray(data) || data.length < fixRows || !Array.isArray(data[0]) || data[0].length < fixColumns)
throw new Error('no data');
// we handle max 2 fixed rows
if (fixRows > 2)
throw new Error('max 2 fixed rows are allowed');
// fill empty cells in the first row with value set last in previous columns (for 2 fixed rows)
var tmp = '';
for (j=0;j<data[0].length;j++)
if (data[0][j] != '')
tmp = data[0][j];
else
data[0][j] = tmp;
// for 2 fixed rows calculate unique column number
if (fixRows == 2)
{
uniqueCols = 0;
tmp = {};
for (j=fixColumns;j<data[1].length;j++)
if (typeof tmp[ data[1][j] ] == 'undefined')
{
tmp[ data[1][j] ] = 1;
uniqueCols++;
}
}
// return first row: fix column titles + pivoted values column title + values column title(s)
row = [];
for (j=0;j<fixColumns;j++) row.push(fixRows == 2 ? data[0][j]||data[1][j] : data[0][j]); // for 2 fixed rows we try to find the title in row 1 and row 2
for (j=3;j<arguments.length;j++) row.push(arguments[j]);
ret.push(row);
// processing rows (skipping the fixed columns, then dedicating a new row for each pivoted value)
for (i=fixRows; i<data.length && data[i].length > 0; i++)
{
// skip totally empty or only whitespace containing rows
if (data[i].join('').replace(/\s+/g,'').length == 0 ) continue;
// unpivot the row
row = [];
for (j=0;j<fixColumns && j<data[i].length;j++)
row.push(data[i][j]);
for (j=fixColumns;j<data[i].length;j+=uniqueCols)
ret.push(
row.concat([data[0][j]]) // the first row title value
.concat(data[i].slice(j,j+uniqueCols)) // pivoted values
);
}
return ret;
}
However, I have many many rows and columns, and my resulting table has too many rows to be output. There are many blanks in my data. I would like to skip writing rows that have blank values, as below:
Wide format:
Region Activity1 Activity2 Activity3
A 1 2
B 1
C 1
Desired long format:
Region ActivityName Frequency
A Activity1 1
A Activity3 2
B Activity2 1
C Activity1 1
I am currently using the code that is in the linked answer. The error I receipt is: "Error Result too large." My results would definitely not be too large if I could skip the blank values.
It seemed easier to re-create the script than to debug yours.
This script assumes that data starts in Cell A1.
The key elements
Create a two temporary arrays: one to hold row values, the other to hold the progressive compilation of row arrays
Loop through the rows and columns
Add a value set to the array each time the column value isn't blank
when finished, update the new array values
Don't forget to delete the "old" columns of data that are outside the columns of the "new" data
function so6049421501() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "Sheet1";
var sheet = ss.getSheetByName(sheetname);
// get data
var range = sheet.getDataRange();
// Logger.log("DEBUG: source range = "+range.getA1Notation())
var data = range.getValues();
// get number ofrows in the source data
var fixRows = range.getNumRows()-1;
var fixColumns = range.getNumColumns()-1;
// Logger.log("DEBUG: rows = "+fixRows+", columns = +fixColumns");
// create a new array to hold values
var newarray = [];
// set the number of columns in the new array
var numCols = 3;
// add header row
newarray.push(["Region","ActivityName","Frequency"]);
for (var r = 0;r<fixRows;r++){
for (var c=0;c<fixColumns;c++){
// create a new array to hold the values for this row
var rowarray = [];
// if column isn't blank
if (data[+r+1][+c+1] !==""){
// column isn't blank
// push the region
// Logger.log("DEBUG: region = "+data[r+1][0]);
rowarray.push(data[r+1][0])
// push the activity
// Logger.log("DEBUG: activity = "+data[0][+c+1]);
rowarray.push(data[0][+c+1])
// push the frequency
DEBUG: Logger.log("Frequency = "+data[+r+1][+c+1])
rowarray.push(data[+r+1][+c+1]);
//Logger.log(rowarray)
newarray.push(rowarray);
}
}
}
// Logger.log(newarray);
//Logger.log("DEBUG: new array len = "+newarray.length);
// Logger.log("DEBUG: target range = "+sheet.getRange(1, 1, newarray.length, numCols).getA1Notation());
// Update the new array
sheet.getRange(1, 1, newarray.length, numCols).setValues(newarray);
// delete the data in the unused columns
sheet.getRange(1,4,+fixRows+1,+fixColumns+1-numCols).clear()
return;
}