Optimizing .gs search & replace script? - google-apps-script

Hoping someone could just point out my probably obvious mistakes.
Code searches a Data page & copies matched info to a Results page. This is fine & works quickly.
What happens is when I add an overwrite section to the script. It overwrites matched entries as "Done" on the data page, so if the Data page gets added to, the previous entries won't match again.
I have 2 variants I've tried that work but are quite slow...as in you can sit & watch each match change to "Done" once every few seconds.
Appreciate any insight.
Here's the code:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var data = sheet1.getRange(2,1,lr-1,2).getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
// Slow Overwrite-----------------------------------
/*
if(i>0){
var temp1 = sheet1.getRange(i,2).getValue();
var temp2 = sheet1.getRange(i+1,2).getValue();
var temp3 = sheet1.getRange(i+2,2).getValue();
// Testing1
if(temp1 == key ){
sheet1.getRange(i,2).setValue("Done");
}
if (i== lr-2){
if (temp2 == key){
sheet1.getRange(i+1,2).setValue("Done");
}
if (temp3 == key){
sheet1.getRange(i+2,2).setValue("Done");
}
}
}
*/
}
// Shorter code but even slower overwrite----------------
/*
for(var i=1; i<=lr; i++){
var temp1 = sheet1.getRange(i,2).getValue();
if(temp1 == key){
sheet1.getRange(i,2).setValue("Done");
}
}
*/
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Link to the Test Sheet

Solution:
The script runs slow because there are API calls in for loops. Best practice is to manipulate only the array inside the loop and do the API calls before and after the loop.
In your code it would look like this:
function cellMatch() {
// Sheet Import
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName("Raw_Data"); // Data
var sheet2 = ss.getSheetByName("Pallet_Data");// Result
var sheet3 = ss.getSheetByName("Close");// Search
// Data Import
var lr = sheet1.getLastRow();
var datarange = sheet1.getRange(2,1,lr-1,2);
var data = datarange.getValues();
var lc = sheet2.getLastColumn()+1;
var key = sheet3.getRange("A2").getValue();
var matched = [["Pallet "+lc+" ("+key+")"]];
//Start
for (var i=0; i<lr-1; i++) {
if (data[i][1] == key) {
var temp = [];
temp.push(data[i][0]);
matched.push(temp);
data[i][1] = "Done";
}
// Location Update ---------------------------------
var A7 = sheet3.getRange("A7");
A7.setValue(matched);
// Data Write -----------------------------------------
datarange.setValues(data);
var result = sheet2.getRange(1,lc,matched.length);
result.setValues(matched);
}
Note that the for loop does not have any getValue()/setValue() methods.
This has the same effect on your sample sheet, only way faster.

Related

I need to split a Google Sheet into multiple tabs (sheets) based on column value

I have searched many possible answers but cannot seem to find one that works. I have a Google Sheet with about 1600 rows that I need to split into about 70 different tabs (with about 20-30 rows in each one) based on the value in the column titled “room”. I have been sorting and then cutting and pasting but for 70+ tabs this is very tedious.
I can use the Query function but I still need to create a new tab, paste the function and update the parameter for that particular tab.
This script seemed pretty close:
ss = SpreadsheetApp.getActiveSpreadsheet();
itemName = 0;
itemDescription = 1;
image = 2;
purchasedBy = 3;
cost = 4;
room = 5;
isSharing = 6;
masterSheetName = "Master";
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Update Purchases')
.addItem('Add All Rows To Sheets', 'addAllRowsToSheets')
.addItem('Add Current Row To Sheet', 'addRowToNewSheet')
.addToUi();
}
function addRowToNewSheet() {
var s = ss.getActiveSheet();
var cell = s.getActiveCell();
var rowId = cell.getRow();
var range = s.getRange(rowId, 1, 1, s.getLastColumn());
var values = range.getValues()[0];
var roomName = values[room];
appendDataToSheet(s, rowId, values, roomName);
}
function addAllRowsToSheets(){
var s = ss.getActiveSheet();
var dataValues = s.getRange(2, 1, s.getLastRow()-1, s.getLastColumn()).getValues();
for(var i = 0; i < dataValues.length; i++){
var values = dataValues[i];
var rowId = 2 + i;
var roomName = values[room];
try{
appendDataToSheet(s, rowId, values, roomName);
}catch(err){};
}
}
function appendDataToSheet(s, rowId, data, roomName){
if(s.getName() != masterSheetName){
throw new Error("Can only add rows from 'Master' sheet - make sure sheet name is 'Master'");
}
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
var roomSheet;
if(sheetNames.indexOf(roomName) > -1){
roomSheet = ss.getSheetByName(roomName);
var rowIdValues = roomSheet.getRange(2, 1, roomSheet.getLastRow()-1, 1).getValues();
for(var i = 0; i < rowIdValues.length; i++){
if(rowIdValues[i] == rowId){
throw new Error( data[itemName] + " from row " + rowId + " already exists in sheet " + roomName + ".");
return;
}
}
}else{
roomSheet = ss.insertSheet(roomName);
var numCols = s.getLastColumn();
roomSheet.getRange(1, 1).setValue("Row Id");
s.getRange(1, 1, 1, numCols).copyValuesToRange(roomSheet, 2, numCols+1, 1, 1);
}
var rowIdArray = [rowId];
var updatedArray = rowIdArray.concat(data);
roomSheet.appendRow(updatedArray);
}
But I always get an unexpected token error on line 51 or 52:
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
(And obviously the column names, etc. are not necessarily correct for my data, I tried changing them to match what I needed. Not sure if that was part of the issue.)
Here is a sample of my data: https://docs.google.com/spreadsheets/d/1kpD88_wEA5YFh5DMMkubsTnFHeNxRQL-njd9Mv-C_lc/edit?usp=sharing
This should return two separate tabs/sheets based on room .
I am obviously not a programmer and do not know Visual Basic or Java or anything. I just know how to google and copy things....amazingly I often get it to work.
Let me know what else you need if you can help.
Try the below code:
'splitSheetIntoTabs' will split your master sheet in to separate sheets of 30 rows each. It will copy only the content not the background colors etc.
'deleteTabsOtherThanMaster' will revert the change done by 'splitSheetIntoTabs'. This function will help to revert the changes done by splitSheetIntoTabs.
function splitSheetIntoTabs() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var header = rows[0];
var contents = rows.slice(1);
var totalRowsPerSheet = 30; // This value will change no of rows per sheet
//below we are chunking the toltal row we have into 30 rows each
var contentRowsPerSheet = contents.map( function(e,i){
return i%totalRowsPerSheet===0 ? contents.slice(i,i+totalRowsPerSheet) : null;
}).filter(function(e){ return e; });
contentRowsPerSheet.forEach(function(e){
//crate new sheet here
var currSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
//append the header
currSheet.appendRow(header);
//populate the rows
e.forEach(function(val){
currSheet.appendRow(val);
});
});
}
// use this function revert the sheets create by splitSheetIntoTabs()
function deleteTabsOtherThanMaster() {
var sheetNotToDelete ='Master';
var ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(function(sheet){
if(sheet.getSheetName()!== sheetNotToDelete)
{
ss.deleteSheet(sheet);
}
});
}
I was using Kessy's nice script, but started having trouble when the data became very large, where the script timed out. I started looking for ways to reduce the amount of times the script read/wrote to the spreadsheet (rather than read/write one row at a time) and found this post https://stackoverflow.com/a/42633934
Using this principle and changing the loop in the script to have a loop within the loop helped reduce these calls. This means you can also avoid the second call to append rows (the "else"). My script is a little different to the examples, but basically ends something like:
`for (var i = 1; i < theEmails.length; i++) {
//Ignore blank Emails and sheets created
if (theEmails[i][0] !== "" && !completedSheets.includes(theEmails[i][0])) {
//Set the Sheet name = email address. Index the sheets so they appear last.
var currentSheet = theWorkbook.insertSheet(theEmails[i][0],4+i);
//append the header
//To avoid pasting formulas, we have to paste contents
headerFormat.copyTo(currentSheet.getRange(1,1),{contentsOnly:true});
//Now here find all the rows containing the same email address and append them
var theNewRows =[];
var b=0;
for(var j = 1; j < rows.length; j++)
{
if(rows[j][0] == theEmails[i][0]) {
theNewRows[b]=[];//Initial new array
theNewRows[b].push(rows[j][0],rows[j][1],rows[j][2],rows[j][3],rows[j][4],rows[j][5],rows[j][6],rows[j][7]);
b++;
}
}var outrng = currentSheet.getRange(2,1,theNewRows.length,8); //Make the output range the same size as the output array
outrng.setValues(theNewRows);
I found a table of ~1000 rows timed out, but with the new script took 6.5 secs. It might not be very neat, as I only dabble in script, but perhaps it helps.
I have done this script that successfully gets each room and creates a new sheet with the corresponding room name and adding all the rows with the same room.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("No Room");
} else {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
var currentSheet = SpreadsheetApp.getActiveSpreadsheet()
currentSheet.appendRow(rows[i]);
}
}
}
Please test it and don't hesitate to comment for improvements.

How to make this script run faster?

Hello so I am trying to get values from the 'book' sheet then copy it to 'Completed' sheet. It takes the values form book stores it in the data array and copy them to the 'Completed' sheet. Its working fine but very slow which jeopardizes my work in time-wise. How can I make this run faster ?
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var clear_sheet = spreadsheet.getSheetByName('Completed_Orders'); // clear the destination sheet first
clear_sheet.getRange('A2:X').clear();
var sheet = spreadsheet.getSheetByName('book'); //source sheet
var Datarange = sheet.getRange('Q3:Q'); //range to check
var Datavalue = (Datarange.getValues());
var dest = spreadsheet.getSheetByName('Completed_Orders'); //destination sheet
var data = [];
for (i=0; i<Datavalue.length;i++) {
if ( Datavalue[i] == "Completed") {
data.push.apply(data,sheet.getRange(i+3,1,1,24).getValues());
}
}
dest.getRange(2,1,data.length,data[0].length).setValues(data);
var column = dest.getRange('A3:A');
var values = column.getValues(); // get all data in one call
var ct = 0;
while ( values[ct][0] != "" ) { // to find the last row correctly, getLastRow is not working perfectly
ct++;
}
var endRow = ct+2;
}
Try this:
Don't use this syntax var values=sheet.getRange('A3:A').getValues() because it get's the data all the way down to getMaxRows(). Instead use var values=sheet.getRange(3,1,sheet.getLastRow()-2,1).getValues().
Also Datavalue[i] is a whole row
function myfunction() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var clear_sheet = spreadsheet.getSheetByName('Completed_Orders');
clear_sheet.getRange(2,1,clear_sheet.getLastRow()-1,24).clear();
var sheet = spreadsheet.getSheetByName('book'); //source sheet
var Datarange = sheet.getRange(3,17,sheet.getLastRow()-2,1); //range to check
var Datavalue = Datarange.getValues();
var dest = spreadsheet.getSheetByName('Completed_Orders'); //destination sheet
var data = [];
for (var i=0;i<Datavalue.length;i++) {
if (Datavalue[i]["************You need another index here**************"] == "Completed") { //need another index Datavalue is 2d
data.push(sheet.getRange(i+3,1,1,24).getValues());
}
}
dest.getRange(2,1,data.length,data[0].length).setValues(data);
}
I think this version will be a lot faster.
function myfunction() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var ssh=ss.getSheetByName('book');
var dsh=ss.getSheetByName('Completed_Orders');
dsh.getRange(2,1,dsh.getLastRow()-1,24).clear();
var dv=ssh.getRange(3,1,ssh.getLastRow()-2,24).getValues();//this version just gets this data one time so it should a lot faster.
var data=[];
for (var i=0;i<dv.length;i++) {
if (dv[i][16]=="Completed") {
data.push(dv[i]);
}
}
dsh.getRange(2,1,data.length,data[0].length).setValues(data);
}

Script: Transpose, Find and Save

I have some data containing, Dates, Usernames and an average percent that i want to save in a certain way. My problem is that the order of the usernames can change depending on if new ones are added. I therefore need to "find" a specific username and then save the percentage data in the correct column.
I have found some code that partially helps me save the data that i need. But i could use some help in the "find" the corresponding username and save it in a certain Column part.
function save() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Result');
var range = ss.getRange('B1:B10');
var data = range.getValues();
var tss = SpreadsheetApp.getActive();
var ts = tss.getSheetByName('Archive');
ts.getRange(ts.getLastRow()+1, 1,data[0].length,data.length)
.setValues(Object.keys(data[0]).map ( function (columnNumber) {
return data.map( function (row) {
return row[columnNumber];
});
}));
}
Basically from this:
To a result that looks like this:
Thank you for your assistance.
Alright for anyone out there that may have a similar problem, this is what i ended up with.
function extractAttendance() {
var currentSheet = SpreadsheetApp.getActive();
var attendanceTab = currentSheet.getSheetByName('Data_Filtered');
var userData = attendanceTab.getRange('B1:B').getValues();
var percentageData = attendanceTab.getRange('I1:I').getValues();
var archiveTab = currentSheet.getSheetByName('Archive');
var existingUsersRow = archiveTab.getRange('1:1');
var newRowNumber = archiveTab.getLastRow() + 1;
archiveTab.getRange(newRowNumber, 1).setValue(new Date());
for (var i = 1; i < userData.length; i++) {
var user = userData[i][0];
if (user === '') {
continue;
}
var existingUsers = existingUsersRow.getValues()[0];
var exists = false;
var existingColumnNumber = -1;
for (var j = 0; j < existingUsers.length; j++) {
if (existingUsers[j] === user) {
exists = true;
existingColumnNumber = j + 1;
break;
}
}
if (exists) {
archiveTab.getRange(newRowNumber, existingColumnNumber).setValue(percentageData[i]);
} else {
var newColumnNumber = archiveTab.getLastColumn() + 1;
archiveTab.getRange(1, newColumnNumber).setValue(user);
archiveTab.getRange(newRowNumber, newColumnNumber).setValue(percentageData[i]);
}
}
}
It might be easier to implement your desired functionality through looping rather than through mapping.
The following code retrieves all users ad their percentage data in ‘Result’ and transfers the data (in the format you desire) to "Archive" with the percentages data pasted with the corresponding timestamp into the first empty row.
function save() {
var sss = SpreadsheetApp.getActive();
var ss = sss.getSheetByName('Result');
var range = ss.getRange('B1:B');
var percentageRange = ss.getRange('G1:G');
var userData = range.getValues();
var percentageData = percentageRange.getValues();
var tss = SpreadsheetApp.getActive();
var ts = tss.getSheetByName('Archive');
var userRow=1;
var percentageRow=(ts.getLastRow()+1)
for(var i=0; i<=userData.length; i++)
{
{
var j=(i+2);
ts.getRange(userRow, j).setValue(userData[i])
ts.getRange(percentageRow, 1).setValue(new Date())
ts.getRange(percentageRow, j).setValue(percentageData[i])
}
}
}

How to Collect data from ALL sheets using google Appscript

I have script that searches through an entire workbook for a specific name and returns all the data on that name. The script works, but only collects data from 1 sheet within the workbook.
I searched for some code to assist me getting all the sheet names. So I have code that does that, but for some reason it still only returns from 1 sheet.
The code below collects all the sheet names.
This function is then called in the query function.
I Suspect that this is where the issue is occuring
function sheetnames() {
var out = new Array()
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
for (var i=0 ; i < sheets.length ; i++) {
var name = sheets[i].getName();
var data = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(name);
var values = data.getRange(4, 1, data.getLastRow(),
data.getLastColumn()).getValues();
out.push(values);
}
return out;
}
This function then searches for the requested data.
function query() {
var Sheet = SpreadsheetApp.getActiveSpreadsheet();
var searchSheet = Sheet.getSheetByName("Search");
var searchByName = searchSheet.getRange(4, 8).getValue();
var uses = sheetnames();
var output = new Array();
var i = 0;
var r = 0;
do{
var from = uses[i];
do{
var row = from[r];
if(row == null){
r++;
continue;
}
if(searchByName != null ){
var newName = row[7];
if(newName == searchByName){
output.push(row);
}
}
r ++;
}while(r < from.length);
i ++;
}while(i < uses.length);
return output;
}
This part just prints the data into the cells and is attached to a search drawing, which runs the function in the sheet.
function search() {
var Sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Search");
var data = query();
var count1 = 0;
do{
var subData = data[count1];
var count2 = 0;
do{
var setTo = subData[count2];
Sheet.getRange((count1 + 5), (count2 + 1)).setValue(setTo);
count2 ++;
}while(count2 < subData.length);
count1 ++;
}while(count1 < data.length);
}
The sheet is called the "Daily Payments Sheet." As you can imagine there is A LOT of data. Each sheet name is named by the month and the year that the payment occurred. The more consistent customers would obviously make purchases in more than one month.
So when searching for a customers name, I only get 1 month (1 sheet's data) returned. We have data from May 2018 till date, so again, the script doesn't collect from all the sheets.
Your code is not very readable so I figured some things on my own and simplified it. Things I assume - your search term is in 'Search' sheet column H4 and you want to search all sheets for this term in H4 column and write those out in 'Search' sheet after 4th row. Try this.
// return all rows from all sheets except Search sheet
function sheetValues(ss) {
var out = [];
var sheets = ss.getSheets();
for (var i = 0; i < sheets.length; i++) {
var sheet = sheets[i];
if (sheet.getName() == 'Search') continue;
var values = sheet.getRange(4, 1, sheet.getLastRow() - 3, sheet.getLastColumn()).getValues();
out.concat(values);
}
return out;
}
// search all rows for given term and return results
// look for term in H column of every row
function query(ss, term) {
if (!term) return;
var values = sheetValues(ss);
var output = [];
for (var i = 0; i < values.length; i++) {
var row = values[i];
var name = row[7]; // 7 = col H
if (name == term) {
output.push(row);
}
}
return output;
}
// get search results and print into Search sheet
function search() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Search');
var searchByName = sheet.getRange(4, 8).getValue(); // search term is in H4 cell
var data = query(ss, searchByName);
sheet.getRange(5, 1, sheet.getLastRow() - 4, sheet.getLastColumn()).clearContent();
sheet.getRange(5, 1, data.length, data[0].length).setValues(data);
}

Optimal way to set cell values in Google Sheet via Script

I have a template sheet with checkboxes and I want to copy the checked ones to a new sheet. I have a working version that involves adding rows but I am looking for something faster. I thought getting a range of values on both the new and old sheets and working on the arrays would be best but I hit a error:
'Cannot covert Array to Object[][]".
I think the issue has to do with the fact that this is a new unpopulated sheet. The code below is the simplest example of what is happening. Am I doing something wrong, or is this just not possible?
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
var r = s.getRange(1,1,5);
var v = r.getValues();
for ( var i=0; i < 5; i++) {
v[i] = i;
}
r.setValues(v); //ERROR: Cannot covert Array to Object[][]`enter code here`
}
It looks like the line v[i] = i; converts the Object[][] to an array. So , i think (bizarre) I need to create a new array[][] asfollows:
function test(){
var s = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var r = s.getRange(1,1,5,1);
var v = r.getValues();
var ta = [];
for ( var i=0; i < 5; i++) {
ta[i] = [];
ta[i].push(i) ;
}
r.setValues(ta);
}
Ok. Here is the full solution.
The function looks for the sheet "Work" that has 2 columns; the first is a checkbox, the second is the string value of interest. For every checked box (value == true), the 2nd column's value, Font weight, and Font size are copied into appropriately 'shaped' structures.
Once constructed, a new sheet is created, a range in the new sheet is retrieved and used to set the values, weights and sizes of a single column.
function copyCheckedItems () {
var cl = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Work');
if (cl) {
var cnt = cl.getLastRow();
var range = cl.getRange(1,1, cnt, 2 );
var values = range.getValues();
var weights = range.getFontWeights();
var sizes = range.getFontSizes();
// Compute data needed for new sheet in the right shape.
var tv = [];
var tw = [];
var ts = [];
var newCnt = 0;
for (var row in values) {
if(values[row][0]){
tv[newCnt] = [];
ts[newCnt] = [];
tw[newCnt] = [];
tv[newCnt].push(values[row][1]);
tw[newCnt].push(weights[row][1]);
ts[newCnt].push(sizes[row][1]);
newCnt++;
}
}
// construct the new sheet in a minimum of calls
var name = Browser.inputBox('Enter WorkSteet name');;
var sheetOut = SpreadsheetApp.getActiveSpreadsheet().insertSheet(name);
var ro = sheetOut.getRange(1,1,newCnt,1);
ro.setValues(tv);
ro.setFontSizes(ts);
ro.setFontWeights(tw);
//Browser.msgBox("Done.");
}
else {
Browser.msgBox('Work sheet not found!');
}
}