I grabbed this script from: https://developers.google.com/apps-script/articles/sending_emails
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 3)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = row[1]; // Second column
var emailSent = row[2]; // Third column
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
I'm wondering if I would be able to make a dynamic subject line (per email). Is that possible?
Thank you!
James
Sure. Assuming that you put the value of the subject in the next column (D from you example), just do something like the following:
// This constant is written in column C for rows for which an email
// has been sent successfully.
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // First row of data to process
var numRows = 2; // Number of rows to process
// Fetch the range of cells A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 3)
// Fetch values for each row in the Range.
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[0]; // First column
var message = row[1]; // Second column
var emailSent = row[2]; // Third column
var subject = row[3];
if (emailSent != EMAIL_SENT) { // Prevents sending duplicates
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Make sure the cell is updated right away in case the script is interrupted
SpreadsheetApp.flush();
}
}
}
Related
I need to update a file automatically that already has data in it.
The document is filled with an SQL data base thanks to the code below
However, I want it to update itself everyday without deleting any data that are already in the document and only adding new ones (don't want any duplicates).
function readData(db, queryString) {
//connect to the database
var server = 'your-servername-OR-serverPublicIpAddress';
var username = 'your-sql-username';
var password = 'your-password';
var dbUrl = 'jdbc:sqlserver://' + server + ':1433;databaseName=' + db;
var conn = Jdbc.getConnection(dbUrl, username, password );
//query the data
var stmt = conn.createStatement();
var exec_query = stmt.executeQuery(queryString);
var metaData = exec_query.getMetaData();
var numCols = metaData.getColumnCount();
//save query data to an array
var result=[]; //initiate a blank array
//save the column header
header = []; //initiate the header row
for (var col = 0; col < numCols; col++) {
header.push(metaData.getColumnName(col + 1)); //add the name of each column to the header row
};
result.push(header);//after the header row is formed, put it to the result array
//save the data of each row
while (exec_query.next()) {
row_data = [];
for (var col = 0; col < numCols; col++) {
row_data.push(exec_query.getString(col + 1));//add data of each column to the row data
//Logger.log(row_data);
};
result.push(row_data); // add row data to result
//Logger.log(result);
};
exec_query.close();
return result
};
function pushDataToGoogleSheet(data, SheetName) {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = SpreadsheetApp.setActiveSheet(spreadsheet.getSheetByName(SheetName));
var lastRow = sheet.getLastRow();
sheet.getRange(lastRow+1, 1, data.length, data[0].length).setValues(data);
sheet.getDataRange().removeDuplicates();
};
function main() {
db = 'YOUR_DATABASE_NAME'
SQLquery = 'YOUR_SQL_QUERY'
raw_statistics = readData(db, SQLquery); //get raw statistics
pushDataToGoogleSheet(raw_statistics, 'YOUR_SHEET_NAME'); //push to google sheet
};
function main() {
db = 'YOUR_DATABASE_NAME'
SQLquery = 'YOUR_SQL_QUERY'
raw_statistics = readData(db, SQLquery); //get raw statistics
pushDataToGoogleSheet(raw_statistics, 'YOUR_SHEET_NAME'); //push to google sheet
};
However, in the pushDataToGoogleSheet it says that it can't define the length. So, I don't know if I put the right thing for data or not or if there is an issue in my code...
Do you have an idea ?
Thank you for your help !
Hello I'm brand new to the script world!
Problem: Vlookup return is not editable.
I tried IndexMatch without success.
I am making a truck maintenance file. The trucks have hour and kilometer trackers that are updated hourly in sheets. Our goal is to enter truck unit number and have the date, hours, and kilometers populate our mechanics notes. From Data entry sheet a button will enter that data into each units maintenance page. Vlookup returned the right results but if an adjustment on date, hours, or kilometers needs to be made the cell can't be edited.
I am looking for a hand in setting this up.
I will share the sheet.
Search Key 'Data Entry A2'
Range 'Data Entry A8:C17'
Index 'Data Entry A8:A17'
Thanksenter link description here
function onEdit(e)
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Menu");
var activeCell = sheet.getActiveCell();
var col = activeCell.getColumn();
var row = 2;
if (col == 1 ) { // update when change in column 1, adapt if neededfunction
CopyRow() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var menuSheet = ss.getSheetByName("Menu");
var inventorySheet = ss.getSheetByName("Inventory");
menuSheet.getRange("C2:D2").clear();
var partNumber = menuSheet.getRange(2,1).getValue();
var lastRow = inventorySheet.getLastRow() + 1;
var foundRecord = false;
for(var j = 2; j < lastRow; j++)
{
if(inventorySheet.getRange(j,1).getValue() ==partNumber)
{
var nextRow = menuSheet.getRange(2,3);
var getCopyRange = inventorySheet.getRange('B' + j + ':C' + j);
getCopyRange.copyTo(menuSheet.getRange(2, 3));
foundRecord = true;
}
}
if(foundRecord == false)
{
menuSheet.getRange(5,1).setValue(['(NO RECORDS FOUND)']);
}
}
}
so I'm a long way form a competent programmer, but I do dabble in google sheets scripts.
I previously had a script running on a timer trigger to hide rows based on dates found in column A.
function min() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("A:A").getValues();
var today = new Date().getTime() - 86400000
for (var i = s.getLastRow(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u < today) {
s.hideRows(i);
}
}
}
}
function max() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("A:A").getValues();
var today = new Date().getTime() + 86400000
for (var i = s.getLastRow(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u > today) {
s.hideRows(i);
}
}
}
}
function SHOWROWS() {
// set up spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet(), sheets = ss.getSheets();
for(var i = 0, iLen = sheets.length; i < iLen; i++) {
// get sheet
var sh = sheets[i];
// unhide rows
var rRows = sh.getRange("A:A");
sh.unhideRow(rRows);
}
}
this would be set to run at midnight every night so as to hide all rows not +-1 day from the current date.
I have now switched to using this document primarily through the android app I need to swap the input layout Moving dates from rows and values in columns to the inverse, values are now in rows and the dates are in columns, inputing vertical values would be much quicker....but honestly its more about understanding what im doing wrong thats really motivating my search for a answer.
can anyone help me modify my old script to work on this changed sheet.
my attempts fall flat..eg:
function min() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getSheetByName("OLD");
var v = s.getRange("1:1").getValues();
var today = new Date().getTime() - 86400000
for (var i = s.getLastColumn(); i > 1; i--) {
var t = v[i - 1];
if (t != "") {
var u = new Date(t);
if (u < today) {
s.hidecolumns(i);
}
}
}
}
example spreadsheet:
https://docs.google.com/spreadsheets/d/1EjRIeDPrG_qp1S9QhInl9r3G0cZx_16OvnTXORgA3n4/edit?usp=sharing
there is two sheets one with the old version of my layout called "OLD" and my new desired layout called "NEW"
thanks in advance to anyone able to help
This function hides a column whose top row date is less yesterday.
function hideColumnWhenTopRowDateIsBeforeYesterday() {
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sh=ss.getSheetByName("Sheet1");
var rg=sh.getRange(1,1,1,sh.getLastColumn());
var vA=rg.getValues()[0];
var yesterday=new Date(new Date().getFullYear(),new Date().getMonth(),new Date().getDate()-1).valueOf();
vA.forEach(function(e,i){
if(new Date(e).valueOf()<yesterday) {
sh.hideColumns(i+1);
}
});
}
My Spreadsheet before hiding columns:
My Spreadsheet after hiding columns:
I have a column with dates and I want to conditionally color any cell that is older that 2 week yellow, and any that is older than 90 days red. I can't figure out how to do that.
Should be able to just add the conditions like any other. You can use the TODAY() function in excel and subtract:
[TestMethod]
public void Conditional_Formatting_Date()
{
//https://stackoverflow.com/questions/56741642/epplus-two-color-conditional-date-format
var file = new FileInfo(#"c:\temp\Conditional_Formatting_Date.xlsx");
if (file.Exists)
file.Delete();
//Throw in some data
var dataTable = new DataTable("tblData");
dataTable.Columns.AddRange(new[] {
new DataColumn("Col1", typeof(DateTime)),
new DataColumn("Col3", typeof(string))
});
var rnd = new Random();
for (var i = 0; i < 100; i++)
{
var row = dataTable.NewRow();
row[0] = DateTime.Now.AddDays(-rnd.Next(1, 100));
row[1] = $"=TODAY() - A{i +1}";
dataTable.Rows.Add(row);
}
//Create a test file
using (var package = new ExcelPackage(file))
{
//Make the stylesheet
var ws = package.Workbook.Worksheets.Add("table");
var range = ws.Cells[1, 1].LoadFromDataTable(dataTable, false);
ws.Column(1).Style.Numberformat.Format = "mm-dd-yy";
ws.Column(1).AutoFit();
//Add the calc check
var count = 0;
foreach (DataRow row in dataTable.Rows)
ws.Cells[++count, 2].Formula = row[1].ToString();
//Add the conditions - order matters
var rangeA = range.Offset(0, 0, count, 1);
var condition90 = ws.ConditionalFormatting.AddExpression(rangeA);
condition90.Style.Font.Color.Color = Color.White;
condition90.Style.Fill.PatternType = ExcelFillStyle.Solid;
condition90.Style.Fill.BackgroundColor.Color = Color.Red;
condition90.Formula = "TODAY() - A1> 90";
condition90.StopIfTrue = true;
var condition14 = ws.ConditionalFormatting.AddExpression(rangeA);
condition14.Style.Font.Color.Color = Color.Black;
condition14.Style.Fill.PatternType = ExcelFillStyle.Solid;
condition14.Style.Fill.BackgroundColor.Color = Color.Yellow;
condition14.Formula = "TODAY() - A1> 14";
package.Save();
}
}
Which gives this in the output:
I am assuming that you have the column number of the date column and number of rows in your records. Also, the following loop is under assumption that the first row is your column header and records begin from second row. Change the loop counter's initialization and assignment accordingly.
int rowsCount; //get your no of rows
int dateColNumber; //Assign column number in excel file of your date column
string cellValue;
DateTime dateValue;
DateTime today = DateTime.Now;
double daysCount;
for(int i=1;i<rowsCount;i++)
{
cellValue = ws.Cells[i + 1, dateColNumber].Text.ToString(); //First row is header start from second
if(DateTime.TryParse(cellValue,out dateValue))
{
daysCount = (today - dateValue).Days;
if(daysCount>90)
{
ws.Cells[i + 1,dateColNumber].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 1,dateColNumber].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.Red);
}
else if(daysCount>14)
{
ws.Cells[i + 1, dateColNumber].Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
ws.Cells[i + 1, dateColNumber].Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.Yellow);
}
}
}
I have four rows in an excel, first row is for the heading and rest of the three rows has values in it. I have entered the code in a way to avoid the header and read the rows which contains values only. However instead of fetching only three rows it comes with one additional null value rows as below, Why it is fetching the null values did I miss anything? Find the code and error message.
Message
PASSED: testShipment("Mumbai", "New York", "18000", "10000", "20000")
PASSED: testShipment("Mumbai", "Cochin", "2000", "30000", "5000")
PASSED: testShipment("Cochin", "Farah", "16000", "18000", "19000")
FAILED: testShipment(null, null, null, null, null)
Code
int TotalCol = sh.getRow(0).getLastCellNum();
int Totalrows = sh.getLastRowNum()+1;
String[][] data = new String[Totalrows][TotalCol];
DataFormatter formatter = new DataFormatter(); // creating formatter using the default locale
for (int i = 1; i < Totalrows; i++) {
Row r = sh.getRow(i);
for (int j = 0; j < TotalCol; j++) {
Cell c = r.getCell(j);
try {
if (c.getCellType() == Cell.CELL_TYPE_STRING) {
String j_username = formatter.formatCellValue(c);
data[i][j] = j_username;
System.out.println("data[i][j]" + data[i][j]);
} else {
data[i][j] = String.valueOf(c.getNumericCellValue());
String j_username = formatter.formatCellValue(c);
data[i][j] = j_username;
System.out.println("data[i][j] numeric val" + data[i][j]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Try with the below code like checking null condition
for (int k = 1; k <= totalRows; k++) {
String testCaseID = sheet.getRow(k).getCell(0).getStringCellValue();
if (testCaseID.equalsIgnoreCase(tcID)) {
for (int l = 1; l < totalCols; l++) {
String testData_FieldName = sheet.getRow(0).getCell(l).getStringCellValue();
if (testData_FieldName.equalsIgnoreCase(header)) {
cell = sheet.getRow(k).getCell(l);
if (cell != null) {
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC:// numeric value in excel
result = cell.getNumericCellValue();
break;
case Cell.CELL_TYPE_STRING: // string value in excel
result = cell.getStringCellValue();
break;
case Cell.CELL_TYPE_BOOLEAN: // boolean value in excel
result = cell.getBooleanCellValue();
break;
case Cell.CELL_TYPE_BLANK: // blank value in excel
result = cell.getStringCellValue();
break;
case Cell.CELL_TYPE_ERROR: // Error value in excel
result = cell.getErrorCellValue() + "";
break;
default:
throw new CustomException("The cell data type is invalid");
}
}
}
}
k = totalRows + 1;
}
}
You need to change either the data array declaration part or Totalrows calculation part. Currently, you have created 4 rows object and only 3 rows values are assigned and hence 4th row values are holding null value.
String[][] data = new String[Totalrows][TotalCol];
In your string array, you are not persisting the header value and storing only values. So, please modify your code with any one of the below options (I would suggest you to use option 1)
Option 1:
Remove the +1 from Totalrows variable and add the equal condition in your first for loop
//Removed the +1
int Totalrows = sh.getLastRowNum();
String[][] data = new String[Totalrows][TotalCol];
DataFormatter formatter = new DataFormatter(); // creating formatter using the default locale
//Condition is modified as i <= Totalrows
for (int i = 1; i <= Totalrows; i++) {
Option 2:
Change the data[][] declaration part
int Totalrows = sh.getLastRowNum()+1;
String[][] data = new String[Totalrows-1][TotalCol];
Here is the code that works, thanks to everyone for helping on this!
int TotalCol = sh.getRow(0).getLastCellNum();
int Totalrows = sh.getLastRowNum()+1;
//Entering minus one(-1) during data declaration ignores the first row as first row is a header
String[][] data = new String[Totalrows-1][TotalCol];
DataFormatter formatter = new DataFormatter(); // creating formatter using the default locale
for (int i = 1; i <Totalrows; i++) {
Row r = sh.getRow(i);
for (int j = 0; j < TotalCol; j++) {
Cell c = r.getCell(j);
try {
if (c.getCellType() == Cell.CELL_TYPE_STRING) {
String j_username = formatter.formatCellValue(c);
//Adding minus on(data[i-1]) helps to read the first cell which is (0,1), in this case (0,1) would not read the header since we have skipping the header from the table in the previous step on top, therefore the actual table starts from the second row.
data[i-1][j] = j_username;
System.out.println("data[i-1][j]" + data[i-1][j]);
} else {
data[i-1][j] = String.valueOf(c.getNumericCellValue());
String j_username = formatter.formatCellValue(c);
data[i-1][j] = j_username;
System.out.println("data[i-1][j] numeric val" + data[i-1][j]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
"