I'm making a google sheet that writes to a SQL server periodically. This is my code so far, less the SQL stuff. It grabs the a variable from the last entry in the database ("User" in this test), and submits everything after it.
function connectToMySqlDB() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getDataRange();
var values = dataRange.getValues();
var lastColumn = sheet.getLastColumn();
Logger.log('Starting query process... ');
// Connect to DB and get last entry
var lastline = "User"
Logger.log('Found serial number of last enter in SQL Database: ' + lastline);
// Find row of entry in goolge sheet
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == lastline) {
row = [i+1]
Logger.log('Found serial number in row: ' + row);
}
}
}
// Get first unfilled row
var crange = sheet.getRange("C1:C").getValues();
var Clast = crange.filter(String).length;
Logger.log('Found last row in C: ' + Clast);
var cfin = [Clast - row]
Logger.log('Slection will be: ' + row + ' rows.');
// select rows from entry to last filled row
var selection = sheet.getRange(row,0,cfin,lastColumn)
// Submit rows line by line to DB (from last entry to first unfilled)
// Write one row of data to a table.
}
The error I get from the google error is:
The coordinates or dimensions of the range are invalid. (line 33, file "Code")
Using the debug tool, the variable "row" has no value. The log file however shows that it does work
[16-09-15 11:39:41:063 PDT] Starting query process...
[16-09-15 11:39:41:064 PDT] Found serial number of last enter in SQL Database: User
[16-09-15 11:39:41:067 PDT] Found serial number in row: 26
[16-09-15 11:39:41:157 PDT] Found last row in C: 37
[16-09-15 11:39:41:158 PDT] Selection will be: rows.
My guess is somehow "var cfin = [Clast - row]" is clearing the "row" variable. I can't figure out for the life of me how to get around this.
You seem to want row to be a number, but you're wrapping its calculation in square braces, giving you an array of one element long. Remove the square braces.
row = i+1;
(As an aside, you're declaring it as a string, wont make a difference but its better to not do that!)
After that, you're making the same mistake with cfin. Dont wrap it in square braces if you simply want a number
var cfin = Clast - row;
You may also have a problem if row is set by being found, but then the outer loop will reset that variable. You can get round this by breaking out of both loops:
var row = -1;
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
if (values[i][j] == lastline) {
row = i+1
Logger.log('Found serial number in row: ' + row);
break;
}
}
if(row > -1) break;
}
Related
First off, I am not a coder at all, just a teacher who's handy at googling things to make life easier. In my attendance book, I bold the times a student comes in tardy (they get a 1 if present and a 0 if absent in order to calculate attendance rate).
I found an awesome script that allows me to count the number of bold items in a range. However, the range is set and I can't specify a new range within google sheets for each student as is necessary.
I tried changing it to "function countColoredCells(countRange)" but it doesn't work as I assume there is something else I have to do within the rest of the script.
I literally have little to no coding knowledge and would really appreciate any help to solve this!
function countboldcells() {
var book = SpreadsheetApp.getActiveSpreadsheet();
var sheet = book.getActiveSheet();
var range_input = sheet.getRange("C3:S3");
var range_output = sheet.getRange("N3");
var cell_styles = range_input.getFontWeights();
var count = 0;
for(var r = 0; r < cell_styles.length; r++) {
for(var c = 0; c < cell_styles[0].length; c++) {
if(cell_styles[r][c] === "bold") {
count = count + 1;
}
}
}
range_output.setValue(count);
}
range_input in the existing script is hard-coded. This is unsatisfactory because it doesn't permit analysis on a student-by-student basis. To fix this, you need to loop through the data for each student, and do 'countbold' for each student.
Let's assume that "C3:S3" is the range for a single student. Let's also assume that the data for other students is contained in each subsequent row, and that there are two header rows.
To do:
Work out the number of rows of student data - refer variable ALast.
Get the data for all students in one go. Why? Because this is more efficient than getting the data one row at a time - refer range_input discussed below.
Loop through each row of the data (i.e. loop by student - using a "for" loop).
Count the bold cells and update the results for each student - using most of your existing code;
Note:
The destination range (range_output) is calculated for each row, using getRange (row,column). This could have been done by saving values to an array, and updating all the values in a single process, but I though it was better to retain the approach the OP had already taken, and not over-complicate matters. If there are a LOT of students AND the code is taking too long to run, then updating the counts by array would be more efficient.
The input range (range_input) is defined using getRange(row, column, numRows, numColumns).
row = 3, the first row of data
column = 3, Column C
numRows = a calculated value (ALast minus two header rows)
numColumns = Columns C to S inclusive = 17 (assigned to a variable).
function so54260768() {
// Setup spreadsheet and target sheet
var book = SpreadsheetApp.getActiveSpreadsheet();
var sheet = book.getActiveSheet();
// get the number of students in Column A
var Avals = book.getRange("A1:A").getValues(); // assuming rows one and two are headers
var Alast = Avals.filter(String).length;
//Logger.log("DEBUG: The last row on A = " + Alast);// DEBUG
// number of columns in the data range
var NumberofColumns = 17;
// get the data for all students
var range_input = sheet.getRange(3, 3, Alast - 2, NumberofColumns); // the first two rows are headers
var cell_styles = range_input.getFontWeights();
// start loop though each row - one row per student
for (z = 0; z < Alast - 2; z++) {
// set the bold counter to zero
var count = 0;
//loop through the cells in this row; count the cells that are bold
for (var i = 0; i < NumberofColumns; i++) {
if (cell_styles[z][i] === "bold") {
count = count + 1;
}
}
//Logger.log("DEBUG: row="+(z+3)+", count="+count);//DEBUG
var range_output = sheet.getRange(z + 3, 14).setValue(count); //. row, column
}
}
I'm a bit of newbie at coding, especially Javascript/Google-script language. I've created the code below, and it works, but now that I've got a working code I'd like to see how I can optimize it. It seems to me that all of the getValue() calls are a major performance hit, and I've never really been good at optimizing loops. Anyone know a better way to accomplish the same as this code?
What it does: Checks each spreadsheet in one of my folders to see if it needs to have the rest of the script run. If true, it opens that sheet and counts the number of rows that have data, using that to limit the amount of rows it checks in the loop. It then looks for any row marked for push and copies that range to another spreadsheet in my drive. It then continues to the next file in the folder and does the same.
Here's my code:
function myVupdate() {
try {
var folder = DriveApp.getFolderById("123abc"),
files = folder.getFiles();
while (files.hasNext()) {
var file = files.next(),
sss = SpreadsheetApp.open(file);
SpreadsheetApp.setActiveSpreadsheet(sss);
//Work orders update
var ss = sss.getSheetByName("Sheet2"),
refresh = ss.getRange("W3").getValue();
if (refresh == 0) {continue};
var avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 23).getValue();
if (cell == 0) {
var cells = [["v" + "WO-" + val[row-1][3] + "_" + val[row-1][2],val[row-1][13],val[row-1][14],val[row-1][15],new Date()]];
var tss = SpreadsheetApp.openById("target_spreadsheet"),
ts = tss.getSheetByName("Sheet5");
ts.insertRowBefore(2);
var last_hmy = ts.getRange(3,1).getValue();
ts.getRange(2,1).setValue(last_hmy+1);
ts.getRange(2,2,cells.length,cells[0].length).setValues(cells);
ts.getRange(2,7).setValue(sss.getName());
ss.getRange(row,17).setValue(last_hmy+1);
ss.getRange(row,18,cells.length,cells[0].length).setValues(cells);
//Turnover update
var ss = sss.getSheetByName("Sheet1"),
avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
}
}
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 24).getValue();
if (cell == 0) {
var cells = [["v" + val[row-1][3] + "_" + val[row-1][2],val[row-1][12],val[row-1][15],val[row-1][16],new Date()]];
var tss = SpreadsheetApp.openById("target_spreadsheet"),
ts = tss.getSheetByName("Sheet5");
ts.insertRowBefore(2);
var last_hmy = ts.getRange(3,1).getValue();
ts.getRange(2,1).setValue(last_hmy+1);
ts.getRange(2,2,cells.length,cells[0].length).setValues(cells);
ts.getRange(2,7).setValue(sss.getName());
ss.getRange(row,18).setValue(last_hmy+1);
ss.getRange(row,19,cells.length,cells[0].length).setValues(cells);
}
}
}
}
catch(e) {
// Browser.msgBox("An error occured. A log has been sent for review.");
var errorSheet = SpreadsheetApp.openById ("target_sheet").getSheetByName("Error Log"),
source = sss.getName();
lastRow = errorSheet.getLastRow();
var cell = errorSheet.getRange('A1');
cell.offset(lastRow, 0).setValue(e.message);
cell.offset(lastRow, 1).setValue(e.fileName);
cell.offset(lastRow, 2).setValue(e.lineNumber);
cell.offset(lastRow, 3).setValue(source);
cell.offset(lastRow, 4).setValue(new Date());
MailApp.sendEmail("my#email.com", "Error report - " + new Date(),
"\r\nSource: " + source + "\r\n"
+ "\r\nMessage: " + e.message
+ "\r\nFile: " + e.fileName
+ "\r\nLine: " + e.lineNumber
);
}
}
Hello and welcome to Stack Overflow,
first of all, you are correct. The more getValue(), or setValue() calls you do the worse the performance, read more on best practices here. Google recommends you batch these as much as possible. One thing that immediately springs to attention is the following:
var val = ss.getDataRange().getValues();
so now you have all the values on the sheet in a 2D array. That means that in the following bit
var ss = sss.getSheetByName("Sheet2"),
refresh = ss.getRange("W3").getValue();
if (refresh == 0) {continue};
var avals = ss.getRange("D5:D").getValues(),
count = avals.filter(String).length,
rows = count + 5
var val = ss.getDataRange().getValues();
for (var row=5; row < rows; row++) {
var cell = ss.getDataRange().getCell(row, 23).getValue();
every single getValue() or getValues() is no longer necessary. Instead, you know that refresh = val[2][22] because you need the 3rd row and 23rd column, as you already have the entire range that has data from that sheet.
Same with avals as all values in range D5:D are in vals[n][3], where n starts from 4. Remember, the array index starts from 0 (so first row and first column is vals[0][0].
So anywhere you are trying to use getValues() from the ss spreadsheet, you already have that data. What you can also do, is manipulate the array you have, so you always change the values only in that array. Once you are done with it, you use ss.getDataRange().setValues(vals) to push the entire array back to the same range (you can just store the range in a variable like datRange = ss.getDataRange() and then do datRange.setValues(vals).
You will just need to work with a separate data array for any other sheet. I did not go into detail for the rest of the code as the same ideas go throughout. Since you already grab everything with getValues() there is no longer any reason to use getValue() for any cell within that range.
I modified the code this way... it gathers data from all the sheets and finds only the rows that have data, BUT now I am having a problem modifying the range with each pass so that it is equal to the number of rows that do have value (found with (values[row][0] != '')). I have put a ??? in the spot where I am trying to have a variable height.
function getAllData() {
var folder = DocsList.getFolderById("folderid");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Base")
sheet.clearContents();
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet 1");
var lastLine = sheet2.getLastRow();
var values = sheet2.getRange('A3:J').getValues();
var formulas = sheet2.getRange('A3:J').getFormulas();
var data = [];
for(var row = 0 ; row < (values).length ; row++){
var lastrow = sheet.getLastRow()+1;
if (values[row][0] != '') {
for(var col = 0 ; col < formulas[row].length ; col++){
if(formulas[row][col] != '')
{values[row][col] = formulas[row][col]};
data.push(values[row]);}
if(data.length > 0)
sheet.getRange(lastrow, 1, ???, data[0].length).setValues(data);
}
}
};
}}
You are using getValue() as opposed to getValues() (With a letter "s" on the end)
var onecell = posheet.getRange('B4').getValue();
The documentation states:
getValue() - Returns the value of the top-left cell in the range.
The parameter for getRange() is kind of tricky and not well documented.
For example this:
getRange(2, 3, 6, 4)
gets a range from C2 to G8. Figure that out. The first number is the number 2, which is for the row 2. The second number is 3, for the third column (which is C). The third and fourth numbers are relative to the first two numbers.
Also, you are using: appendRow([array]) which uses an array for the parameter. So you must make sure that the data is in the form of an array, or use something else.
Here is the link for getValues:
Google Documentation - getValues
The example is this code:
// The code below will get the values for the range C2:G8
// in the active spreadsheet. Note that this will be a javascript array.
var values = SpreadsheetApp.getActiveSheet().getRange(2, 3, 6, 4).getValues();
Logger.log(values[0][0]);
Here is code that seems to work:
function getAllData() {
var folder = DocsList.getFolderById("Your file ID");
var contents = folder.getFiles();
Logger.log("file length: " + contents.length);
var file;
var data;
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1")
sheet.clearContents();
sheet.appendRow(["Value from Sheet One", "Range of values from Sheet Two"]);
var numOfFiles = contents.length;
for (var i = 0; i < numOfFiles; i++) {
file = contents[i];
Logger.log("count: " + i);
//Reset to null on every iteration
var onecell = null;
var theRange = null;
var theFileType = file.getFileType();
Logger.log("theFileType: " + theFileType);
if (theFileType==DocsList.FileType.SPREADSHEET) {
var sheet1 = SpreadsheetApp.open(file).getSheetByName("Sheet1");
var sheet2 = SpreadsheetApp.open(file).getSheetByName("Sheet2");
// The code below will get the values for the range A3:A9
// in the active spreadsheet. Note that this will be a javascript array.
onecell = sheet1.getRange('B4').getValue();
theRange = sheet2.getRange(1,3,1,6).getValues();
Logger.log('onecell: ' + onecell);
Logger.log('onecell[0][0]: ' + onecell[0][0]);
Logger.log('theRange: ' + theRange)
Logger.log('theRange[0][0]: ' + theRange[0][0])
var multipleValues = [theRange[0][0], theRange[0][1], theRange[0][2], theRange[0][3], theRange[0][4]];
Logger.log('multipleValues: ' + multipleValues);
sheet.appendRow([onecell, "'" + multipleValues]);
};
}
}
In the first column, it only enters one value into the sheet cell. In the second column, the cell gets multiple values put into it from the row. In other words, and entire rows values, and combined and put into one cell. I think that's what you want from the code.
If you try to put an array into a spreadsheet cell, instead of showing the array of values as text, it shows something like an object. So I put a quote in front of the values so the cell formatting would default to text.
My code seems to be working properly, except for line 22, which is writing an "emailSent" value to line 20 of the Google Spreadsheet that the script runs against, instead of line 2 of that spreadsheet, which is the only row that has data. Any ideas?
function sendEmails2() {
//trying to adopt code from this tutorial https://developers.google.com/apps-script/articles/sending_emails
//-believe there is an issue with where I am declaring getRange to work-
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var lastRow = 40; // Last row of data to process
// Fetch the range of cells A1:B10002
var dataRange = sheet.getRange(startRow, 1, lastRow, 10)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
var i = data.length;
for (i in data) {
var row = data[i];
var emailAddress = row[7]; // raised an error at one point
var isEmailSent = row[9]; //
var partTimeApproved = row[0]
if (partTimeApproved != '') { // prevents sending emails to non-approved rows. for some reason = 'Y' doesn't work but !='' does //... or does it
if (isEmailSent == '') { // Prevents sending duplicates and sending emails to non-approved rows
var subject = "Email Subject Test";
var message = "Email body test"; // Standard string
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 10).setValue('emailSent'); //this didn't work: -replacing startRow + i with row-
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
}
I understand that while the code I am adopting from uses
getrange(startRow + i, 10).setValue('emailSent');
however, I do not understand how it could be finding row 20 and am not sure how to proceed in fixing it.
First
var i = data.length; <-- makes no sense
for (i in data) { <-- since you override i here
And I have no clue what data actually is. I am assuming it is an array of strings with numbers. AKA ['0','1']
So when you are expecting 2 and you are getting 20, it sounds like you are adding a string to a number
> 2 + "0"
"20"
convert it to a number.
> 2 + parseInt("0", 10)
2
So in the code:
getrange(startRow + parseInt(i, 10), 10).setValue('emailSent');
I'm attempting to grab the values from a data range, loop over the data, match a value in that data, then based on a matching value, update cell located a few columns over.
I'm able to locate value to match, but I'm having a hard time understanding how to update the cell a few columns over. Below is the code I've gotten so far minus the .setValue piece.
var trackingSS = 'Spreadsheet 1';
var decisionSS = 'Spreadsheet 2';
function grabRequestID() {
var ss = SpreadsheetApp.openById(decissionSS);
var range = ss.getActiveSheet().getRange(ss.getLastRow(), 2, 1, 1)
var requestID = range.getValue();
return requestID;
}
function managersDecision() {
var ss = SpreadsheetApp.openById(trackingSS)
var sheet = ss.getSheetByName('Requests');
var data = sheet.getDataRange().getValues();
var requestID = grabRequestID();
for (var i=0; i < data.length; i++) {
for (var j=0; j < data[i].length; j++) {
if (data[i][j] == requestID) {
Logger.log('found it');
}
}
}
}
As you can see there are two functions. managersDecision() reads in all the data from spreadsheet 1. It then calls grabRequestID() and uses this value (from spreadsheet 2) as the matching criteria as it loops over the data from spreadsheet 1. Currently it will locate and find the match.
What I want to have happen now, is based on the match, go over two columns in the same row and update the cell value to "approved" or "denied" based on successfully finding a match.
I'm a bit lost on how to get it to write to the cell. Should I try and identify the row its in and then work to set the value? Maybe grab the entire row the match is in (into an array) and then rewrite the row?
Any assistance would be appreciated..
To set a value you just need to take in count that you are working with an array that start at zero, but in the spreadsheet we start counting at one. You'll also need to be sure that you are trying to write in an existing cell. So prior writing, add the necessary column.
I didn't wrote the "denied" part as it was going through all the cell of the spreadsheet, but I wrote a second version of the managersDecision function where I only go through one column and here I took care of that denied part.
here the code:
var trackingSS = 'Spreadsheet1';
var decisionSS = 'Spreadsheet2';
function grabRequestID() {
var ss = SpreadsheetApp.openById(decisionSS);
var range = ss.getActiveSheet().getRange(ss.getLastRow(), 2, 1, 1)
var requestID = range.getValue();
Logger.log("requestID= "+requestID);
return requestID;
}
function managersDecision() {
var ss = SpreadsheetApp.openById(trackingSS)
var sheet = ss.getSheetByName('Requests');
var data = sheet.getDataRange().getValues();
var requestID = grabRequestID();
for (var i=0; i < data.length; i++) { // going through all the rows
for (var j=0; j < data[i].length; j++) { // this is going through all the cell of a row
if (data[i][j] == requestID) {
Logger.log('found it');
var row = Number(i)+1;
var col = Number(j)+1+2;
while(sheet.getMaxColumns()<col){
sheet.insertColumnsAfter(sheet.getMaxColumns(),col-sheet.getMaxColumns());
}
sheet.getRange(row, col).setValue("approved");
}
}
}
}
function managersDecision2() {
var ss = SpreadsheetApp.openById(trackingSS)
var sheet = ss.getSheetByName('Requests');
var data = sheet.getRange("A:A").getValues()
var requestID = grabRequestID();
var col = 1+2;
while(sheet.getMaxColumns()<col){
sheet.insertColumnsAfter(sheet.getMaxColumns(),col-sheet.getMaxColumns());
}
for (var i=0; i < data.length; i++) { // going through all the rows
var row = 1+i;
if (data[i][0] == requestID) {
Logger.log('found it');
sheet.getRange(row, col).setValue("approved");
}
else if(data[i][0] !=""){
Logger.log(row)
sheet.getRange(row, col).setValue("denied");
}
}
}