Loop script to copy and paste data, how to optimise - google-apps-script

I got a sheet with data automatically refreshed everyday. Here is the structure:
data1 data2 data3 data4 data5 etc...
a1 a2 a3 a4 a5 etc...
b1 b2 b3 b4 b5 etc...
c1 c2 c3 c4 c5 etc...
I wrote a script to save these data in another sheet everyday. here is my script :
function savedata() {
var sheet = SpreadsheetApp.openById('my_id').getSheetByName('Sheet1');
var numRows = sheet.getLastRow()-1;
for(var i = 0; i < numRows; i++) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2")
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
var data1 = sheet.getRange('Sheet1!A2:G2').getValues();
sheet.appendRow(data1[0]);
ss.deleteRow(2)
}
}
As you can understand I'm looping through all my row copying them and deleting them one by one until there nothing left. Everything is added to a second sheet.
Is there a more effective way to do this job ?

Indeed we can optimize this code.
Few points to keep in mind:
Always try to use inbuilt appscript functions first instead of looping as looping consumes more time than inbuilt functions on server side and ultimately your script will be slow when data becomes huge.
Also, in your for loop, you have written SpreadsheetApp.getActiveSpreadsheet(). It will get the spreadsheet every time the loop runs, you dont have to do that, declare them outside of the loop and still you will get the same result. Unless your data is getting modified runtime, you dont want to do that.
Also, I don't think your data1 variable is fetching new range for new iteration of for loop, I think it brings the same row values and you will write same data in multiple rows in your destination sheet.
Now, you can code something like this to get the similar result in efficient way:
var sheet1 = SpreadsheetApp.openById("ID1").getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.openById("ID2").getSheetByName("Sheet2");
var content = sheet1.getRange(1, 1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues(); //Get all the values starting from 1st row 1st column and till end of rows and columns
sheet2.getRange(1, 1,sheet1.getLastRow(), sheet1.getLastColumn()).setValues(content); //Get the same length and width to paste the data
sheet1.clear(); //You can put a condition here to make sure that all the data is copied successfully by checking if no. of rows in sheet1 and sheet2 is same
Also, there is difference between sheet1.clear() and sheet1.deleteRow(). If you use clear, it will just make the spreadsheet cells blank. If you use delete, it will delete the rows entirely and if you have only 5 rows, then after deletion, it will show you warning that you can't delete all the cells.
One more thing, delete fully blank rows and columns if you have no use of those as there is limit of 20 million cells in spreadsheets which includes cells in all the tabs.
EDIT
if(sheet2.getRange(1, 1).getValue().trim() == "")
{
var startIndex = 0; //I'm assuming your destination sheet is blank in first iteration
}
else
var startIndex = sheet2.getLastRow();
sheet2.getRange(startIndex+1, 1,sheet1.getLastRow(), sheet1.getLastColumn()).setValues(content);
Also one more important thing which I just added, see that I used .trim() in the if statement. The reason is, lets say you have entered 4-5 blank spaces by pressing spacebar in the A1 cell of destination sheet, then it will be considered as written row and getLastRow() will return 1 instead of any error. So, trim() here will remove all the white spaces of the A1 cell and check if it is still blank. It is very useful when you're checking for blank cells or values so that no once can bypass the code.

Related

Trying to copy a variable-length list

I'm trying to copy a list from Sheet A to sheet B. (I have the number of rows used by the data in the list saved in a cell, if needed.)
The spreadsheet file contains 5 sheets, of which "Analysis" is one. It contains 4-5 tables, each separated by a blank row.
The list itself is variable in length - sometimes it's 9 rows, sometimes its 17. Thankfully, it's always 5 columns wide.
Once I get the list into Sheet B, I need to set all cell borders to TRUE.
Here's the code that I've been playing with:
var sss =
SpreadsheetApp.openById(Sheet A);
var ss = sss.getSheetByName('Analysis'); //
var tblRows = sss.getRange('Analysis!K190').getValue();
var range = SpreadsheetApp.getRange('190,5,tblRows,5'); //assign the range you want to copy
var data = range.getValues();
var tss =
SpreadsheetApp.openById(Sheet B);
var ts = tss.getSheetByName('2018');
ts.getRange(ts.getLastRow()+1,1,tblrows,5).setValues(data);
(I'm not sure that this formatted properly)
Anyhow, I have the number of rows used by the list saved as a variable in a spreadsheet cell, and I'd like to use that, because I have found "getLastRow" to get the last row of the sheet, not the last row that is non-blank. (It might be giving me trouble because I'm doing something wrong, tho...) I know that I've messed up, so feel free to throw everything out and start over, if needed.
Anybody care to throw some ideas my way?

How could a dummy Browser.msgBox call affect output in GAS?

I have a script that moves data from one sheet to another and as part of this must insert the appropriate number of rows into the second sheet to account for the data from the first sheet. I'm running into an issue where, for reasons I don't quite understand, the data is duplicated in the second sheet, e.g. if 94 rows are in the original sheet, the second contains these 94 rows then an additional 94 rows right below with the exact same data. My code is:
function moveToEntrySheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var rawRow = 2;
var entrySheet = ss.getSheetByName('Sheet2');
var rawSheet = ss.getSheetByName('Sheet1');
var entryRow = 4;
var numEntries = rawSheet.getLastRow() - 1;
if (numEntries <= 0){
return;
}
Browser.msgBox(numEntries);
entrySheet.insertRowsBefore(entryRow, numEntries);
Browser.msgBox(numEntries);
var entryRange = entrySheet.getRange(4, 2, numEntries, 6);
var vals = rawSheet.getRange(2, 1, numEntries, 6).getValues();
Browser.msgBox(numEntries);
entryRange.setValues(vals);
}
To try and debug this I put calls to Browser.msgBox() in my code to make sure that numEntries is the expected number (94), which it does appear to be at each of the 3 points that I put the call in. The weird thing is that when I run this function with the calls to msgBox() included, the entries are only pulled to the other sheet once. If I keep the first call in it works the same, and if I keep the second or third call in double the number of rows that should be inserted are inserted but the data isn't copied itself, essentially giving n empty rows in the sheet, where n is the number of rows of data in sheet 1. How could calls to Browser.msgBox() affect the copy in this way, and how can I avoid the copy from duplicating? Thanks!

Paste as Value when cell is empty (google-apps-script)

Currently I am using the following script:
function moveValuesOnly() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getRange('Sheet1!C16:C16');
source.copyTo(ss.getRange('Sheet2!A1'), {contentsOnly: true});
source.clear();
}
However I want to append the pasted data in sheet2 with new data that is generated weekly.
So once a week sheet1 is updated and the value in 'Sheet1!C16' will change. I want to append this in 'Sheet2!A2'. and so forth.
So how can I edit this script to make sure the new value is copied in the next empty cell (so when 'Sheet2!A1' has a value get the new value from 'Sheet1!C16' and copy it to 'Sheet2!A2'. And when 'Sheet2!A1'is has a value, copy the new value from 'Sheet1!C16' to 'Sheet2!A3'
I need to to this for all weeks of the year (so just to be safe 53 weeks)
Thnx in advance!
It depends, you can do a few things.
The first one is the easiest, however there is a condition. If each week you copy to cell A1 → A2 → A3 and just keep going down, then each week, that data that you copy over is always the lowest. So for example if you wish to copy data to cell A3 that means that the last row to have any data in any column is row 2. That way all you need to do is get the range like this: ss.getRange(ss.getLastRow() + 1, 1)
The other method is to use PropertiesService to store which row is the last one in use. On script run, get that value
var nextRow = PropertiesService.getScriptProperties().getProperty('property we added before')
and then when you get the target range you use
ss.getRange(nextRow, 1)
PropertiesService.getScriptProperties().setProperty('property we added before', nextRow + 1)
that way you get the next row each time and you update the property for the next time you wish to run. With this you also need to keep in mind that you might want to reset the counter after the year.

google sheets form function not updating with data input from the script

I'm having some trouble getting a function in a google sheet to calculate using values inserted by a script.
I have a google script that is adding data to a sheet based on user-inputted data from a form that the script has created. So, in the form, a user inputs their name, selects a product and some options, and the script adds this to a sheet named 'Client Data Sheet'.
I then have a different sheet which is supposed to do the math to calculate the price. My script copies all the functions from a hidden template row into the next-available row, so, for example, cell D7 contains ='Client Data Sheet'!A4, D8 contains ='Client Data Sheet'!B4 etc... These all display the correct values.
The problem is the price calculation function, in that same calculation sheet, which has a rather complex function that a previous coder wrote. This function should calculate the price of the product and options based on the data in the same sheet. It does so without calling a script, just pulling data from cells in other sheets, running some if() checks to decide whether or not to add extra costs to the total price, and adding it all up.
Problem is, it doesn't update based on the new data. It just shows 0, as though the other cells were empty, even though they now contain data updated by pulling data from another sheet which was edited by a script. If I go in to edit the function and just press enter, it re-calculates correctly, so I basically just need the function to re-evaluate based on the new data that is in the cells it's dependent on.
My theory is that it's not updating the function since I didn't directly edit the cells it's dependent on. I could try to change the awkward, huge function this other coder wrote so it pulls from the spreadsheet my script edits, rather than from cells that copy in that data, but that seems like a workaround, and is unsatisfying.
TL;DR: a function isn't updating based on data that changes in cells that copy the data from other cells which are filled by a script. Anybody have any advice for how to get the function to update?
EDIT: Ok, so if I make sure that the function that isn't updating pulls at least some data from cells that the script updates, it works. It seems that it doesn't recognize that the cell once removed has updated as well. It would be better, though if it was able to pull from the cells that pull their data from the cells the script updates. This would let other users of the sheet change the products if a customer requested a change later on without having to edit my hidden sheets that the data is pulled from.
Code:
This copies a template row in my spreadsheet that contains the math functions:
function new_client(){
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Liquidation');
if(ss.getName()=="Data" || ss.getName().indexOf("Liq")==0){
var last_row = ss.getLastRow();
var last_col = ss.getLastColumn();
ss.insertRowsAfter(last_row, 1);
var template_row = 4;
var copy_range = ss.getRange(template_row, 1, 1, last_col);
var paste_range = ss.getRange(last_row+1, 1, 1, last_col);
copy_range.copyTo(paste_range, {contentsOnly:false});
paste_range.getCell(1,1).setValue("NO");
SpreadsheetApp.flush();
}
}
This copies data from the form into a different spreadsheet, which the sheet edited above pulls data from:
var clientSheet = SpreadsheetApp.getActive().getSheetByName('Client Data Sheet');
clientSheet.insertRowsAfter(clientSheet.getMaxRows(), 1)
var lastRow = clientSheet.getLastRow() + 1;
var lastColumn = clientSheet.getLastColumn();
var destRow = clientSheet.getRange(lastRow, 1, 1, lastColumn);
var column = 1;
for (var key in user) {
if (user.hasOwnProperty(key)) {
destRow.getCell(1, column).setValue(user[key]);
column++;
}
}
So, for example, after this code is run, the Client Data Sheet contains a cell, B4, which now contains the name of the chosen product, "foo". The Liquidation sheet has a cell let's call it A5 that contains the function ='Client Data Sheet'!B4, as well as another cell which has the price calculation function: =if(A5="foo", 100, 0)
When the script above inserts the values from the form, the cell B4 in Client Sheet and the cell A5 in the Liquidation sheet will contain the right value, but the cell with the calculation function =if(B4="foo", 100, 0) will not update.

using query() with apps script keeps adding 500 rows to sheet

I'm working on a large sheet and cells are precious given gsheets quota.
I have a range, and when data updates automatically a script updates the named range automatically to be the full length of the data.
The named range is called "gadatapull". This range is on the tab "datapull".
Two tabs. "datapull" is where fresh data is dumped and "data_prep" is where I do stuff to the data. After a fresh pull just now datapull has 2,733 rows of data, including the headers.
I would like data_prep to have the same length as datapull. Plus 7 rows for text at top of data_prep.
When my script to update data runs I do this:
// clear dataprep sheet for new data
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
data_prep has 7 rows (because the script just deleted all rows above 7).
Now, in data_prep cell A7 I have:
=query(indirect("gadatapull"),"select *")
Expected result was that all the fresh data in "gadatapull" would appear in data_prep tab and that data_prep tab would expand accordingly.
But what actually happens is all the data arrive as expected, but then there are an additional blank 500 rows at the bottom. This 500 number is too rounded off. Makes me think Gsheets is automatically adding this number as a default under some condition.
How can I prevent Gsheets from adding these additional 500 rows?
Instead of letting the sheet API expanding the number of rows (which is your hypothesis and might well be true ;-) you can add all the necessary cells before importing data.
I didn't try in real conditions but this should work.
Btw, the script imports data as well.
code :
function copyDataToSheet(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataprep = ss.getSheetByName('data_prep');
var datapull = ss.getSheetByName('datapull');
var lastRow = 7;
var maxRows = dataprep.getLastRow();
if(maxRows - lastRow > 0) {
dataprep.deleteRows(lastRow+1, maxRows-lastRow);
}
var datapullSize = datapull.getLastRow();
dataprep.insertRows(7,datapullSize);// insert exactly the number of rows you need.
var dataToCopy = datapull.getDataRange().getValues()
dataprep.getRange(7,1,dataToCopy.length,dataToCopy[0].length).setValues(dataToCopy);
}