Optimizing google apps script for replacing/appending values - google-apps-script

I have a function that loops through array C:D to find a match in A:B, if there is it replaces the value in B with D and if there's no match it appends C:D to A:B. This function is using loops. I know there's a way to optimize this, but I'm lost. How else can this script run without loops?
function moveValues() {
var ss = SpreadsheetApp.openById('open_id');
var source = ss.getRange('sheet2!D:C');
var destination = ss.getRange('sheet2!A:B');
var destCount = 0;
for (var j = 1; j <= destination.getLastRow(); j++) {
if (destination.getCell(j,1).getValue() == "") {
destCount = j;
break;
}
}
for (var i = 1; i <= source.getLastRow(); i++) {
Logger.log(source.getLastRow());
var added = false;
var targetName = source.getCell(i,1).getValue();
var copyValue = source.getCell(i,2).getValue();
if (targetName == "") {
break;
}
for (var j = 1; j <= destCount; j++) {
var curName = destination.getCell(j,1).getValue();
if (copyValue != "" && targetName == curName) {
destination.getCell(j, 2).setValue(copyValue);
added = true;
break;
}
}
if (!added) {
destination.getCell(destCount, 1).setValue(targetName);
destination.getCell(destCount, 2).setValue(copyValue);
destCount += 1;
}
}
source.clear();
};

You will still need to use loop(s), but the code can be optimized. Use getValues() at the beginning. That returns a 2D array. You can use .indexOf() to determine whether there is a match in the other array.
function moveValues() {
var i,L,sh,ss,srcRng,destRng,srcData,targetData,v;
ss = SpreadsheetApp.openById('open_id');
sh = ss.getSheetByName('sheet2');//Get sheet2
lastRow = sh.getLastRow();//Get the row number of the last row
srcRng = sh.getRange(1,1,lastRow);//Get the range for all the values in column 1
destRng = sh.getRange(3,1,lastRow);//Get the range for all the values in column 3
srcData = srcRng.getValues();//Get a 2D array of values
targetData = destRng.getValues();//Get a 2D array of values
srcData = srcData.toString().split(",");//Convert 2D to 1D array
targetData = targetData.toString().split(",");//Convert 2D to 1D array
L = srcData.length;
for (i=0;i<L;i++) {//Loop the length of the source data
v = srcData[i];//Get this value in the array
if (targetData.indexOf(v) !== -1) {//This value was found in target array
}
}
This is not a complete answer to your question, but hopefully it will give you some ideas.
In this example the code is getting just the columns of data to compare, and not the columns of data to change.

Related

Reduce script execution time - Google script

I made a script that works properly (does what I want it to), however, it's painfully slow and at this pace, it will finish in about 20 days. I can't wait for 20 days and I'm not good enough at this to make it faster on my own.
Here's a brief description of the task:
Masterlist - it's a sheet with 23 columns and 29000+ rows.
Seed - it's an empty sheet that I'm to copy the Masterlist to.
Duplicates - it's an empty sheet where I will store any duplicate rows.
The process:
Get the first line from Masterlist. Check if line already in Seed. If line not in Seed, add line. If line already in Seed, add line to Duplicates. Either way, delete the original line from the Masterlist.
The definition of duplicate:
Each line has an emails column. Column can be either a single email address, or multiple email addresses separated by "; ". If an email is found within line in Masterlist and already exists within line in Seed, this whole line is considered a duplicate.
Example:
"aaa#gmail.com" is not a duplicate of "a#gmail.com; aa#gmail.com"
"bbb#gmail.com" is a duplicate of "b#gmail.com; bbb#gmail.com"
Furthermore, if the emails cell is empty in the Masterlist, this is not considered a duplicate.
Here comes my code - it works but is not fast enough.
function getSheet(name){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
return sheet;
}
function getRowByID(sheet, rowID) {
var range = sheet.getRange(rowID, 1, 1, 23);
var value = range.getValues();
return [range, value];
}
//main executes the entire thing
function main(){
var sourceSheet = getSheet('Masterlist');
var targetSheet = getSheet('Seed');
var remainingSheet = getSheet('Duplicates');
var counter = sourceSheet.getLastRow();
var start = new Date();
while(counter >= 2){
var sourceLine = getRowByID(sourceSheet, 2)[1];
var duplicates = checkEmailMatch(sourceLine, targetSheet);
if(duplicates == 0){
targetSheet.appendRow(sourceLine[0]);
sourceSheet.deleteRow(2);
}
else{
remainingSheet.appendRow(sourceLine[0]);
sourceSheet.deleteRow(2);
}
counter--;
}
}
//iterates through existing lines in the Seed sheet (locates the email cell and reads its contents)
function checkEmailMatch(row, seed){
var sourceEmail = row[0][7];
var counter = seed.getLastRow();
var result = [];
if(!counter){
return 0;
}
else{
var j = 0;
var i = 2;
for(i; i <= counter; i++){
var seedLine = getRowByID(seed, i)[1];
var seedEmail = seedLine[0][7];
if(!seedEmail){}
else if(compareEmails(seedEmail, sourceEmail) == true) {
result[j] = i;
j++;
}
}
return result;
}
}
//Compares each email in Masterlist ("; " separated) with each email in Source ("; " separated)
function compareEmails(emailSeedCell, emailSourceCell){
var seedEmails = emailSeedCell.split("; ");
var sourceEmails = emailSourceCell.split("; ");
for(var i = 0; i < seedEmails.length; i++){
for(var j = 0; j < sourceEmails.length; j++){
if(seedEmails[i] == sourceEmails[j]) return true;
}
}
return false;
}
Please help me - if you need any additional info, I'd be happy to provide! Please note that this is my third script ever, so any feedback is welcome!
Thanks to everyone who chipped in to help, I managed to come up with this code that reduced the execution time more than 10000 times! Thanks, everyone - here's the code:
function sheetToArray(name){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
var counter = sheet.getLastRow();
var columns = sheet.getLastColumn();
var array = sheet.getRange(2, 1, counter, columns).getValues();
return array;
}
function compareEmails(emailSeedCell, emailSourceCell){
var seedEmails = emailSeedCell.split("; ");
var sourceEmails = emailSourceCell.split("; ");
var result = false;
for(var i = 0; i < seedEmails.length; i++){
for(var j = 0; j < sourceEmails.length; j++){
if(seedEmails[i] == sourceEmails[j]) result = true;
}
}
return result;
}
function save2DArrayToSpreadsheet(name, array){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
sheet.getRange(2, 1, array.length, array[0].length).setValues(array);
}
function main(){
var masterArray = sheetToArray('Masterlist');
var seedArray = [];
var duplicateArray = [];
for(var i = 0; i < masterArray.length; i++){
Logger.log(i);
if(!seedArray.length){
seedArray.push(masterArray[i]);
}
else if(!masterArray[i][7]){
seedArray.push(masterArray[i]);
}
else{
var result = false;
for(var j = 0; j < seedArray.length; j++){
if(compareEmails(seedArray[j][7], masterArray[i][7]) == true){
result = true;
}
}
if(result == true){
duplicateArray.push(masterArray[i]);
}
else{
seedArray.push(masterArray[i]);
}
}
}
save2DArrayToSpreadsheet("Seed", seedArray);
save2DArrayToSpreadsheet("Duplicates", duplicateArray);
}

Code takes 2 minutes to paste values on another tab. How to optimize it?

I've done this code on Google Script, but it's taking way too long (it completes 10-15 lines in 30 minutes. I don't know how to optimize it more. Could the size of the spreadsheet be influencing it's processing? If not, how do I change it to improve?
function PreenchePlanilhaFinal() {
var App = SpreadsheetApp;
App.getActiveSpreadsheet().getSheetByName('MacroHelp').getRange(1,1).activate();
var helpMacro = App.getActiveSpreadsheet().getActiveSheet(); //aba que vc está ativo
var lastLine = helpMacro.getLastRow();
for (var i = 214; i < lastLine; i++){
if (helpMacro.getRange(i, 17).getValue() == "")
{
var regionCode = helpMacro.getRange(i, 5).getValue();
var nomeAba = ""; //inicializo a variável da região
for (var j = 1; j < lastLine; j++){
if (regionCode == helpMacro.getRange(j, 20).getValue()){
nomeAba = helpMacro.getRange(j, 21).getValue();
break;
}
}
var email = helpMacro.getRange(i,1).getValue();
var aba = App.getActiveSpreadsheet().getSheetByName(nomeAba);
aba.getRange(1,1).activate(); //ativo a aba
var lastLineNovaAba = aba.getLastRow();
for (var k = 1; k <= lastLineNovaAba; k++){
if (email == aba.getRange(k, 8).getValue()){
App.getActiveSpreadsheet().getActiveSheet().getRange(k, 31, 1, 11).setValues(helpMacro.getRange(i, 6, 1, 11).getValues());
}
}
helpMacro.getRange(i, 17).activate().setValue("Feito");
}
}
}
You are trying to optimise script function PreenchePlanilhaFinal().
Execution time is affected by many getValue statements; each of these (particularly when repeated in a loop) can be quite costly. The solution is, where possible, to 1) getValues() once only and 2) do this outside the loop.
The following script is untested, but it demonstrates the basic methodology.
function PreenchePlanilhaFinal() {
var App = SpreadsheetApp;
App.getActiveSpreadsheet().getSheetByName('MacroHelp').getRange(1,1).activate(); // get MacroHelp A1
var helpMacro = App.getActiveSpreadsheet().getActiveSheet(); //aba que vc está ativo
var lastLine = helpMacro.getLastRow();
// new line to get last column
var lastColumn = helpMacro.getlastColumn();
// new line to declare start line as a variable
var startRow = 214;
// define the range and get values
var helpMacroData = helpMacro.getRange(startRow,1,lastLine-startRow+1,lastColumn).getValues();
// declare aba outside the loop, and define the range and get values
var aba = App.getActiveSpreadsheet().getSheetByName(nomeAba);
var abaLR = aba.getlastRow();
var abaLC = aba.getLastColumn();
var abaData = aba.getRange(1,1,abaLR,abaLC).getValues();
// note i = 0 since the values start on line 214
for (var i = 0; i < lastLine; i++){
if (helpMacroData[i][16] == "") // describe variable as array value
{
var regionCode = helpMacroData[i][4];// describe variable as array value
var nomeAba = ""; //inicializo a variável da região
for (var j = 0; j < lastLine; j++){ // set j to 0 since arrays are zero-based
if (regionCode == helpMacroData[0][19]){// describe variable as array value
nomeAba = helpMacroData[j][20];// describe variable as array value
break;
}
}
var email = helpMacroData[i][0];// describe variable as array value
aba[0][0].activate(); //ativo a aba //// describe variable as array value
for (var k = 0; k < abaLC; k++){ set k to 0 since arrays are zero-based, also make "<" not "<="
if (email == aba[k][7]){ // describe variable as array value
App.getActiveSpreadsheet().getActiveSheet().getRange(k, 31, 1, 11).setValues(helpMacro.getRange(i, 6, 1, 11).getValues());
}
}
helpMacro.getRange(i, 17).activate().setValue("Feito");
}
}
}

Google Script to Match cell value and copy Paste related data in different tab

Click here for Sample Sheet
I need a solution that matches a cell value (Sheet1! Q5) to a range in another tab/sheet (NegotiationData! A1:O1) and paste the relevant data fetched from the first sheet under the designated columns of the second sheet under the matched value.
For example, if Sheet1!Q5 matches with the name in NegotiationData! A1 then do the following
Fetch Sheet1! R6 and paste in NegotiationData!A3:A
Fetch Sheet1! Q6 and paste in NegotiationData!B3:B
Fetch Sheet1! Q7 and paste in NegotiationData!C3:C
Also, each time the script runs it should not overwrite data but find the next empty row and paste the values.
I have an incomplete script that I'm trying to achieve the above from my research from various posts but since I'm just a week old to coding I'm not able to go any further than where I have got with the below script.
I'm not finding how to match the value and fetch the relevant data and paste them below the matched value.
Please help!
The Incomplete / Incorrect Script (File Name: NegotiationSubmit)
function submitNegotiation() {
var sh, id, v, estNum, negotiation, negoNotes, i;
sh = SpreadsheetApp.getActive();
id = sh.getSheetByName('Sheet1').getRange('Q5').getValue();
v = sh.getRange('R6').getValue();
estNum = Number(sh.getRange('Q6').getValue().split(" ")[1]);
negoNotes = sh.getRange('Q7').getValue();
negotiation =sh.getSheetByName('NegotiationData').getRange('A1:O');
if(v && estNum) {
negotiation.setValues(negotiation.getValues()
.map(function (r, i) {
if (r[0] == id) {
r[1] = v;
r[2] = estNum;
r[3] = negoNotes;
}
return r;
})
)
}
}
How about this modification?
Modification points :
Retrieve values of "Q5:R7" at once, and the values are converted to the import values.
Use the destructuring assignment for retrieving each value.
Import the converted values using the number of column retrieved by ids[0][i] == id.
Modified script :
function submitNegotiation() {
var id, estNum, v, negoNotes;
var sh = SpreadsheetApp.getActive();
var values = sh.getSheetByName('Sheet1').getRange("Q5:R7").getValues();
[id] = values[0];
[estNum, v] = values[1];
[negoNotes] = values[2];
// estNum = Number(estNum.split(" ")[1]); // If you want to use "estNum = Number(sh.getRange('Q6').getValue().split(" ")[1]);", please use this line.
var sh2 = sh.getSheetByName('NegotiationData');
var ids = sh2.getRange("A1:O1").getValues();
for (var i=0; i<ids[0].length; i++) {
if (ids[0][i] == id) {
sh2.getRange(sh2.getLastRow() + 1, i + 1, 1, 3).setValues([[v, estNum, negoNotes]]);
}
}
}
Note :
I was confused to the following points.
In your script, estNum is Number(sh.getRange('Q6').getValue().split(" ")[1]);. But in your sample spreadsheet, Estimate 1 of cell "Q6" is used.
I commented this in modified script.
In your sample spreadsheet, "Story ID" is 1. But in your script, it's US-001 of cell "R6".
In this modified script, US-001 of cell "R6" was used.
If I misunderstand your question, I'm sorry.
Edit :
function submitNegotiation() {
var id, estNum, v, negoNotes;
var sh = SpreadsheetApp.getActive();
var values = sh.getSheetByName('Sheet1').getRange("Q5:R7").getValues();
[id] = values[0];
[estNum, v] = values[1];
[negoNotes] = values[2];
estNum = Number(estNum.split(" ")[1]); // If you want to use "estNum = Number(sh.getRange('Q6').getValue().split(" ")[1]);", please use this line.
var sh2 = sh.getSheetByName('NegotiationData');
var ids = sh2.getRange("A1:O1").getValues();
for (var i=0; i<ids[0].length; i++) {
if (ids[0][i] == id) {
var temp = sh2.getRange(1, i + 1, sh2.getLastRow(), 3).getValues();
for (var j=temp.length-1; j>=0; j--) {
if (temp[j].join("") != "") {
sh2.getRange(j + 2, i + 1, 1, 3).setValues([[v, estNum, negoNotes]]);
break;
}
}
}
}
}
I realize that this does not resemble your code. Sorry about that. I'm learning too. But I'm putting it up anyway to provide an alternative method that includes finding the last row of the appropriate column...
function submitNegotiation() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('Sheet1');
var negotiationData = ss.getSheetByName('NegotiationData');
// Sheet1 variables
var user = sheet1.getRange('Q5').getValue();
var storyId = Number(sheet1.getRange('R6').getValue().split("-")[1]);
var estimateNum = sheet1.getRange('Q6').getValue();
var note = sheet1.getRange('Q7').getValue();
var pointers = [storyId, estimateNum, note];
// NegotiationData variables
var range = negotiationData.getDataRange().getValues();
var columns = negotiationData.getLastColumn();
var users = negotiationData.getRange(1, 1, 1, columns).getValues();
for(var i = 0; i < columns; i++) {
// match user with users to get column number
if(users[0][i] == user) {
var col = negotiationData.getRange(1, i + 1).getColumn();
// count rows in col
var rowCount = 1;
for(var i = 1; i < range.length; i++) {
if (range[i][col - 1] != "") {
rowCount++;
}
}
// assign pointers
var newRow = rowCount + 1;
for(var j = 0; j < pointers.length; j++) {
negotiationData.getRange(newRow, col, 1, 1).setValue(pointers[j]);
col++;
}
}
}
}

Get row & column indices of found value

How do I get the row and column indices of the cell containing a value I'm looking for?
Here's an example of two sheets, "Grave" and "Data_grave":
My code, below, should...
First, get a specific value in sheet "Grave" (in example value is - "Win").
Find the number of the row & column with this value in sheet "Data_grave".
Finally, it should write some data ("wow") near the found value "Win" (from column+1).
However, I receive an error message at line 17 (the line following my search loops):
Can't convert 4,4 to (class)
How do I solve that?
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
for(var i=1, iLen=findData.length; i<iLen; i++) {
for(var j=0, jLen=searchData.length; j<jLen; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen; k++) {
var find = findData;
if(find == searchData[j][k]) {
rowNum.push([j+1]);
collNum.push([k+2]);
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}
As Adelin commented: the error message is indicating that you are not using .getRange(rowNum,collNum) properly. That method expects two numbers, but you're providing it two arrays.
When you've "found" the cell you're searching for, instead of push() (which treats rowNum and colNum as arrays), you simply want to use:
var rowNum = j+1;
var colNum = k+2;
You could also use a boolean found as an additional exit condition for all your loops, to stop searching upon success.
function myFind() {
var ss = SpreadsheetApp.getActive(), rowNum = [], collNum = [];
var findData = ss.getSheetByName('Grave').getRange("A2").getValue();
var searchData = ss.getSheetByName('Data_grave').getDataRange().getValues();
var found = false;
for(var i=1, iLen=findData.length; i<iLen && !found; i++) {
for(var j=0, jLen=searchData.length; j<jLen && !found; j++) {
for(var k=0, kLen=searchData[0].length; k<kLen && !found; k++) {
var find = findData;
if(find == searchData[j][k]) {
var rowNum = j+1;
var collNum = k+2;
found = true;
}
}
}
}
ss.getSheetByName('Data_grave').getRange(rowNum,collNum).setValue("wow");
}

How to split single rows into multiple rows depending on whether one cell has values separated by commas or new lines in Google Sheet?

The spreadsheet has some cells with cell contents that are separated by commas, or a new line.
123, 876, 456
Column "C" is the column that determines whether a row should be split up into multiple rows.
EXAMPLE Spreadsheet
Information from the Form goes into the "Form Submission" page.
We have a specific format that we must meet to submit to our report tracking software that requires the issue numbers (found in Column C) to be separated into their own rows with the information found in Columns A:B, D:J remaining the same (see Desired Outcome sheet).
I found a similar question and we implemented it into our Google Sheets.
This script requires, on a separate sheet, the function =result('FormSubmission'!A2:J) to be placed in the first column / row that we wish the data to be displayed (see "Current Outcome" sheet, Cell A2.)
Here is the coding that we are using:
function result(range) {
var output2 = [];
for(var i = 0, iLen = range.length; i < iLen; i++) {
var s = range[i][2].split("\n");
for(var j = 0*/, jLen = s.length; j < jLen; j++) {
var output1 = [];
for(var k = 0, kLen = range[0].length; k < kLen; k++) {
if(k == 2) {
output1.push(s[j]);
} else {
output1.push(range[i][k]);
}
}
output2.push(output1);
}
}
return output2;
}
function results(range) {
var output2 = [];
for(var i = 0 /, iLen = range.length; i < iLen; i++) {
var s = range[i][2].split(",");
for(var j = 0 /, jLen = s.length; j < jLen; j++) {
var output1 = []/;
for(var k = 0, kLen = range[0].length; k < kLen; k++) {
if(k == 2 /) {
output1.push(s[j]);
} else {
output1.push(range[i][k]);
}
}
output2.push(output1);
}
}
return output2;
}
If someone submits multiple issue numbers separated by commas in the form, the row needs to be split up into multiple rows, as shown in the Desired Outcome sheet.
Here is some code that I tested, and it works. It also works for cells that have both new lines, and comma separated values. It does not required that the range be passed in. . . . . . Don't need that for this code. It writes the new rows directly to the 'Current Outcome' sheet.
function result() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var frmSubmissionSheet = ss.getSheetByName('Form Submission');
var desiredOutcomeSheet = ss.getSheetByName('Current Outcome');
var data = frmSubmissionSheet.getRange(1, 1, frmSubmissionSheet.getLastRow(), frmSubmissionSheet.getLastColumn()).getValues();
var issueNumbers = "",
hasComma = false,
arrayOfIssueNumbers = [],
arrayRowData = [],
thisRowData,
hasNewLine;
for (var i=0;i<data.length;i+=1) {
if (i===0) {continue}; //Skip row 1
issueNumbers = data[i][2];
hasComma = issueNumbers.indexOf(",") !== -1;
hasNewLine = issueNumbers.indexOf("\n") !== -1;
Logger.log(hasNewLine)
if (!hasComma && !hasNewLine) {
desiredOutcomeSheet.appendRow(data[i]);
continue; //Continue to next loop, there are no multiple issue numbers
};
if (hasNewLine) {
var arrayNewNewLine = issueNumbers.split("\n");//Get rid of new line
issueNumbers = arrayNewNewLine.toString(); //Back to string. Handles cells with both new line and commas
};
arrayOfIssueNumbers = [];
arrayOfIssueNumbers = issueNumbers.split(",");
for (var j=0;j<arrayOfIssueNumbers.length;j+=1) {
arrayRowData = []; //Reset
thisRowData = [];
thisRowData = data[i];
for (var k=0;k<thisRowData.length;k+=1) {
arrayRowData.push(thisRowData[k]);
};
arrayRowData.splice(2, 1, arrayOfIssueNumbers[j]);
desiredOutcomeSheet.appendRow(arrayRowData);
};
};
};
In addition to Sandy's contribution, here is some alternative code:
function extract(range, colToSplit, delimiter) {
var resArr = [], row;
range.forEach(function (r) {
r[colToSplit-1].replace(/(?:\r\n|\r|\n)(\d|\w)/g,", ").split(delimiter)
.forEach(function (s) {
row = [];
r.forEach(function (c, k) {
row.push( (k === colToSplit-1) ? s.trim() : c);
})
resArr.push(row);
})
})
return resArr;
}
This is a custom function that takes three arguments:
the range
the column on which the splitting should be based
the delimiter to split by
can be used in the spreadsheet like so:
=extract('Form Submission'!A1:J, 3, ", ")
The code subsitutes all new line characters (that are followed by a digit or a letter) to comma's and splits (based on column 3) using the comma as a delimiter.
I hope that helps ?