Google Script - Conditionally append rows from sheet to sheet - javascript

I'm trying to append rows from one sheet to another sheet based on two conditions. I'm fairly new to google script and coding in general.
You can find a copy of the worksheet here.
I want to extract all rows with a weeknum (column I of MASTER sheet) that matches the previous weeknum (found on the B2 cell of the DASHBOARD sheet) and that also have "Not Coming" (column K of MASTER SHEET).
The goal is to create a call list (on the Call List Cancelled W-1) every week with people that did not honor their appointments the week before. Ideally the rows would be appended on the top of the call list sheet.
I've managed to build this code as for now. But it does not work.
// Creating Call List from cancelled appointments in W-1
var ss = SpreadsheetApp.getActiveSpreadsheet();
//CALL LIST
var sheetCallList = ss.getSheetByName("Call List Cancelled W-1");
var rangeCallList = sheetCallList.getDataRange();
//MASTER
var sheetMaster = ss.getSheets()[0];
var dataRangeMaster = sheetMaster.getDataRange();
var valuesMaster = dataRangeMaster.getValues();
var lastRowMaster = sheetMaster.getLastRow();
var cameOrNot = sheetMaster.getRange("K:K").getValues();
var weekNumColumn = sheetMaster.getRange("I:I").getValues()
//DASHBOARD
var sheetDashboard = ss.getSheets()[1];
var weeknum = sheetDashboard.getRange(2, 2).getValue();
var lastweeknum = weeknum - 1
function appendRowsToCallList() {
for (var i = 2; i < lastRowMaster; i++); {
if ( lastweeknum == weekNumColumn && cameOrNot == "Not Coming") {
sheetCallList.rangeCallList.appendRow(valuesMaster);
}
}
}

Solution:
There is a typo in your for loop:
for (var i = 2; i < lastRowMaster; i++);
Since there's an extra semicolon, this would loop but execute nothing.
Also, to append rows, you need to refer to individual arrays in valuesMaster and check individual values in the other arrays. Also indexes start with 0 so to check from the second value you start the index with 1.
Function code:
function appendRowsToCallList() {
for (var i = 1; i < lastRowMaster; i++) {
if ( lastweeknum == weekNumColumn[i][0] && cameOrNot[i][0] == "Not Coming") {
sheetCallList.appendRow(valuesMaster[i]);
}
}
}
Output:

Related

Google Apps Script: Loop through a list

I have a deleteEachRow function that loops through a sheet and delete Rows that have a particular Column Value.
This works fine and was hoping to modify it in such a way that it loops through a multile sheets in the work-book and also delete rows based on multiple Column Values.
The deleteRow() script
//GLOBALS
var SS = SpreadsheetApp.openById("sheetID");
var SHEET = SS.getSheetByName("Sheet1");
var RANGE = SHEET.getDataRange();
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL){
SHEET.deleteRow(i+1);
};
};
};
What I have tried..
var SHEET = SS.getSheetByName(["Sheet1", "Sheet2"]);
var DELETE_VAL = ["abc","DEF"];
function deleteEachRow(){
var rangeVals = RANGE.getValues();
//Reverse the 'for' loop.
for(var i = rangeVals.length-1; i >= 0; i--){
for(var i=0; size = DELETE_VAL.length; i < size; i++){
if(rangeVals[i][COL_TO_SEARCH] === DELETE_VAL[i]){
for(var i=0; size = SHEET.length; i < size; i++){
SHEET[i].deleteRow(i+1);
};
};
};
};
};
Which completes executing from my logs, but does not actually work. I may have murdered some logic here, please pardon me, I am new to .gs/.js.
Thanks for your anticipated response.
Issue : You're passing array to getSheetByName, whereas as per documentation it accepts String only. i.e. Name of the single sheet you want to fetch.
https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet#getsheetbynamename
So you can modify your function to take sheet name as input and then delete rows in that sheet. Then call your function with desired sheet names. Something like this:
var spreadSheet = SpreadsheetApp.openById("sheetID");
var DELETE_VAL = "abc";
var COL_TO_SEARCH = 4; // The column to search for the DELETE_VAL (Zero is first)
function deleteEachRow(sheetName){
var SHEET = spreadSheet.getSheetByName(sheetName);
var RANGE = SHEET.getDataRange();
var rangeVals = RANGE.getValues();
// existing logic
};
// Invoke deleteEachRow() for each sheet you want to delete the rows
["Sheet1", "Sheet2"].forEach((sheetName) => deleteEachRow(sheetName));
Umair is right, there was a simply error in the first line. But I'd want to add that the sheet.deleteRow(row) is not the best practice in case if there are many rows to delete. This command is quite time consuming.
If you have more than dozen rows to delete it's better to grab all data from a sheet (or range) var data = range.getValues(), clear the sheet (or the range), to process the array inside the script and refill the sheet back with new data new_range.setValues(array). It will work much faster.

Optimize for-loop function in Google Apps Script (Arrays maybe)?

First-time poster here. I would like some insight on some Google App Script code i think could be spruced up a bit.
Long story short.
I have a 2 Google Sheet tables
A “LOCALE” spreadsheet - listing unique names of locations
A “FEED” spreadsheet - listing photo descriptions, including the locations. The same location is listed multiple times in this spreadsheet.
Both of these tables have a “Location” column, which references each other with a Key column.
The problem I want to solve for:
When I edit a location name in the “LOCALE” spreadsheet, it should automatically update all the location names in the “FEED” spreadsheet.
The way I solved this problem:
I used a for loop within a for loop for this. To summarize:
for every row in "LOCALE"...
..go through every row in "FEED"...
...If a value in the Key column in the FEED Sheet matches a value in the Key column in the LOCALE Sheet...
...but the value in the Location column in the FEED Sheet doesn't match the value in the Location column in the LOCALE Sheet...
...update the Location column in the FEED Sheet with the value in the Location column in the LOCALE Sheet.
If you're not confused yet, here's the code i wrote for it:
// for each row in the "Locale" sheet...
for(var L = LocationsRefValues.length-1;L>=0;L--) {
// for each row in the "Feed" sheet...
for(var F = FeedRefValues.length-1;F>=0;F--) {
if (FeedRefValues[F][97] == LocationsRefValues[L][17] &&
FeedRefValues[F][10] != LocationsRefValues[L][1]) {
FeedDataSheet.getRange(F+2,10+1).setValue(LocationsRefValues[L][1]);
}
}
}
Now, this code works perfectly fine, I've had no issues. However, i feel like this a bit clunky, as it takes a while to finish its edits. I'm certain there's any easier way to write this and run this code. I've heard arrays may address this situation, but i don't know how to go about that. Hence, why I'm looking for help. Can anyone assist?
Keep in mind I'm a total Google App Script beginner who got this code working through sheer dumb luck, so the simpler the solution the better. Thanks for any consideration to my problem in advance. Looking forward to hearing from you all.
This is the full function (after i made edits suggested here.)
function ModeratorStatus() {
var Data = SpreadsheetApp.getActiveSpreadsheet(); // Local Spreadsheet
var ModeratorStatusDataSheet = Data.getSheetByName("The Status (Moderators)");
var ModeratorStatusRange = ModeratorStatusDataSheet.getRange("A2:C");
var ModeratorStatusRefValues = ModeratorStatusRange.getValues();
var ModeratorDataSheet = Data.getSheetByName("The Moderator_Numbers"); // DATA "Member" sheet
//var ModeratorRefValues = ModeratorDataSheet.getRange("A2:AD").getValues();
var ModeratorStatusObj = {};
for (var MOS = ModeratorStatusRefValues.length-1; MOS>=0; MOS--) {
ModeratorStatusObj[ModeratorStatusRefValues[MOS][2]] = ModeratorStatusRefValues[MOS][0];
}
var ModeratorValues = ModeratorDataSheet.getRange("A1:AD").getValues();
for (var MO = ModeratorValues.length-1; MO >=0; MO--) { // for each row in the "Moderator" sheet...
var ModeratorVal28 = ModeratorValues[MO][28];
if (ModeratorStatusObj[ModeratorVal28] != ModeratorValues[MO][1]) {
ModeratorValues[MO][1] = ModeratorStatusObj[ModeratorVal28];
}
}
var destinationRange = ModeratorDataSheet.getRange(1, 1, ModeratorValues.length, ModeratorValues[0].length);
destinationRange.setValues(ModeratorValues);
I used the code in a different function as a test. To make it easier
LOCALE = MODERATOR STATUS
FEED = MODERATOR
If there are no duplicate [17]s with different [1]s in the LocationsRefValues, you can reduce the computational complexity from O(n ^ 2) to O(n) by creating a mapping object for LocationsRefValues beforehand, whose keys are the LocationsRefValues[L][17]s and whose values are the LocationsRefValues[L][1]s:
var locationObj = {};
for (var L = 0; L < LocationsRefValues.length; L++) {
locationObj[LocationsRefValues[L][17]] = LocationsRefValues[L][1];
}
for (var F = FeedRefValues.length - 1; F >= 0; F--) { // for each row in the "Feed" sheet...
var feedVal97 = FeedRefValues[F][97];
if (locationObj[feedVal97] != FeedRefValues[F][10]) {
FeedDataSheet.getRange(F + 2, 10 + 1).setValue(locationObj[feedVal97]);
}
}
Thanks #TheMaster, you can speed this up by calling setValue only once, at the end, rather than calling it in a loop, probably something along the lines of:
var locationObj = {};
for (var L = 0; L < LocationsRefValues.length; L++) {
locationObj[LocationsRefValues[L][17]] = LocationsRefValues[L][1];
}
var feedValues = FeedDataSheet.getValues();
for (var F = FeedRefValues.length - 1; F >= 0; F--) { // for each row in the "Feed" sheet...
var feedVal97 = FeedRefValues[F][97];
if (locationObj[feedVal97] != FeedRefValues[F][10]) {
feedValues[F + 2][10 + 1] = locationObj[feedVal97];
}
}
var destinationRange = ss.getRange(1, 1, feedValues.length, feedValues[0].length);
destinationRange.setValues(feedValues);
you can use onEdit(e) trigger to get the reference to the edited cell. In that case you won't need to iterate over the entire Locale" sheet:
function onEdit(e) {
var range = e.range; // edited cell
var rowIndex = range.getRow()
var colIndex = range.getColumn()
if (rowIndex >= LocaleRange.startRow && rowIndex <= LocaleRange.EndRow &&
colIndex >= LocaleRange.startColumn && colIndex <= LocaleRange.EndColumn) {
var index = rowIndex - LocaleRange.startRow
var keyValue = LocationsRefValues[index][17]
var newLocValue = range.getValue()
var newFeedValues = FeedRefValues.map(function (row) {
return (row[97] == keyValue) newLocValue ? : row[10]
})
FeedDataRange.setValues(newFeedValues)
}
}
Here are docs on using onEdit trigger: https://developers.google.com/apps-script/guides/triggers

How do I make less calls to sheet.getRange and reduce the execution time in a double loop

I want to reduce the calls to sheet.getRange in the following function, because the execution times out.
I've tried re-arranging the loops and parsing the data as a double array, however the size of my spreadsheet changes every day and I need to be able to reference the columns by name only.
function runDuplicateRemover() {
var sheet= SpreadsheetApp.getActive().getSheetByName('Sheet 1');
var rangeData = sheet.getDataRange();
var lastRow = rangeData.getLastRow();
var Cdata = sheet.getDataRange().getValues();
// here I am accessing the column which is used to find duplicates
var colCRM = Cdata[0].indexOf("CRM ID")+1;
var arrayOfDuplicates = [];
for(i=1; i<lastRow; i++){
var cellToCompare = sheet.getRange(i+1,colCRM);
// I am just changing all the colors to see the execution
cellToCompare.setBackground("#88b4fc");
var crmToCompare =cellToCompare.getValue();
//checks to see that this value is not already contained in the rows to delete
if (!cellToCompare.isBlank() && (arrayOfDuplicates.indexOf(i)+1)==0 ){
arrayOfDuplicates.push(i);
cellToCompare.setBackground("#f9d9f9");
for (j = i+1; j<lastRow; j++) {
var cellCurrent = sheet.getRange(j+1,colCRM);
cellCurrent.setBackground("#f2fc88");
var crmCurrent = cellCurrent.getValue();
if (crmToCompare == crmCurrent) {
arrayOfDuplicates.push(j);
cellCurrent.setBackground("#fc92f1");
}
}
//pops last value since that's the only one I want to keep
sheet.getRange(arrayOfDuplicates.pop()+1,colCRM).setBackground("#dbf7d4");
}
}
for (t = arrayOfDuplicates.length-1; t>=0; t--) {
sheet.deleteRow(arrayOfDuplicates[t]+1);
}
}
I'd like to reduce the calls to the sheet.getRange, however I don't know how to delete the rows and then return the data back to the sheet without messing all the column order up.

JS / GS Simple Google Sheets Script

Im trying to create a simple script that firstly checks (all cells in row 3 starting from column 3) for whether they contain a name different from the available sheets and if so create a new one. If not go to the next cell down. Preferably until the row is empty but I didnt get that far. Currently I have
var row = 3; //Global Variable
function Main() { // Main Function
Activate();
GetNames();
}
function Activate() { // Initialize
var get = SpreadsheetApp.getActiveSpreadsheet();
var import = get.getSheetByName("Import");
}
function GetNames() { // Get Names and check for existing Sheets
var ss = SpreadsheetApp.getActiveSpreadsheet();
var import = ss.getSheetByName("Import");
var sheet = ss.getSheets()[0];
for (i = 0; i < 1000; i++) { // Loop which I think is broken
var names = import.getRange(row,3).getValue();
if (import.getSheetByName(names) == null) {
import.insertSheet(names);
import.activate();
}
row + 1;
}
}
And here is the Data
It succeeds to add the first sheet but fails to continue in the loop I think.
As you will probably see I'm very new to this and any help would be appreciated.
You probably wanted to increase row by 1 here:
row + 1;
But you're not. row + 1 is just an expression with a value (4, in your example, because row remains 3 throughout). What you would need is the statement
row = row + 1;
But if this is all that you're using the global variable row for, you don't need it at all. Just use the loop variable i that's already counting from 0 to 1000. You probably want something like import.getRange(i+3,3).

Data copying with Google Apps Script

I am trying to write a script in Google Apps Script that takes cell information from one sheet and copies it to another sheet, both for just grabbing certain columns to display on the second sheet and also a condition based on the values inside cells in a certain column. Here is what I have so far:
function onMyEdit() {
var myMaster = SpreadsheetApp.openById("xxxxx");
var masterSheet = myMaster.setActiveSheet(myMaster.getSheets()[0]);
var myNames = SpreadsheetApp.openById("xxxxx");
var namesSheet = myNames.setActiveSheet(myNames.getSheets()[0]);
var row1 = masterSheet.getRange(1, 1, masterSheet.getLastRow(), 1);
var rowV = row1.getValues();
var firstArray = masterSheet.getDataRange().getValues();
var dataList = [];
for (var i = 1; i < rowV.length; i++) {
dataList.push(firstArray[i][0]);
dataList.push(firstArray[i][1]);
dataList.push(firstArray[i][2]);
dataList.push(firstArray[i][3]);
}
for (var j = 0; j < rowV.length - 1; j++) {
namesSheet.getRange(2, j + 1, 1, 1).setValue(dataList[j]);
}
}
So as of now it only works on one row, starting from the second row (to allow for column headers). And I suppose when I want to grab rows conditionally based on cell data, I will use an 'if' statement for the condition inside the 'for' loop, but I want the data to copy to the next available row in both sheets. I suppose I'd use something like:
' getLastRow + 1 '
or something like that. I need this code to be as efficient as possible because of the amount of data and its purpose. I am pretty new to programming so please explain in detail, and thanks again.
I'm not sure I understood exactly what you wanted to do but -from what I understood- this code snippet should give you a better way to start with...
(I added a few comments to explain in the code itself)
function onMyEdit() {
var myMaster = SpreadsheetApp.openById("MasterSheet ID");
var masterSheet = myMaster.getSheets()[0]; // get 1rst sheet
var myNames = SpreadsheetApp.openById("NamesSheet ID");
var namesSheet = myNames.getSheets()[0]; // get 1rst sheet
var firstArray = masterSheet.getDataRange().getValues();
var dataList = [];
for ( r = 1; r < firstArray.length; r++) { // iterate the first col of masterSheet
if(firstArray[r][0]=='some condition'){ // if value in the first column == 'some condition get the second column cell in the new array (here you could change what you want to get)
dataList.push([firstArray[r][1]])
}
}
Logger.log(dataList)
if(dataList.length>0){
namesSheet.getRange(1,namesSheet.getLastColumn()+1,dataList.length,1).setValues(dataList);//copy data in a column after last col
}
}

Categories

Resources