copy one row from one sheet to another - google-apps-script

I know this has been asked before because I have spent the last 5 hours trying them all from here and even the closed google product forum. I can't get any of the loops to stop. The conditions are getting met, but the loop keep running and running. I have been copying from other examples online and inserting my own variables, but in the end, I'll have 5 rows go to the new sheet several times until the sheet gets tired or something.
I've tried getDataRange...I've tried getRange...I've tried var in loops and other loops, I've tried them all, so please take a look and let me know how this code keeps resulting in rows getting added to the new sheet multiple times over:
function runReportAllMemCos1() {
var sheet1 = sskey.getSheetByName('Businesses');
var sheet2 = sskey.getSheetByName('tempsheet');
var data = sheet1.getRange(1,1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues();
var dest = [];
for (var i = 0; i < data.length; i++ ) {
if (data[i][12] == "Associate") {
dest.push(data[i]);
} Logger.log(data)
if (dest.length > 0 ) {
sheet2.getRange(sheet2.getLastRow()+1,1,dest.length,dest[0].length).setValues(dest);
}
}
}
Thanks for any help!

I don't really agree with user1795832's answer as it doesn't make use of batch writing but simply writes data to sheet 2 each time the condition is true... so why using an array to do that ?
Try simply to batch write to sheet 2 after the loop is completed and it should work as expected.
function runReportAllMemCos1() {
var sheet1 = sskey.getSheetByName('Businesses');
var sheet2 = sskey.getSheetByName('tempsheet');
var data = sheet1.getRange(1,1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues();
var dest = [];
for (var i = 0; i < data.length; i++ ) {
Logger.log(data[i][12]);// just to check if the condition is true sometimes ;-)
if (data[i][12] == "Associate") {
dest.push(data[i]);
}
} // here is the end of the for loop
Logger.log(dest) ; // log the dest array instead
if (dest.length > 0 ) { // if something has been written in your array then batch write it to the dest. sheet
sheet2.getRange(sheet2.getLastRow()+1,1,dest.length,dest[0].length).setValues(dest);
}
}
EDIT : other possibility following your comment to choose the columns you copy
function runReportAllMemCos1() {
var sheet1 = sskey.getSheetByName('Businesses');
var sheet2 = sskey.getSheetByName('tempsheet');
var data = sheet1.getRange(1,1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues();
var dest = [];
for (var i = 0; i < data.length; i++ ) {
Logger.log(data[i][12]);// just to check if the condition is true sometimes ;-)
if (data[i][12] == "Associate") {
var destRow = []; // initialise intermediate array
destRow.push(data[i][1],data[i][2],data[i][12]);// choose here the columns you want to add (here col2, 3 & 13)
dest.push(destRow);
}
} // here is the end of the for loop
Logger.log(dest) ; // log the dest array instead
if (dest.length > 0 ) { // if something has been written in your array then batch write it to the dest. sheet
sheet2.getRange(sheet2.getLastRow()+1,1,dest.length,dest[0].length).setValues(dest);
}
}

Yep, the problem is your dest variable. You aren't resetting it after each loop, so each row just keeps getting compounded in there and duplicates each time (so when looping through the third row, rows 1 and 2 are still in the dest variable). Put "dest = [];" just inside your for loop and it'll work.
function runReportAllMemCos1() {
var sheet1 = sskey.getSheetByName('Businesses');
var sheet2 = sskey.getSheetByName('tempsheet');
var data = sheet1.getRange(1,1, sheet1.getLastRow(), sheet1.getLastColumn()).getValues();
var dest = [];
for (var i = 0; i < data.length; i++ ) {
dest = [];
if (data[i][12] == "Associate") {
dest.push(data[i]);
} Logger.log(data)
if (dest.length > 0 ) {
sheet2.getRange(sheet2.getLastRow()+1,1,dest.length,dest[0].length).setValues(dest);
}
}
}

Related

Google script to copy a newly added row from one sheet and append to another only if it doesn't exist in the second sheet

I have a google sheet (Sheet 1) which gets auto-populated by a script. Now I want to copy the newly added rows to Sheet 2 only if doesn't exist in Sheet 2. I am trying to match column 1 from both the sheets and if it doesnt exist in sheet 2, the row should be appended in sheet 2. I do not have much experience in google script. Here is my code.
var s1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Original');
var s2 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Filtered');
var values1 = s1.getDataRange().getValues();
var values2 = s2.getDataRange().getValues();
var resultArray = [];
for(var n=0; n < values1.length ; n++)
{
var keep = false;
var counter = 0;
for(var p=0; p < values2.length ; p++)
{
if( values1[n][1] == values2[p][1])
{
keep = true;
break ;
}
}
if(keep == false)
{
resultArray.push(values1[n]);
s2.appendRow();
resultArray = [];
keep = false
}
}
}
You need to pass an argument to appendRow function. You currently append nothing. See here for an example.
var aRow = ["col1", "col2", "col3"];
SpreadsheetApp.getActiveSheet().appendRow( aRow );
On your end, you should have a array which is populated with the values you want to copy for a particular row. If that row does not exist in sheet2, then add it there.
https://developers.google.com/apps-script/reference/spreadsheet/sheet#appendRow(Object)

How to store data in Array using For loop in Google apps script - pass array by value

Edit: answer at the bottom
Im experiencing some weird behavior I came across when using google script.
I have a 2d array and I insert data into it using for loop.
I noticed that if use
appendRow(someArray[i])
INSIDE the for loop, everything works as expected.
But If try to access data or use appendRow outside of the for loop, it always gives me the data of the last row the ran in the for loop.
so:
appendRow(someArray[1])
gives same result as
appendRow(someArray[2]),appendRow(someArray[3])
when used OUTSIDE of the for loop.
Can anyone tell me whats causes it?
It also happens when im using setValue on a 2d array, I can use it outside of the loop, or all of the rows are identical.
I have spent 2 hours on this simple little thing, and finally understand what causing the problem but I still cant figure out how to fix it.
Im attaching a simple code that explains what the problem, please focus on the second FOR loop.
function myFunctionAppendRowInside() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = sheet.getDataRange().getValues();
var newRow = data[5];
var arr = new Array(100,100);
var currentId = 20000;
var productSku = data[5][2];
for (var i = 0; i < 99; i++){
arr[i] = newRow
}
for (var i = 0; i < 99; i++){
target.getRange(targetRow,1).setValue(evento[i]);
arr[i][0] = currentId + i;
arr[i][2] = productSku + i;
sheet.appendRow(arr[i]);
}
//All of them gives the same row, which is the one created in the last run of the FOR loop arr[98]
sheet.appendRow(arr[1]);
sheet.appendRow(arr[2]);
sheet.appendRow(arr[3]);
}
Please explain to me whats causes it and how to overcome it.
Edit : added my code which uses "setValues" , but still experiencing the same problem. My array is populated only with the LAST row created by the "for loop"
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var newSheet = activeSpreadsheet.insertSheet();
newSheet.setName("newSheet");
var data = sheet.getDataRange().getValues();
//Taking the 5th row, using it as a template for rest of rows
var newRow = data[5];
//2d Array
var arr = new Array(100,100);
//data for the only 2 columns who are going to change between rows
var currentId = 20000;
var productSku = data[5][2];
for (var i = 0; i < 99; i++){
newRow[0] = currentId + i;
newRow[2] = productSku + i;
arr[i] = newRow;
}
newSheet.getRange(2, 1, arr.length, arr[0].length).setValues(arr);
}
Second Edit:
So the issue was that "getValues()" returns an array, which I needed to work with.
But Array are passed by reference and not by value, so any changes I made along the code, where changing the original array which I got from "getValues".
The solution:
iterate over the array received from "getValues", and copy the values one by one(cell by cell) to the a new array and only then manipulate it.
I also created a 2d array, which also requires running a "for loop"
Im attaching the working code which does:
1.copy row 13, which includes 51 columns from my original sheet.
2.create an empty 2d array (9999x51).
3. take row 13 and manipulate its columns based of current iteration (ie row '1' will include original data + '1'
The code :
function myFunction() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var data = sheet.getDataRange().getValues();
//create Array
var arr = [];
//turn it to a 2 dimension array
for (var i=0;i<10000;i++) {
arr[i] = [];
}
//starting point for our new id which will run from 30000-39999
var currentId = 30000;
//run on our 2 dimension array and manipulate data cell by cell
for (var i=0; i <= 9999; i++){
for (var j=0; j<= data[13].length - 1; j++){
if (j == 0){
var obj = currentId + i;
arr[i][j] = obj;
}else{
if (j == 2){
arr[i][j] = data[13][j] + i;
}else{
arr[i][j] = data[13][j];
}
}
}
}
//copy to new sheet
var newSheet = activeSpreadsheet.insertSheet();
newSheet.setName("newSheet466");
newSheet.getRange(1, 1,10000, 51).setValues(arr);
}
I'm not sure what causes your problem. But instead of appending each row (which can get very slow) it is faster to get a range and set its value. For example:
sheet.getRange(1, 1, arr.length, arr[0].length).setValues(arr);
Where the two 1's are the starting position that you wish to use. For more info check out the documentation on getRange.

Hiding/unhiding named ranges: Improve script process time?

Below is a script I'm using to automate the hiding/unhiding of groups of rows in Google Sheets.
Col B contains drop-down menus that allow the user to toggle hiding or unhiding rows according to named ranges. However, the script is a bit laggy. Here is a sample spreadsheet where you can see for yourself.
I haven't found a more efficient approach than creating four different arrays in Google Apps Script...but any advice on how to speed up this script would be greatly appreciated!
(NB: The script operates with a "From spreadsheet" and "On edit" trigger.)
function hideRows() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName("Sheet");
//create array of spreadsheet's named ranges
var namedRanges = ss.getNamedRanges();
var named = [];
for (var i = 0; i < namedRanges.length; i++) {
named.push(namedRanges[i].getName());
}
var arrName = named.sort();
//create array of named range values
var value = [];
for (var i = 0; i < arrName.length; i++) {
value.push(ss.getRangeByName(arrName[i]).getValue());
}
//create array of named range row indexes
var index = [];
for (var i = 0; i < arrName.length; i++) {
index.push(ss.getRangeByName(arrName[i]).getRowIndex());
}
//create array of named range row numbers
var rows = [];
for (var i = 0; i < arrName.length; i++) {
rows.push(ss.getRangeByName(arrName[i]).getNumRows());
}
for (var i = 0; i < arrName.length; i++) {
//If a value in value is equal to "Collapsed" then hide the corresponding named range
if (value[i] == "Collapsed") {
sheet.hideRows(index[i+1],rows[i+1]);
}
//If a value in value is equal to "Expanded" then show the corresponding named range
else if (value[i] == "Expanded") {
sheet.showRows(index[i+1],rows[i+1]);
}
}
}
I didn't test this. I rewrote the code, and consolidated three of the loops into one loop. The code could be consolidated more, depending upon whether you need to further use the data for something else. For example, you are sorting the range names. If it's not really needed to have them in order, then the code could be consolidated further.
function HideRows() {
var arrName,i,index,L,named,namedRanges,
rngByName,rngNumRows,rngRowIndex,rngVal,rows,
sheet,ss,thisRng,thisRngName,value;
ss = SpreadsheetApp.getActive();
sheet = ss.getSheetByName("Sheet");
named = [];
value = [];
index = [];
rows = [];
//create array of spreadsheet's named ranges
namedRanges = ss.getNamedRanges();
L = namedRanges.length;
for (i = 0; i < L; i++) {
named.push(namedRanges[i].getName());
}
arrName = named.sort();
Logger.log(arrName);
//create array of named range values
L = named.length;
for (i = 0; i < L; i++) {
thisRngName = arrName[i];
thisRng = ss.getRangeByName(thisRngName);
rngVal = thisRng.getValue();
rngRowIndex = thisRng.getRowIndex();
rngNumRows = thisRng.getNumRows();
value.push(rngVal);
index.push(rngRowIndex);//create array of named range row indexes
rows.push(rngNumRows);//create array of named range row numbers
//If a value in arrValue is equal to "Collapsed" then hide the corresponding named range
if (rngVal == "Collapsed") {
sheet.hideRows(index[i+1],rows [i+1]);
}
//If a value in arrValue is equal to "Expanded" then show the corresponding named range
else if (rngVal == "Expanded") {
sheet.showRows(index[i+1],rows [i+1]);
}
}
}

Google script - Exceeded maximum execution time , help optimize

google script spreadsheet
Novice
I try to create a matrix , if the array is a small database everything works fine, of course if it exceeds 800 lines and more rests on the error "You have exceeded the maximum allowed run time ." Not effectively create a matrix :
var s = SpreadsheetApp.getActiveSheet(); //List
var toAddArray = []; //Greate Arr
for (i = 1; i <= s.getLastRow()+1; ++i){ //Start getting Value
var numbr = s.getRange(i,4); //detect range
var Valus = numbr.getValues().toString(); //get value
//filter value
var newznach = Valus.replace(/\-/g, "").replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
toAddArray.push([i.toFixed(0),Valus,newznach]); //add to array 0- Row numb, 1- Value, 2- "filtered" value
}
toAddArray =
{
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
Row, Value, NewValue - filtered
...
}
Can I somehow get an array of the same the other way ( faster, easier ) ?
You're doing a call to getValues every row, that eats a lot of performance.
It is better to do one big call to have all the data and then go through it sequentially.
var s = SpreadsheetApp.getActiveSheet();
var data = s.getRange(1,4, s.getLastRow()).getValues();
var toAddArray = data.map(function(row, i) {
var Valus = row[0].toString();
var newznach = Valus.
replace(/\-/g, "").
replace(/[0-9][0-9][0-9][0-9][0-9][a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "").
replace(/[a-zA-Zа-яА-Я][a-zA-Zа-яА-Я]/g, "");
return [i.toFixed(0), Valus, newznach];
});
this code:
var Valus = numbr.getValues().toString();
slows you down because you read data from the sheet in a loop.
Try reading data once into array and then work with it:
var data = s.getDataRange().getValues();
And then work with data, in a loop. This sample code log each cell in active sheet:
function logEachCell() {
var s = SpreadsheetApp.getActiveSheet();
var data = s.getDataRange().getValues();
// loop each cell
var row = [];
for (var i = 0; i < data.length; i++) {
row = data[i];
for (var j = 0; j < row.length; j++) {
Logger.log(row[j])
}
}
}

Apps Script: Construct range of rows from array of row numbers

I have a list of row numbers in a spreadsheet which I need to change the background colour of. As the spreadsheet is quite large (10+ sheets, each with almost 5000 rows), I am trying to construct a range so I can batch set the background, as doing each row individually was taking over the max time of 6 minutes.
Here's the code I have:
// highlight required rows
var first = -1, last = -1;
for(var j = 0; j < rowNumsToHighlight.length; j++) {
if(first == -1) {
first = rowNumsToHighlight[j];
continue;
}
// if the current row number is one more than the previous, update last to be the current row number
if(rowNumsToHighlight[j] - 1 == rowNumsToHighlight[j - 1]) {
last = rowNumsToHighlight[j];
continue;
}
// otherwise the last row should be the previous one
else {
last = rowNumsToHighlight[j - 1];
}
var numRows = (last - first) + 1;
var range = sheet.getRange(first, 1, numRows, 4);
if(range.getBackground().toUpperCase() != highlightColour.toUpperCase()) {
range.setBackground(highlightColour);
}
first = -1;
last = -1;
}
rowNumsToHighlight is just an array that looks like: [205,270,271,272,278,279]. So, with that as an example, setBackground should be ran on row 205, on rows 270-272, and on 278-279.
I'm fairly sure the solution is simple, but just can't see it. Thanks for any help.
==== Updated Code ====
Based on Serge's code below, I made it more efficient again by reducing the number of getRange() calls made. Time is down from 78 to 54 seconds.
function updateColours(sheet, array, colour){
var columns = sheet.getLastColumn();
var rows = sheet.getLastRow();
var range = sheet.getRange(1, 1, rows, columns);
Logger.log("Resetting highlight on all rows...");
range.setBackground(null);
var backgrounds = range.getBackgrounds();
for(var n = 0; n < backgrounds.length; n++){
var rowIdx = n + 1;
if(array.indexOf(rowIdx) > -1){
for(var c = 0; c < columns; c++){
backgrounds[n][c] = colour;
}
}
}
Logger.log("Highlighting non-translated rows...");
range.setBackgrounds(backgrounds);
}
Maybe this one is faster(?) and built in a way that will make your work easier (function with arguments).
It writes only once to the sheet (or 2 if you clear colors before writing)...
use like below :
function testBG(){
updateColors(0,[7,8,9,18,19,23]);
}
function updateColors(sheetNum,array){
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheets()[sheetNum];
var columns = sh.getMaxColumns();
var range = sh.getRange(1,1,sh.getMaxRows(),columns);
sh.getRange(1,1,sh.getMaxRows(),columns).setBackground(null);// use this if you want to clear all colors before setting them
var backGrounds = range.getBackgrounds();// get all cells BG
for(var n=0;n<backGrounds.length;n++){
var rowIdx = n+1;
if(array.indexOf(rowIdx)>-1){
for(c=0;c<columns;c++){
backGrounds[n][c]="#F00";// if row number is in the array fill in red
}
}
}
sh.getRange(1,1,sh.getMaxRows(),columns).setBackgrounds(backGrounds);//update sheet in one call
}
test sheet in view only, make a copy to test.
This is how I would do it:
function createRanges() {
var rowNumsToHighlight = [5,7,8,9,18,19];
var arrayLength = rowNumsToHighlight.length;
var loopCounter = 0, thisNumberInArray=0, nextNumberInArray=0, crrentNmbrPlusOne=0;
var currentRangeBegin=0, numberOfRowsInRange=1;
currentRangeBegin = rowNumsToHighlight[0];
for(loopCounter=0; loopCounter < arrayLength; loopCounter+=1) {
thisNumberInArray = rowNumsToHighlight[loopCounter];
nextNumberInArray = rowNumsToHighlight[loopCounter+1];
crrentNmbrPlusOne = thisNumberInArray+1;
if (nextNumberInArray===undefined) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
return;
};
if (nextNumberInArray!==crrentNmbrPlusOne) {
workOnTheRange(currentRangeBegin, numberOfRowsInRange);
numberOfRowsInRange = 1; //Reset to 1
currentRangeBegin = nextNumberInArray;
} else {
numberOfRowsInRange+=1;
};
};
};
function workOnTheRange(first,numRows) {
var range = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet11').getRange(first, 1, numRows, 4);
range.setBackground("red");
};
I've tested the code and it works.