How to Change .getMergedRanges() From String to a Range? - google-apps-script

The following code will run very slow in my Google Sheets because my getRange is too large. Is there a way to only loop through the columns that are merged? I only want the for loop to get the number of columns in "yourRange" that are merged.
function getUpfrontCosts() {
var sheet = SpreadsheetApp.getActive().getSheetByName('LPB_COST');
var cl , count=0;
var yourRange = sheet.getRange("H13:UV13");
for (var i = 1; i < yourRange.getNumColumns()+1; i++)
{
cl=yourRange.getCell(1, i);
if (cl.isPartOfMerge()){
if (cl.offset(15, 0).getBackground() == "#ff8300" && cl.getMergedRanges()[0].getCell(1, 1).getValue()=='Upfront Costs') {
count = count + cl.offset(15, 0).getValue();
}
else {
}
} else {
}
}
return count;
};
The second code is how I am trying to turn a string to a range. I am getting "Cell reference out of range" error
How can I change cl to not be a string and be a range?
function getUpfrontCosts()
{
var sheet = SpreadsheetApp.getActive().getSheetByName('LPB_COST');
var destSheet = SpreadsheetApp.getActive().getSheetByName('Top Level PN');
var cl , count=0;
var yourRange = sheet.getRange("I13:UZ13");
var mergedRanges = yourRange.getMergedRanges();
for (var i = 0; i < mergedRanges; i++){
}
var newRange = sheet.getRange(mergedRanges[i].getA1Notation());
Logger.log(newRange.getA1Notation());
for (var i = 0; i < newRange.getNumColumns()+1; i++){
cl=newRange.getCell(1, i);
Logger.log(newRange.getA1Notation());
if (cl.offset(15, 0).getBackground() == "#ff8300" && cl.getValue()=='Upfront Costs') {
count = count + cl.offset(15, 0).getValue();
}
else {
}
}
return count;
};
This is the line with the error
cl=newRange.getCell(1, i);

if you want to use merge cell range, you can do this:
function UntitledMacro1()
{
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var cl , count=0 ;
var yourRange = sheet.getRange("I13:UZ13");
var bb = yourRange.getMergedRanges();
for (a=bb[0].getColumn();a<bb[0].getLastColumn()+1;a++)
{
//Your actual columns from I13, for first merge range, here your cl,
//but if your range is ("13:13"), you don't need
//-sheet.getRange("I13").getColumn()+1
cl=yourRange.getCell(1, a-sheet.getRange("I13").getColumn()+1);
//For your offset 15
Logger.log(cl.offset(15, 0).getValue());
//on so on
}
};

Related

How to repeat each value in a range n times in another range? (Google App Script)

My Code:
function Repeatn() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet() ;
var mycount = ss.getRange(1, 1).getValue() ;
for (i=0; i!=7; i++){
var Pname = ss.getRange(i+5, 3).getValue() ;
if (Pname ==""){
break ;
}
else {
for (var k=0; k!=mycount; k++){
ss.getRange(k+1, 6).setValue(Pname) ;
}
}
}
}
[This is what I am trying to achieve]
[My current Output]
I cannot figure out how to paste each value from column C in column F one after the other. Need help.
Use Array().filter(), Array().forEach() and Array().push(), like this:
/**
* Repeats each row in rangeToRepeat timesToRepeat times and
* puts the result in rangeToFill.
*/
function repeatRows() {
const sheet = SpreadsheetApp.getActiveSheet();
const timesToRepeat = sheet.getRange('A1').getValue();
const rangeToRepeat = sheet.getRange('C5:C');
const rangeToFill = sheet.getRange('F1');
const values = rangeToRepeat.getValues()
.filter(String);
const result = [];
values.forEach(row => {
let count = timesToRepeat;
while (count) {
result.push(row);
count--;
}
});
rangeToFill
.offset(0, 0, result.length, result[0].length)
.setValues(result);
}

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);
}

Cell reference is out of range - But I use GetDataRange()

I've tried a bunch of solution but it always end with Cell ref is out of range.
I thought that GetDataRange() & GetLastRow() would cover the last non-blank cell but I'm stuck. I've tried to hardcode my cell values (from X to Y) but nothing seems to change.
var course;
var searchingCol;
function CalculateFurnitureList()
{
GetCourseString();
FindColumnNumber(course);
GetSpecialFurnituresValue();
}
function GetCourseString()
{
var activeSheet = SpreadsheetApp.getActiveSheet();
var courseType = activeSheet.getRange("A2").getValue();
Browser.msgBox('Generate furniture list for ' + courseType + ' course');
course = courseType;
}
function FindColumnNumber(ct)
{
var furnitureSheet = SpreadsheetApp.getActive().getSheetByName("MaterielCours");
var range = furnitureSheet.getDataRange();
var width = range.getWidth();
//Search every cell in the designated area to find the course name
for (var i = 1; i <= width; i++)
{
var data = range.getCell(1,i);
if (data.getValue() == ct)
{
Browser.msgBox('Trouvé ! ' + data.getValue() + ' se trouve en : ' + i +':1');
searchingCol = i;
}
}
}
function GetSpecialFurnituresValue()
{
Browser.msgBox(searchingCol);
var furnitureSheet = SpreadsheetApp.getActive().getSheetByName("MaterielCours");
var range = furnitureSheet.getDataRange();
var length = range.getLastRow();
for (var i = 1; i < length; i++)
{
var data = range.getCell(searchingCol, i);
if (data.getValue() !== "")
{
// Do nothing
}
else
{
if (data == "Abs")
{
Browser.msgBox('NTM');
}
else
{
// Calculate value
}
}
}
}
It's
var data = range.getCell(searchingCol, i);
in GetSpecialFurnituresValue() which causes the problem.
The weird thing is that the same process in FindColumnNumber is working perfectly.
(editing my original answer - I must have been tired!) In the function GetSpecialFurnitureValues, there are a few things.
The for loop uses i < length, but it should be <= or you will miss the last row:
// for (var i = 1; i < length; i++)
for (var i = 1; i <= length; i++)
Your column/rows were simply reversed. :)
// var data = range.getCell(searchingCol, i);
var data = range.getCell(i, searchingCol);
Hope that helps!

Insert table from gmail to google spreadsheet by google script

I recive an email with table many times in one day it can be 10x2 or 40x2 or something like this. Every time I need to paste this table in sheet start from cell A1/ Now I have this code, but it paste whole table to cell A1:
var SEARCH_QUERY = "label:inbox is:unread to:me subject:importnumbers";
// Credit: https://gist.github.com/oshliaer/70e04a67f1f5fd96a708
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
emails.push([msgs[j].getBody().replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '').replace(/^\s*/gm, '').replace(/\s*\n/gm, '\n')
]);
}
}
return emails;
}
function appendData_(sheet, array2d) {
sheet.getRange(1, 1).setValues(array2d);
}
function saveEmails() {
var array2d = getEmails_(SEARCH_QUERY);
if (array2d) {
appendData_(SpreadsheetApp.getActiveSpreadsheet().getSheetByName('numberlist'), array2d);
}
markArchivedAsRead ();
}
function markArchivedAsRead() {
var threads = GmailApp.search('label:inbox is:unread to:me subject:importnumberlist');
GmailApp.markThreadsRead(threads);
};
Please, help me to paste table with right dimensions.
Try this one.
Replace getEmails_(q) function
Code#1:
function getEmails_(q) {
var emails = [];
var threads = GmailApp.search(q);
for (var i in threads) {
var msgs = threads[i].getMessages();
for (var j in msgs) {
var arrStr = msgs[j].getBody()
.replace(/<\/tr>/gm, '[/tr]')
.replace(/<\/td>/gm, '[/td]')
.replace(/<.*?>/g, '\n')
.replace(/^\s*\n/gm, '')
.replace(/^\s*/gm, '')
.replace(/\s*\n/gm, '\n')
.split("[/tr]");
var line = [];
for (var i = 0; i < arrStr.length - 1; i++) {
line = arrStr[i].split("[/td]");
line.length -= 1;
emails.push(line);
}
}
}
return emails;
}
Replace appendData_ function
Code#2:
function appendData_(sheet, array2d) {
var h = array2d.length;
var l = array2d[0].length;
sheet.getRange(1, 1, h, l).setValues(array2d);
}
This code converts the html text into 2d array and then paste it into Spreadsheet.
Note 2020-06
Your email may (1) contain several tables or (2) a table with merged cells. In this case, the proposed script may cause an error:
The number of columns in the data does not match the number of columns
in the range
In this situation, you'll need to create another function to parse the message body. I suggest you see the HTML-code of your message and inspect it in Gmail:
Right Click > Instect
or [Ctrl]+[Shift]+[I]
The other approach in this situation is to paste data "as is", use sample function to convert any 2d array into rectangular:
function convert2ArrayToRectangular_(array2d)
{
// get max width
var res = [];
var w = 0;
for (var i = 0; i < array2d.length; i++)
{
if (array2d[i].length > w) {w = array2d[i].length;}
}
var row = [];
for (var i = 0; i < array2d.length; i++)
{
row = array2d[i];
if(array2d[i].length < w)
{
for (var ii = array2d[i].length; ii < w; ii++)
{
row.push('');
}
}
res.push(row);
}
return res;
}
Sample usage:
function test_get_email() {
var q = 'Test_table_to_sheets';
var array2d = getEmails_(q);
var sheet = SpreadsheetApp.openById('1BKkd5LwBYyGoi2um-S3pTCBKrUEko34m9vJu94K8uOQ').getSheetByName('Test_table_to_sheets2');
appendData_(sheet, convert2ArrayToRectangular_(array2d));
}

google apps script two columns summary

I have a google spreadsheet with two columns corresponding to lessons: the first with names of the porfessors (occasionally repeating themselves) and the second with numbers (number of hours). I would like to have as output two columns, the first with the names of the porfessors and the second with the sum of all the hours
I tried with the following code, but it seems to give me back two arrays with the initial colums, as if the condition if (names[names.length-1] == namesColumn[i]) is never met.
What am I doing wrong?
function resumeProfessors() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var namesColumn = sheet.getRange("C4:C31").getValues();
var lessonsColumn = sheet.getRange("G4:G31").getValues();
var names = [];
names.length = 0;
var lessons = [];
lessons.length = 0;
namesColumn.sort();
for (var i = 0; i < namesColumn.length; i++) {
if (names[names.length-1] == namesColumn[i]){
lessons[lessons.length-1] = lessons[lessons.length-1] + lessonsColumn[i];}
else{
sheet.getRange(i+4, 9).setValue(names[names.length-1] + namesColumn[i]);
names[names.length] = namesColumn[i];
lessons[lessons.length] = lessonsColumn[i];
};}
writeResume(names, lessons);
}
Ty
Given your use-case, I'd recommend using a Pivot table or the =QUERY formula.
However, assuming your input sheet looks something like this -
And the expected output is something like this -
You can try the below code -
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var input = ss.getSheetByName('Sheet1');
var output = ss.getSheetByName('Sheet2');
var inputValues = input.getDataRange().getValues();
Logger.log(inputValues)
for (var i = 1; i < inputValues.length; i++) {
var name = inputValues[i][0];
var totalHours = [];
for (var j = 0; j < inputValues.length; j++) {
var hours = inputValues[j][1];
if (name == inputValues[j][0]) {
totalHours.push(inputValues[j][1]);
}
}
var outputValues = output.getDataRange().getValues();
var newEntry = true;
for (var k = 0; k < outputValues.length; k++) {
if (name == outputValues[k][0]) {
newEntry = false;
}
}
if (newEntry) {
output.appendRow([name,totalHours.reduce(function(a, b) {return a + b})]);
}
}
}
Hope this helps.