Inserting rows with data upon finding certain value in Column B - google-apps-script

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 `${}`

Related

Get row values by column name match

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.

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 Sheets Add row based on cell number value

I'm trying to make a google sheet script that adds a row based on cell value, basically if I have in the Quantity (Column D) 7x laptops, I want the script to add 6 additional rows below if Column H is marked as "Yes" through data validation.
What I was able to find and to do is only duplicate that row but is without data validation and I would prefer to add the data validation and possible make each quantity split to 1 (instead of 7) after the duplication.
`function autoDup() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
var newData = [];
for(var n in data){
newData.push(data[n]);
if(!Number(data[n][3])){continue};// if column 3 is not a number then do nothing
for(var c=1 ; c < Number(data[n][3]) ; c++){ // start from 1 instead of 0 because we have already 1 copy
newData.push(data[n]);//store values
}
}
sheet.getRange(1,1,newData.length,newData[0].length).setValues(newData).sort({column: 1, ascending: false});// write new data to sheet, overwriting old data
}`
Hope someone is able to help me.
Thank you,
Column D contains a qty and goods description. If Column H = "Yes", you want to insert a number of rows below Col D equal to the qty minus one. If Column H <> "Yes, then take no action.
Sample data - Before
Sample data - After
function so5925663201() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "59256632";
var sheet = ss.getSheetByName(sheetname);
var row = 7;
// get value of Column H
var colHValue = sheet.getRange(row,8).getValue();
if (colHValue === "Yes"){
//Logger.log("DEBUG: Col H = yes. do something")
// get value of Column D
var Value = sheet.getRange(row,4).getValue();
var searchterm = "x";
var indexOfFirst = Value.indexOf(searchterm);
//Logger.log("DEBUG: the first instance of 'x' is "+indexOfFirst);
// get the quantity and convert from a string to a number
var qty = Value.substring(0, indexOfFirst);
var qtynum = +qty;
// var newtype = typeof qtynum; // DEBUG
//Logger.log("DEBUG: the quantity is "+qtynum+", new type = "+newtype)
// This inserts rows after
sheet.insertRowsAfter(row, qtynum-1);
}
else{
//Logger.log("DEBUG: col H <> Yes. do nothing");
}
}

Range selection and copying fixed value (from a cell) in that selection

Background
I have some code which is supposed to copy certain rows from Sheet B into Sheet A based on integer values in cells E1,J1 and I1. E1 has date format. After rows are copied from Sheet B to A, I need to fill column 12 (Column L) with the date from E1 to newly added rows.
https://docs.google.com/spreadsheets/d/15pTVfcoxM2wQTMC-3iLzXVXIEEaZFYXaOf97amy4yRg/edit?usp=sharing
Problem
The last three rows of code is not working well. Even though I am trying to select range for same column 12 (column L), it seems to select multiple columns and an additional 2 rows than what I had expected.
function test() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("B");
var aa = sheet.getRange("E1");
var Date = aa.getValue();
var aa = sheet.getRange("J1");
var lastrow = aa.getValue();
var aa = sheet.getRange("I1");
var lastrowV = aa.getValue();
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("A");
var range = sheet.getRange(2, 1, lastrowV, 11);
var data = range.getValues();
sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("B");
sheet.getRange(lastrow, 1, data.length, 11).setValues(data); /* cell J1 gets updated after this*/
var aa = sheet.getRange("J1");
var lastrowN = aa.getValue() - 1;
range = sheet.getRange(lastrow, 12, lastrowN, 12);
range.activate();
sheet.getRange(lastrow, 12, lastrowN, 12).setValues(Date);
}
Background
The OP is attempting to insert a given date in the cell at the end of a row. However, the OP's definition of the range is faulty because it is selecting multiple columns (when only one column is required) and the number of rows is greater (by 2 (two)) than the number required. In addition, regardless of the range height, the OP is attempting to set a single value (rather than an array) into the range.
Problems
1) The definition of the datecolumn (Column L) included a value for the number of columns (probably a carry over from having defined the data range earlier).
Old range: getRange(lastrow,12, lastrowN, 12);. Delete the last parameter (number of columns) and the code behaves.
2) The code used this method setValues(Date) to populate the date column (8 rows in the OP's example data). the problem here is that the value assigned is the single value Date. not an array. This was addressed by creating and populating a temporary array datearray, and using this to update values in the date column.
3) In addition to the problems noted, the OP code is problematic in that a number of variables names were re-used with entirely different contexts (including "sheet" and "aa"), and some variables were declared multiple times. This made the code hard to read and debug. I took the opportunity to resolve as many of these as possible.
function so5473808801() {
// setup spreadsheet and sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetB = ss.getSheetByName("B");
var sheetA = ss.getSheetByName("A");
// define key variables
// date
var daterange = sheetB.getRange("E1");
var datevalue = daterange.getValue();
// rows on SheetA
var Arows = sheetB.getRange("I1"); // = 9
var Alastrow = Arows.getValue();
// rows on sheet B
var Brows = sheetB.getRange("J1"); // = 3
var Blastrow = Brows.getValue();
// define the data range on Sheet A
var Adatarange = sheetA.getRange(2, 1, Alastrow, 11);
// Logger.log("DEBUG: The defined range on Sheet A is "+Adatarange.getA1Notation());//DEBUG
var Adatavals = Adatarange.getValues();
// define a target range on Sheet B and set values from A
var targetrange = sheetB.getRange(Blastrow, 1, Adatavals.length, 11);
// Logger.log("DEBUG: The target on sheetB = "+targetrange.getA1Notation()); // DEBUG
targetrange.setValues(Adatavals);
// set a range to update date on Sheet B
var daterows = (Alastrow - 1); // doesn't take 2 row header on B intoi account
var Bdaterange = sheetB.getRange(Blastrow, 12, daterows);
// Logger.log("DEBUG: The date range on sheet B = "+Bdaterange.getA1Notation());
// create an array to store multiple copies of datevalue
var datearray = [];
//populate the array
for (var i = 0; i < daterows; i++) {
datearray.push([datevalue]);
}
// set the date into Column L
Bdaterange.setValues(datearray);
}

Transforming csv data for analysis and visualization

Say I have a csv file with following data format:
ID, Name, Gender, Q1
1, ABC, Male, "A1;A2"
2, ACB, Male, "A2;A3;A4"
3, BAC, Female, "A1"
I would like to transform it into following format so that my data virtualization tool can process it properly:
ID, Name, Gender, Questions, Responses
1, ABC, Male, Q1, A1
1, ABC, Male, Q1, A2
2, ACB, Male, Q1, A2
2, ACB, Male, Q1, A3
2, ACB, Male, Q1, A4
3, BAC, Female, Q1, A1
Using Text to Columns feature in LibreOffice I can easily separate Q1 column A1;A2 into different columns like A1, A2, but I am stuck at transposing and repeating rows.
Additional Info:
Data is collected via Google Form, unfortunately google spreadsheets store multiple choice question responses in one cell using semicolon-separator like A1;A2;A3..., while my visualization tool cannot see this underlying data structure, only treat them as a single string, making aggregation/grouping difficult.
In the actual data (survey results) I have around 5000 entries, each with multiple cells that require such processing, which will result in a table of around 100,000 entries. A way to automate the transformation is needed.
The tool I use to analyze/visualize data is "Tableau Public", they have a data reshaper plugin for Excel that semi-automate such tasks (see section Make sure each row contains only one piece of data), but no LibreOffice alternative.
You can use JavaScript on Google Spreadsheet to transform the data before exporting to other applications. Here is a quick-and-dirty script I just wrote for your sample data:
function transformRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("Result");
var header = values[0].slice(0, values[0].length - 1);
header.push("Question");
header.push("Answer");
newSheet.appendRow(header);
var question = values[0][values[0].length - 1];
// Note: Code below is inefficient and may exceed 6-minute timeout for sheets with
// more than 1k rows. Change it to batch updating to speed up.
// Ref: https://developers.google.com/apps-script/reference/spreadsheet/range#setValues%28Object%29
for (var i = 1; i <= numRows - 1; i++) {
var row = values[i];
var answers = row[row.length - 1].split(";");
for (var ansi = 0; ansi < answers.length; ansi++) {
var newRow = row.slice(0, row.length - 1);
newRow.push(question);
newRow.push(answers[ansi]);
newSheet.appendRow(newRow);
}
}
};
To use it:
Open script editor in your opened sheet (Tools -> Script editor...)
Create a empty project for spreadsheet
Paste the code into the editor
Save, and run it (Run -> transformRows)
Return to the spreadsheet, a new sheet will be created and filled with transformed data.
I made a more general purpose version of #SAPikachu's answer. It can convert any number of data columns, assuming that all the data columns are to the right of all the non-data columns. (Not the clearest terminology...)
function onOpen() {
var ss = SpreadsheetApp.getActive();
var items = [
{name: 'Normalize Crosstab', functionName: 'normalizeCrosstab'},
];
ss.addMenu('Normalize', items);
}
/* Converts crosstab format to normalized form. Given columns abcDE, the user puts the cursor somewhere in column D.
The result is a new sheet, NormalizedResult, like this:
a b c Field Value
a1 b1 c1 D D1
a1 b1 c1 E E1
a2 b2 c2 D D2
a2 b2 c2 E E2
...
*/
function normalizeCrosstab() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var firstDataCol = SpreadsheetApp.getActiveRange().getColumn();
var dataCols = values[0].slice(firstDataCol-1);
if (Browser.msgBox("This will create a new sheet, NormalizedResult. Place your cursor is in the first data column.\\n\\n" +
"These will be your data columns: " + dataCols,Browser.Buttons.OK_CANCEL) == "cancel") {
return;
}
var resultssheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("NormalizedResult");
if (resultssheet != null) {
SpreadsheetApp.getActive().deleteSheet(resultssheet);
}
var newSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("NormalizedResult");
var header = values[0].slice(0, firstDataCol - 1);
var newRows = [];
header.push("Field");
header.push("Value");
newRows.push(header);
for (var i = 1; i <= numRows - 1; i++) {
var row = values[i];
for (var datacol = 0; datacol < dataCols.length; datacol ++) {
newRow = row.slice(0, firstDataCol - 1); // copy repeating portion of each row
newRow.push(values[0][firstDataCol - 1 + datacol]); // field name
newRow.push(values[i][firstDataCol - 1 + datacol]); // field value
//newSheet.appendRow(newRow);
newRows.push(newRow);
}
}
var r = newSheet.getRange(1,1,newRows.length, header.length);
r.setValues(newRows);
};