How to speed up COLUMNS deleting function in Google App Script - google-apps-script

I am working through data in Google sheets, attempting to delete EMPTY COLUMNS based I have 600 so it take too much time
Any suggestions on how to speed this up?
Here is my code :
function delCols() {
var s = SpreadsheetApp.getActive().getActiveSheet()
var c = 600
var data = s.getRange(1, 1, 1, c).getValues()
Logger.log(data[0].length)
var delCol=0
for (var i = data[0].length; i >0 ; i--) {
if(s.getRange(1, c ).getValue() =="") { s.deleteColumn(c);delCol=delCol+1 }
c=c-1
}
s.insertColumns(600-delCol, delCol);
Logger.log(delCol)
}
Thank you in advance if you may help

Create a new sheet, for example "Sheet0", for intermediate data. It can be hidden.
Run this script function to remove empty columns from the active sheet:
function removeEmptyColumns() {
var ss = SpreadsheetApp.getActive();
var activeSheet = ss.getActiveSheet(),
sheet0 = ss.getSheetByName('Sheet0');
// AHG is 600-th column (if I am not mistaken)
var formula = "=TRANSPOSE(QUERY(TRANSPOSE('" + activeSheet.getName() +
"'!A:AHG); \"select * where Col1 is not null\"; 0))";
sheet0.getRange('A1').setFormula(formula);
SpreadsheetApp.flush();
var values = sheet0.getDataRange().getValues();
sheet0.getRange('A1').clear();
activeSheet.clearContents();
activeSheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}
I believe it is faster, than column by column deletion. Some remarks:
There is a restriction for use of formulas on the active sheet, because we copy only values.
If you do not remove the formula in the script, then calculations will take place on every active cheet change, resulting in delays.
Please note, empty column detection is based on top cell value, as in your code.

Related

Use app script to insert more than one row into a sheet on button click

I have a code written to insert a single row into a google sheet below a keyword (a little clunky but I don't mind, unless anyone has a fix for it off the top of their head). Problem is, I want to add 250 rows beneath that and, being new to app scripts, I'm not sure where to specify the number of rows I want it adding for me.
Here's what the code I've got looks like:
function addRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var EditSheet = ss.getSheetByName("Testing"); //source sheet
var LastRowEdit = ss.getLastRow();
for(var i = 1; i <= LastRowEdit; i++)
{
if(EditSheet.getRange(i,1).getValue() == 'add') //keyword
{
EditSheet.insertRowAfter(i);
}
}
}
I would love to hear your suggestions for how I can adjust this to match my goals.
Thank you, guys!
In your situation, how about using insertRowsAfter(afterPosition, howMany) instead of insertRowAfter(afterPosition)? So, how about the following modifcation?
From:
EditSheet.insertRowAfter(i);
To:
EditSheet.insertRowsAfter(i, 250);
By the way, in your script, getValue() is used in a loop. In this case, the process cost becomes high. In order to reduce the cost, how about the following modification?
Modified script:
function addRows() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var EditSheet = ss.getSheetByName("Testing");
var LastRowEdit = ss.getLastRow();
var row = EditSheet.getRange("A1:A" + LastRowEdit).getValues().map(([a]) => a).indexOf("add");
if (row != -1) {
EditSheet.insertRowsAfter(row + 1, 250);
}
}
Reference:
insertRowsAfter(afterPosition, howMany)

How to get all checked cells from checkboxes in google apps script?

I have a goolesheets that contains checkboxes. I would like to get all checked cells using google apps script. For example, as below picture, I would like to get a result of 'A3', 'A6' and 'A9'.
I tried many different ways and I found a script below but it only gives me the last cell, which is 'A9'.
Any ideas or suggestions would be much appreciated.
var range = '';
var sel = SpreadsheetApp.getActive().getSelection().getActiveRangeList().getRanges();
for(var i = 0; i < sel.length; i++){
range += sel[i].getA1Notation() + ', ';
}
Logger.log(range);
Explanation:
Your current solution uses getSelection which means if you select the desired cells manually:
you will get A3, A9, A6, which based on your code is the correct answer. Although, I guess you don't want to manually select them, otherwise it wouldn't make sense to use a script to find them.
Side Note:
If you want to use that solution, I assume you want A3, A9, A6 instead of A3, A9, A6, so this would make more sense:
function myFunction() {
const sel = SpreadsheetApp.getActive().getSelection().getActiveRangeList().getRanges();
const range = [];
for(var i = 0; i < sel.length; i++){
range.push(sel[i].getA1Notation());
}
Logger.log(range.join(', '));
}
assuming again you have selected the checkboxes as in the screenshot.
Solution:
In the below solution, adjust Sheet1 to the name of your sheet.
function myFunction() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet1'); // put the name of your sheet here
const colA = sh.getRange('A1:A'+sh.getLastRow()).getValues().flat();
const range=colA.map((c,i)=>c==true?`A${i+1}`:'').filter(c=>c!='').join(', ');
Logger.log(range); // A3, A6, A9
}
In this solution, we are getting all the values of column A, then using map we get the cell reference of the true cells (checkbox is clicked) and then filtering on these values.
I tried replicating your scenario and did some changes in your code specifically in the declaration of the sheet and data range to be checked. For the solution, I changed your loop and added a condition inside to check for true(ticked checkbox) values.
Please see my code below. This is working fine on my end. Note that this is assuming that you only have your checkboxes on column A.
function myFunction() {
var range = '';
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Sheet1');
var val = sheet.getRange(1,1,sheet.getLastRow(), 1).getValues();
for(row in val){
if(val[row][0]==true){
range += 'A'+ (parseInt(row) + 1) + ',';
}
}
Logger.log(range);
}

How to increment simple formula by 1 in Google Apps Script?

Disclaimer: I'm a Google Apps Script newbie.
I'm trying to create a timesheet in Google Sheets that lets a user clock in & clock out to log hours on a given project. I've borrowed code from a YouTube video on the general structure of setting the whole thing up.
Here's what the blank time sheet looks like. It's pretty basic:
I've created a user button (off to the right) where the user presses "Start" and cell A2 will input a timestamp. Then the user can press an "End" button, and a second timestamp, this time in B2, will appear, along with a simple calculation in C2 that measures the delta in the two timestamps, thus giving a duration of time spent on a given task or project. Here's what it looks like:
When the user needs to press "Start" again, a new timestamp appears in cell A3, and so on so forth, along with a new delta calculation for each new row.
Problem: I'm unable to get the simple delta calculation in column C to increment down each new rows so that the setFormula function doesn't contain hardcoded references to cells A2 & B2. See below code for what I have so far:
function setValue(cellName, value) {
SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).setValue(value);
}
function getValue(cellName) {
return SpreadsheetApp.getActiveSpreadsheet().getRange(cellName).getValue();
}
function getNextRow() {
return SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
}
function addStartRecord (a) {
var row = getNextRow();
setValue('A' + row, a);
}
function addEndRecord (b, c) {
var row = getNextRow()-1;
setValue('B' + row, b);
setValue('C' + row, c);
}
function punchIn() {
addSRecord(new Date());
}
function punchOut() {
addERecord(new Date(), '=B2-A2');
}
The problem is with the punchOut() function there at the bottom. Any idea on the best way to increment this delta calculator down each new row?
Note: I saw a pretty good answer to a similar question here, but the code is throwing an error in the script editor after the line containing data[i] = ['=A' + i+1.toString() + ' + 1 ' ]. Also, I don't want to set a definitive last row for the delta calculation (such as 20 in this example). I'd want the user to be able to record as many new start/end times for a project as they'd want.
Edit: Here's a link to the timesheet so you can test the code.
Try modifying your punchOut method like this:
function punchOut() {
var ss = SpreadsheetApp.getActiveSheet();
var row = ss.getLastRow();
addEndRecord(new Date(), '=B' + row + '-A' + row);
}
I tested it in the sheet and it worked well.
setFormula() - this enables you to describe the formula to be inserted into column C.
The following is two simple functions that handle "Punch in" and "Punch Out" (with its calculation).
function so5695101401in() {
// punchin
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
// Logger.log("DEBUG: the last row is "+lR);
var punchinRange = sheet.getRange(lR+1, 1);
// Logger.log("DEBUG: the punchinRange = "+punchinRange.getA1Notation());
punchinRange.setValue(new Date());
}
function so5695101401out() {
// punchout
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var lR = sheet.getLastRow();
//Logger.log("DEBUG: the last row is "+lR);
var punchoutRange = sheet.getRange(lR, 2);
// Logger.log("DEBUG: the punchoutRange = "+punchoutRange.getA1Notation());
punchoutRange.setValue(new Date());
var timeElapsed = sheet.getRange(lR, 3).setNumberFormat("hh:mm:ss");
timeElapsed.setFormula("=B2-A2");
}
setFormula
I use a workaround for this problem, via app script copy the cell with the formula to de new row or range!.
for you problem:
var formula1 = sheetDatos.getRange(lastRow, 3); //get the formula
var copyRange = sheetDatos.getRange(lastRow+1, 3);
formula1.copyTo(copyRange);
for me is more easy in this way, try to do in sheet to understand how this work.
you need a initial formula to go in this way ;)

Improving Apps Script flexibility by using a column of sheet data instead of hard-coded IDs

Background: My coworkers originally each had a worksheet within the same Google Sheets file that makes a lot of calculations (and was getting unusable). Now, everyone has their own (known) Google Sheets file. To run the same calculations, we need to consolidate all that data into a master sheet (image ref below). We tried =importrange(...), but it's too heavy and breaks often (i.e., Loading... and other unfilled cells).
I've written some code to do this import, but right now its only manual: manually repeating the code and manually add the sheet IDs and changing the destrange.getRange(Cell range) each time. We have 80+ analysts, and fairly high turnover rates, so this would take an absurd amount of time. I'm new to Sheets and Apps Script, and know how to make the script use a cell as reference for a valid range or a valid ID, but I need something that can move a cell down and reference the new info.
Example:
Sheet 1 has a column of everyone Sheet ID
Script Pseudocode
get first row's id(Row 1), get sheet tab, get range, copies to active sheet's corresponding row(Row 1).
gets second row's id(Row 2), get sheet tab, get range, copies to active sheet's corresponding row (Row 2)
etc.
My script understanding is way to low to know how to process this. I have no idea what to read and learn to make it work properly.
function getdata() {
var confirm = Browser.msgBox('Preparing to draw data','Draw the data like your french girls?', Browser.Buttons.YES_NO);
if(confirm == 'yes'){
// I eventually want this to draw the ID from Column A:A, not hard-coded
var sourcess = SpreadsheetApp.openById('1B9sA5J-Jx0kBLuzP5vZ3LZcSw4CN9sS6A_mSbR9b26g');
var sourcesheet = sourcess.getSheetByName('Data Draw'); // source sheet name
var sourcerange = sourcesheet.getRange('E4:DU4'); // range
var sourcevalues = sourcerange.getValues();
var ss = SpreadsheetApp.getActiveSpreadsheet(); //
var destsheet = ss.getSheetByName('Master Totals'); //
// This range needs to somehow move one down after each time it pastes a row in.
var destrange = destsheet.getRange('E4:DU4');
destrange.setValues(sourcevalues); // Data into destsheet
}
}
Any suggestions are greatly appreciated!
Thanks to tehhowch for pointing me in the right direction!
function getdata() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var destsheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Master Totals');
var confirm = Browser.msgBox('Drawing Data','Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO);
if(confirm =='yes'){
var lr = ss.getLastRow();
for (var i = 4; i<=lr; i++) {
var currentID = ss.getRange(i, 1).getValue();
var sourcess = SpreadsheetApp.openByUrl(currentID);
var sourcesheet = sourcess.getSheetByName('Data Draw');
var sourcerange = sourcesheet.getRange('E4:DU4');
var sourcevalues = sourcerange.getValues();
var destrange = destsheet.getRange('E' +i+':'+ 'DU'+ i);
destrange.setValues(sourcevalues);
I just had to learn how to use a variable loop.
Edit: thanks also to Phil for making my question more presentable!
Now that you've figured out one way to do it, I'll offer an alternative that uses batch methods (i.e. is much more time- and resource-efficient):
function getData() {
var wb = SpreadsheetApp.getActive();
var ss = wb.getActiveSheet();
var dest = wb.getSheetByName('Master Totals');
if (!dest || "yes" !== Browser.msgBox('Drawing Data', 'Would you like to update the sheet? It may take 2 to 5 minutes.', Browser.Buttons.YES_NO))
return;
// Batch-read the first column into an array of arrays of values.
var ssids = ss.getSheetValues(4, 1, ss.getLastRow() - 4, 1);
var output = [];
for (var row = 0; row < ssids.length; ++row) {
var targetID = ssids[row][0];
// Open the remote sheet (consider using try-catch
// and adding error handling).
var remote = SpreadsheetApp.openById(targetID);
var source = remote.getSheetByName("Data Draw");
var toImport = source.getRange("E4:DU4").getValues();
// Add this 2D array to the end of our 2D output.
output = [].concat(output, toImport);
}
// Write collected data, if any, anchored from E4.
if(output.length > 0 && output[0].length > 0)
dest.getRange(4, 5, output.length, output[0].length).setValues(output);
}
Each call to getRange and setValues adds measurable time to the execution time - i.e. on the order of hundreds of milliseconds. Minimizing use of the Google interface classes and sticking to JavaScript wherever possible will dramatically improve your scripts' responsiveness.

How can I effeciently move a range of values and formulas from one column to another using a google sheets script?

I am trying to move the contents of column D to column A and keep them as formulas or values. The code below works but it takes FOREVER!!
I used this answer to put the values and formulas into an array:
How do I copy a row with both values and formulas to an array?
I used this suggestion to separate them out based on their type:
https://productforums.google.com/forum/#!topic/docs/JtcH-U3qC7s
var ss = SpreadsheetApp.getActiveSheet();
var formulas = ss.getRange("D2:D").getFormulas();
var values = ss.getRange("D2:D").getValues();
var merge = new Array(formulas.length);
for( var i in formulas ) {
merge[i] = new Array(formulas[i].length);
for( var j in formulas[i] )
merge[i][j] = formulas[i][j] !== '' ? formulas[i][j] : values[i][j];
}
for (k=0;k<merge.length;k++){
var rowRange = ss.getRange("A2");
var str = merge[k].toString();
var formulaChecker = str.substring(0,1);
if (formulaChecker == "="){
rowRange.offset(k, 0).setFormula(merge[k]);
}else{
rowRange.offset(k, 0).setValue(merge[k]);
}
}
Because it runs so slowly I feel like I missed something.
Is there a way to make it more efficient and run faster?
Try this:
function copyBoth() {
var sh = SpreadsheetApp.getActiveSheet();
var dataLngth = sh.getLastRow();
var rngCol_D = sh.getRange(2, 4, dataLngth, 1); //Set range for source - Example is column D
var formulas = rngCol_D.getFormulas(); //Get all the forumlas from the source range
var values = rngCol_D.getValues();
var rngCol_A = sh.getRange(2, 1, values.length, 1); //Set range for destination - Example is column A
rngCol_A.setFormulas(formulas);//Write all the formulas to the destination:
//Write all the values, individually to each cell.
var i = 0, thisFormula;
for (i=0;i<dataLngth;i+=1) {
thisFormula = formulas[i][0];
//Logger.log('thisFormula: ' + thisFormula);
//Logger.log('typeof thisFormula: ' + typeof thisFormula);
if (thisFormula === "") {
sh.getRange(i+2, 1).setValue(values[i][0]);//Write the individual value to the single cell
};
};
};
This code is very different from the code you are using. It sets all the formulas first. This eliminates the need to write every single cell. You still need to write values to individual cells that are in between the formulas, and that is done at the end. This example writes all the formulas at once, then the values one by one. But you could do it the opposite way. For example, if there are more values than formulas, it might save a couple of milliseconds.
This strategy also eliminates the need to merge the data, which is probably taking a lot of time.