Google Apps Script Taking one random cell from each row - javascript

So I have a sheet as seen here.
https://docs.google.com/spreadsheets/d/1r2ogg1ldR0CFjOJ6mVObY-WRVipJx_X3pQ0yM2HYEog/edit?usp=sharing.
I am trying to write a script that goes through every row in range A1:D and chooses one random cell to put in the F column of the same row.
I am new to GAP so Im not sure how to write the exact script for that. This is what I got so far
function random() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('Sheet1'); //the sheet that has the data
var range = ss.getRange('A1:D'); //the range I need
var data = range.getValues();
for(var i = ; i < data1.length; i++) { //at this point im just guessing based on online codes
= Math.floor(Math.random()*(j+1)); //method of randomization
ss.getRange('the i row of F column').setValue(data[i][1]); //choosing the row that is being used, selecting the first item of the shuffled array
};
}

You've been pretty close to the solution:
function random() {
var sss = SpreadsheetApp.getActiveSpreadsheet();
var ss = sss.getSheetByName('Sheet1'); //the sheet that has the data
var range = ss.getRange(1,1,ss.getLastRow(), 4); //the range you need: 4 columns on all row which are available
var data = range.getValues();
for(var i = 0; i < data.length; i++)
{
var j = Math.floor(Math.random()*(data[i].length)); //method of randomization
var element = data[i][j]; // The element which is randomizely choose
ss.getRange(i+1, 6).setValue(element);
}
}
I've made some modification on your orignal code:
getLastRow() get the position of the last row which is use. Otherwise, you collect a array the size of your entire sheet.
The Math.random() function is multiply by the size of a row, so you can have as much column as you want.

Related

Copy Selected Columns in One Sheet and Add Them To Selected Columns in Another Sheet

I would like to create a simple google apps script to copy specific column into another sheets.
Previously I tried using getLastRow but I get stuck to modify it.
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
Here is my spreadsheet: https://docs.google.com/spreadsheets/d/1rGvmlKCmbjDSCLCC2Kujft5e4ngbSLzJd2NYu0sxISs/edit?usp=sharing
And here is the modified script so far:
function pasteMultiCol(sourceSheet, destinationSheet,sourceColumns,destinationColumns, doneColumn){
var sourceDataRange = sourceSheet.getDataRange();
var sourceDataValues = sourceDataRange.getValues();
var sourcesheetFirstRow = 0;
var sourceSheetLastRow = sourceDataRange.getLastRow();
var destinationSheetLastRow = destinationSheet.getDataRange().getLastRow();
var pendingCount = 0;
//Find the row start for copying
for(i = 0; i < sourceDataValues.length; i++){
if(sourceDataValues[i][doneColumn-1] === "Copied"){
sourcesheetFirstRow++;
};
if(sourceDataValues[i][doneColumn-1] === ""){
pendingCount++;
};
};
//Update Source sheet first row to take into account the header
var header = sourceSheetLastRow-(sourcesheetFirstRow + pendingCount);
sourcesheetFirstRow = sourcesheetFirstRow+header;
// if the first row equals the last row then there is no data to paste.
if(sourcesheetFirstRow === sourceSheetLastRow){return};
var sourceSheetRowLength = sourceSheetLastRow - sourcesheetFirstRow;
//Iterate through each column
for(i = 0; i < destinationColumns.length; i++){
var destinationRange = destinationSheet.getRange(destinationSheetLastRow+1,
destinationColumns[i],
sourceSheetRowLength,
1);
var sourceValues = sourceDataValues.slice(sourcesheetFirstRow-1,sourceSheetLastRow);
var columnValues =[]
for(j = header; j < sourceValues.length; j++){
columnValues.push([sourceValues[j][sourceColumns[i]-1]]);
};
destinationRange.setValues(columnValues);
};
//Change Source Sheet to Copied.
var copiedArray =[];
for(i=0; i<sourceSheetRowLength; i++){copiedArray.push(["Copied"])};
var copiedRange = sourceSheet.getRange(sourcesheetFirstRow+1,doneColumn,sourceSheetRowLength,1)
copiedRange.setValues(copiedArray);
};
function runsies(){
var ss = SpreadsheetApp.openById("1snMyf8YZZ0cGlbMIvZY-fAXrI_dJpPbl7rKcYCkPDpk");
var source = ss.getSheetByName("Source");
var destination = ss.getSheetByName("Destination");
var sourceCols = [4,5,6,7,8,9,10];
var destinationCols = [7,8,9,10,11,12,13];
var doneCol = 12
//Run our copy and append function
pasteMultiCol(source,destination, sourceCols, destinationCols, doneCol);
};
Your code is taken from my tutorial in my blog article Copy Selected Columns in One Sheet and Add Them To The Bottom of Different Selected Columns in Another Sheet and it just needs a tweak.
I think the issue might be that you have a bunch of formulas in other columns in your "Destination" Sheet tab. So getting the last row of the sheet will result in getting the last row considering all the data including your other formulas.
You might find this explanation in a follow up blog article I wrote helpful: Google Apps Script: Get the last row of a data range when other columns have content like hidden formulas and check boxes
In short, you can change the destinationSheetLastRow variable to something simple like this.
var destinationSheetLastRow = (()=>{
let destinationSheetFirstRow = 7; // The first row of data after your header.
//Get a sample range to find the last value in the paste columns.
let sampleRange = destinationSheet.getRange(destinationSheetFirstRow,
destinationColumns[0],
destinationSheet.getLastRow())
.getValues();
let sampleLastRow = 0;
while(sampleLastRow < sampleRange.length){
if (sampleRange[sampleLastRow][0] == ""){
break;
}
sampleLastRow++;
};
return sampleLastRow;
})()

Google scripts: How to put a range in setValue

I have used the example code from this link, to make the code below.
https://yagisanatode.com/2017/12/13/google-apps-script-iterating-through-ranges-in-sheets-the-right-and-wrong-way/
Most of the code is working as expected, except for the last row.
In Column E, I want to place the custom function =apiav() with the data from cell A.
However the code is returning =apiav(Range) in the Google sheet cells. (to be clear it should be something like =apiav(a1))
I tried everything i could think of and of course googled for hours, but i am really lost and can't find the right solution for this.
function energy(){
var sector = "Energy";
var ss=SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var rangeData = sheet.getDataRange();
var lastColumn = 2;
var lastRow = 999 ;
var sheet = ss.getSheets()[0];
var searchRange = sheet.getRange(2,2, lastRow-1 ,1 );
var ouputrange = sheet.getRange(2,4, lastRow-1 ,1 );
//clear range first
ouputrange.clear("D:D");
ouputrange.clear("E:E");
/*
GOOD - Create a client-side array of the relevant data
*/
// Get array of values in the search Range
var rangeValues = searchRange.getValues();
// Loop through array and if condition met, add relevant
// background color.
for ( i = 0; i < lastColumn - 1; i++){
for ( j = 0 ; j < lastRow - 1; j++){
if(rangeValues[j][i] === sector){
sheet.getRange(j+2,i+4).setValue("yes");
var formularange = sheet.getRange (j+2,i+1);
sheet.getRange(j+2,i+5).setValue('=apiav(' + formularange + ')');
}
};
};
};
Replace:
var formularange = sheet.getRange(j+2,i+1);
with:
var formularange = sheet.getRange(j+2,i+1).getA1Notation();
So you will be able to pass the cell reference instead of the range object.

Loop through range and set values

I would like to create a loop that goes through the range A1:A150 and sets the cell value to =readmessage(Sheet2!A1) with A1 changing dynamically with each cell. So with A2 it reads, Sheet2! A1.
function storeValue() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var range = sheet.getRange("A1:A150");
for (var i = 1; i < range.length; i++) {
range.setValue("=readMessage(Sheet2!"+range+")");
}
}
This doesn't work, nothing is written into the cells in the range.
I'm guessing it's something to do with range.setValue.
Don't confuse values with formulas. For values there is setValue(value) and for formulas there is setFormula(formula).
By the other hand var range = sheet.getRange("A1:A150"); assigns a Range object to the range variable. To get get it address use getA1Notation()
This should work if I understood you correctly:
function storeValue(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
for (var i = 1; i < 150; i++) {
range = sheet.getRange("A"+i);
range.setValue("=readMessage(Sheet2! A"+i+")");
} }
Otherwise comment below.

Google Sheet Script - Return header value if cell value found

Thank you in advance for your help.
I have a google sheet that contains header values in the first row. I have a script that is looking through the remainder of the sheet (row by row) and if a cell is a certain color the script keeps a count. At the end, if the count number is greater than a variable I set in the sheet the script will trigger an email.
What I am looking at trying to do, is to also capture the column header value if the script finds a cell with the set color? I'm sure I need to create an array with the header values and then compare the positions, I'm just not sure how to do so efficiently.
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var lastRow = dataSheet.getLastRow();
var lastColumn = dataSheet.getLastColumn();
//Project Range Information
var projectRange = dataSheet.getRange(6,3,lastRow-5,lastColumn);
var projectRangeValues = projectRange.getValues()[0];
var cellColors = projectRange.getBackgrounds();
//Student Information Range
var studentRange = dataSheet.getRange(6,1,lastRow-5,lastColumn);
var studentRangeValues = studentRange.getValues();
//Pull email template information
var emailSubject = ss.getRange("Variables!B1").getValue();
var emailText = ss.getRange("Variables!B2").getValue();
var triggerValue = ss.getRange("Variables!B4").getValue();
var ccValue = ss.getRange("Variables!B5").getValue();
//Where to Start and What to Check
var colorY = ss.getRange("Variables!B6").getValue();
var count = 0;
var startRow = 6;
//Loop through sheet and pull data
for(var i = 0; i < cellColors.length; i++) {
//Pull some information from the rows to use in email
var studentName = studentRangeValues[i][0];
var studentBlogUrl = studentRangeValues[i][1];
var studentEmail = studentRangeValues[i][2];
var studentData = [studentName,studentBlogUrl];
//Loop through cell colors and count them
for(var j = 0; j < cellColors[0].length ; j++) {
if(cellColors[i][j] == colorY) {
/*This is where I feel I need to add the array comparisons to get the header values */
count = count + 1;
};//end if statement
};//end for each cell in a row
//If the count is greater than trigger, send emails
if (count >= triggerValue) {
//A call to another function that merges the information
var emailBody = fillInTemplateFromObject(emailText, studentData);
MailApp.sendEmail({
to: studentEmail,
cc: ccValue,
subject: emailSubject,
htmlBody: emailBody,
});
} else {};
//reset count to 0 before next row
count = 0;
};//end for each row
};
EDIT:
I have updated the above sections of the code to based on the responses:
//Header Information
var headers = dataSheet.getRange(4,4,1,lastColumn);
var headerValues = headers.getValues();
var missingAssignments = new Array();
In the for loop I added:
//Loop through cell colors and count them
for(var j = 0; j < cellColors[0].length ; j++) {
if(cellColors[i][j] == colorY) {
//This pushes the correct information into the array that matches up with the columns with a color.
missingAssignments.push(headervalues[i][j]);
count = count + 1;
};//end if statement
};//end for each cell in a row
The issue I am running into is that I am getting an error - TypeError: Cannot read property "2" from undefined. This is being caused by the push in the for loop as the script moves to the next row. I am unsure why I am getting this error. From other things I have read, the array is set as undefined. I have tried to set the array to empty and set it's length to 0, but it does not help. I don't think I understand the scoping of the array as it runs through.
EDIT:
Figured it out, the "i" should not iterate. It should read:
missingAssignments.push(headervalues[0][j]);
The end of the first for loop I clear the array for the next row.
missingAssignments.length = 0;
You should get the values of the entire sheet. Then use the shift method to get just the headers. It is hard for me to completely understand your intent without more information about your sheet. Let me know if I can provide more information.
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var lastRow = dataSheet.getLastRow();
var lastColumn = dataSheet.getLastColumn();
//below gets the whole sheet and shifts off the first row as headers
var fullSheet = dataSheet.getDataRange().getValues();
var headers = fullSheet.shift();
//then in your loops you can check against the index of the headers array
Spreadsheets with Apps Scripts is really slow especially if you have lots of data to read.
Check these tips from Apps doc:
Use batch operations
Scripts commonly need to read in data from a spreadsheet, perform
calculations, and then write out the results of the data to a
spreadsheet. Google Apps Script already has some built-in
optimization, such as using look-ahead caching to retrieve what a
script is likely to get and write caching to save what is likely to be
set.
You can write scripts to take maximum advantage of the built-in
caching, by minimizing the number of reads and writes. Alternating
read and write commands is slow. To speed up a script, read all data
into an array with one command, perform any operations on the data in
the array, and write the data out with one command.
Here's an example — an example you should not follow or use. The
Spreadsheet Fractal Art script in the Gallery (only available in the
older version of Google Sheets) uses the following code to set the
background colors of every cell in a 100 x 100 spreadsheet grid:
// DO NOT USE THIS CODE. It is an example of SLOW, INEFFICIENT code.
// FOR DEMONSTRATION ONLY
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
xcoord = xmin;
for (var x = 0; x < 100; x++) {
var c = getColor_(xcoord, ycoord);
cell.offset(y, x).setBackgroundColor(c);
xcoord += xincrement;
}
ycoord -= yincrement;
SpreadsheetApp.flush();
}
The script is inefficient: it loops through 100 rows and 100 columns,
writing consecutively to 10,000 cells. The Google Apps Script
write-back cache helps, because it forces a write-back using flush at
the end of every line. Because of the caching, there are only 100
calls to the Spreadsheet.
But the code can be made much more efficient by batching the calls.
Here's a rewrite in which the cell range is read into an array called
colors, the color assignment operation is performed on the data in the
array, and the values in the array are written out to the spreadsheet:
// OKAY TO USE THIS EXAMPLE or code based on it.
var cell = sheet.getRange('a1');
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
xcoord = xmin;
colors[y] = new Array(100);
for (var x = 0; x < 100; x++) {
colors[y][x] = getColor_(xcoord, ycoord);
xcoord += xincrement;
}
ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
The
inefficient code takes about 70 seconds to run. The efficient code
runs in just 1 second!
If you're looking at the Spreadsheet Fractal Art script (only
available in the older version of Google Sheets), please be aware that
we made a minor change to it to make this example easier to follow.
The script as published uses the setBackgroundRGB call, rather than
setBackgroundColor, which you see above. The getColor_ function was
changed as follows:
if (iteration == max_iteration) {
return '#000000';
} else {
var c = 255 - (iteration * 5);
c = Math.min(255, Math.max(0, c));
var hex = Number(c).toString(16);
while (hex.length < 2)
hex = '0' + hex;
return ('#' + hex + '3280');
}

Google Sheets Script - Get first blank cell

I want to find the first blank cell in a range. This code works, except it seems to be linked to column B.
function selectFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
sheet.setActiveSelection(sheet.getRange("D"+getFirstEmptyRow()))
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet();
var range = sheet.getDataRange();
var values = range.getValues();
var row = 26;
for (var row=26; row<values.length; row++) {
if (!values[row].join("")) break;
}
return (row+1);
}
When I run it, it selects the cell in D, however finds the blanks based on B.
Say we are working in rows 10-20. In column B there is stuff in 10-12. I run this (which should run on D as far as I can see), it will select D13 (because there is stuff in B10:B12. Even though nothing is in D, it should select D10.
UPDATE:::
So I made some changes you suggested.
function selectFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
sheet.setActiveSelection(sheet.getRange("B"+getFirstEmptyRow()))
}
function getFirstEmptyRow() {
var sheet = SpreadsheetApp.getActiveSheet();
var lastRow = sheet.getLastRow();
//getRange(row, column, numRows, numColumns)
var range = sheet.getRange(28, 2, lastRow - 28, 1);
var values = range.getValues();
var row = 28;
for (var i=0; i<values.length; i++) {
if (!values[i].join(" ")) break;
}
return (row+1);
}
It always selects row 29 (I assume because it's starting on 28 and I have return(row+1)?
This line:
var row = 26;
hard codes a starting number of 26. The loop count starts at 26:
for (var row=26; row<values.length; row++) {
If you want the loop to loop the number of times from a start row of 10, to the last row, then change that line to this:
for (var i=0; i<values.length; i++) {
Your code was actually defining the variable row twice. Once just above the for loop, and once inside of the for loop. The row variable shouldn't be used in the loop at all. The for loop needs it's own counter.
In this line of code:
for (var row=26; row<values.length; row++) {
The JavaScript Array join() Method returns a string from an array.
The getDataRange() method from this line of code:
var range = sheet.getDataRange();
Returns a two dimensional array of all the rows and columns in the sheet. The range starts in the upper left hand side of data in the sheet. If you want to exclude the columns up to and including column C, then use something different to get a different range.
So, I would change this line:
var range = sheet.getDataRange();
Maybe change the code to:
var lastRow = sheet.getLastRow();
//getRange(row, column, numRows, numColumns)
var range = sheet.getRange(10, 4, lastRow - 10, 1);
The above code will get a two dimensional array of data, starting in row 10, Column 4, and get 10 less rows than the total number of rows, and only get one column of data from column D (4th column)

Categories

Resources