I think this one is pretty simple, but I am new to this, so I am not sure where to go from here :)
I have a Google Sheet, with some data (pretty large sheet). I want a script that check whether the number in a cell (column I) is larger than the number in the same row, but another column (column D).
Imagine two columns with 5 rows: Column D = (3, 3, 3, 3, 3) and Column I = (2, 2, 7, 2, 2)
SO in this example, I want that the script to tell me that I have problem in "Row 3", because the number in Column I, Row 3 is larger than the number in Column D, Row 3 :)
This is what I have:
function Check() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var lr = sheet.getLastRow();
var Summary;
for (var i=6; i<lr; i++) {
if (sheet.getRange(i,9) > sheet.getRange(i,4)){
summary.setvalue("Problem")
}
}
}
I want it to start in row 6, because my data starts here. I am only checking column I (therefore 9) and column D (therefore 4)
I am not sure what to do with my for loop and If statement from here? SO now the code is checking every row in column 4 (D) and column I (9), but how do I store store the value, whenever the number in column 9 is larger than the number in column 4? And I also want an output, with all rows where we have a problem, when the code is done? If we don't have any problem, I want a message saying: "no problem"
Can anybody guide me from here?
If your output can be set in the sheet (let say in column "J"), you can use this:
function Check() {
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getActiveSheet();
var maxRows = sheet.getMaxRows(); // get the number of rows
var startRow = 6;
var diffCol1 = 'D';
var diffCol2 = 'I';
var outputCol = 'J';
var outputStrOK = 'no problem';
var outputStrKO = 'problem';
var outputRange = []; // stored values
for (var i = startRow; i <= maxRows; i++) {
var valueA = sheet.getRange(diffCol2+i).getValue();
var valueB = sheet.getRange(diffCol1+i).getValue();
// add controls on values then
if (valueA > valueB) {
outputRange.push([outputStrKO]);
} else {
outputRange.push([outputStrOK]);
}
}
// setting a range of values is much faster than fulfilling cell by cell in loop
sheet.getRange(outputCol+startRow+':'+outputCol+maxRows).setValues(outputRange);
}
you should have this as a result:
#|D|I|J
6|9|4|problem
7|4|9|no problem
Related
after my last question I'm facing a problem with copying rows.
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('ws1');
var startRow = 4;
var lastRow = sheet.getLastRow();
var numRows = lastRow - startRow + 1;
var lastCol = sheet.getLastColumn();
var dataSetValues = sheet.getRange(startRow, 1, numRows, lastCol).getValues();
for (var i = 0; i < numRows; i++){
let fVal = dataSetValues[i][5];
let gVal = dataSetValues[i][6];
let sum = +fVal + +gVal;
if (sum > 115) {
let row = dataSetValues[i];
}
}
What do I expect?
I wish set which columns to copy
I edited the code like this
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var sheet = spreadsheet.getSheetByName('ws1');
var startRow = 4;
var lastRow = sheet.getLastRow();
var numRows = lastRow - startRow + 1;
var lastCol = sheet.getLastColumn();
var dataSetValues = sheet.getRange(startRow, 1, numRows, lastCol).getValues();
for (var i = 0; i < numRows; i++){
let aVal = dataSetValues[i][0];
let bVal = dataSetValues[i][1]; // + other columns
let fVal = dataSetValues[i][5];
let gVal = dataSetValues[i][6];
let sum = +fVal + +gVal;
if (sum > 115) {
let row = dataSetValues[i];
var ssDest = spreadsheet.getSheetByName('ws2');
var rngDest = ssDest.getRange(ssDest.getLastRow()+1,1);
//start copy
rngDest.setValues(row)
}
}
I get this error
The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues
Thanks
Your script just needs a few changes made to it:
1. It is important to note that the setValues() method accepts as parameter a two dimensional array in the form of Object[][].
You are simply passing it a one-dimensional array, hence the The parameters (number[]) don't match the method signature for SpreadsheetApp.Range.setValues error you are receiving.
In order to fix this, you will have to transform row into a 2 dimensional array and making the following changes
From
rngDest.setValues(row)
To
rngDest.setValues([row])
2. You will have to specify exactly the number of rows and the number of columns expected in the destination range.
After making the change above, you will end up running into a The number of columns in the data does not match the number of columns in the range error which is again expected. This is due to the fact that the getRange method will also need the number of rows and the number of columns such that when using setValues it will know exactly the structure of the data to set.
If you take a look at the getRange method:
getRange(row, column, numRows, numColumns)
Returns the range with the top left cell at the given coordinates with the given number of rows and columns.
In order to fix this, a simple change has to be made in order to indicate exactly the number of rows and the number of columns:
From
var rngDest = ssDest.getRange(ssDest.getLastRow()+1,1)
To
var rngDest = ssDest.getRange(ssDest.getLastRow() + 1, 1, 1, row.length);
As you can see, the number of rows here is 1 (as you are copying the data one row at a time) and the number of columns is equal to row.length (as the row variable has all the values corresponding to one row at a time).
Reference
Apps Script Range Class - setValues();
Apps Script Sheet Class - getRange();
Apps Script Troubleshooting.
I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))
UPDATE WITH a-change 's response and code
I am working on a function that will let me select a range in a sheet in Google Sheets and then paste the values that I am interested in into a specific order on another sheet.
Suppose RawData (Sheet1) looks like this:
I want to grab the range RawData!A1:L15, so basically everything that is that picture.
Afterwards I want to print it in another sheet (Sheet2 called Analysis) like so:
So far this is the code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
This code results in values being pasted in the 'Analysis' with the same order as in the 'RawData' sheet. The idea is for the data to be able to be pasted in a trio format, with no spaces between values. So the first trio would be: A1 = 1, B1 = 2, C1 = 3, A2 = 4, B2 = 5, C2 = 6, and so on.
A couple of things:
for (var row in values) { — here row is an index of an element, not the element itself. So it'll always be not equal to "X". Better to put it this way:
for (var i in values) {
var row = values[i];
}
Then you need to iterate over row to get to a single element and compare it with "X":
for (var i in values) {
var row = values[i];
for (var j in row) {
var value = row[j];
if (value !== "X") {
}
}
}
Next thing is pasting the value to your target sheet. The reason you are getting the same number in all the cells is that you're calling setValue on the whole A1:C8 cells range instead of one particular cell.
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i + 1), column).setValue(value);
column++;
}
}
}
targetSheet.getRange(i, j) here gives you a single-cell precision.
So alltogether your code would look something like:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("RawData");
var targetSheet = ss.getSheetByName("Analysis");
var range = sheet.getDataRange();
var values = range.getValues();
for (var i in values) {
var row = values[i];
var column = 1;
for (var j in row) {
var value = row[j];
if (value !== "X") {
targetSheet.getRange(parseInt(i) + 1, column).setValue(value);
column++;
}
}
}
}
See how the target sheet is set as a variable instead of using a range on the source sheet — it gives you more readability and freedom
It seems that when iterating like for (var i in row) i is considered to be a string so the parseInt call
column variable is needed to make sure there are no empty cells in the target sheet
I've also changed sheet.getRange(1,1,15) to sheet.getDataRange() to make sure your code gets all the data in the sheet
The approach of setting values into single cells separately is not optimal. It should work for you in your case as the data range seems pretty small but as soon as you get to hundreds and thousand of rows, you'll need to switch to setValues, so you'll need to build a 2D-array before pasting the values. The tricky thing is that your resulting rows may have a variable number of items (depending on how many Xs are in a row) while setValues expects all the rows to be of the same length — it's possible to get round it of course.
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 have dozens of spreadsheets thousands of rows long and I want to subset them by deleting rows that do not satisfy a condition.
Let me put forward a simplified example. Say Row C has string values for department names at a university (eg "ANTHRO" is Anthropology, "ART-HIST" is Art History, and so on). The university has many departments and the spreadsheet has many entries for each department, but I only want data for Anthropology and Art History. Therefore my task is to write a script that deletes each row that does not satisfy the condition RowC = "ANTHRO" or "ART-HIST".
Problem is, I don't know how in javascript/google-apps-script to define a variable that takes a range of (string) values. One attempt saw me define a "cull" variable as an array containing the conditions the script will judge the data on:
var rowsDeleted = 0;
var keep = ["ANTHRO", "ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[2] != keep) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
Yet it did not work. I know I could simply write if(row[2] != "ANTHRO" || != "ART"), but in reality there are much more than two conditions. Defining the so-called "cull" variable seems more efficient.
Any insights as to why the array-approach did not work? Thank you.
You could try using indexOf. If the row value isn't inside the array, it will return a value of -1, otherwise will return the index.
var rowsDeleted = 0;
var keep = ["ANTHRO",
"ART-HIST"];
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (keep.indexOf(row[2]) === -1) {
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
To me, what you're trying to do really is to filter a collecion of data based on speicific conditions. First you can store thousands rows of data into an array, and each element of the array can be a object to represent a row of many columns. And then you can filter the array by condition that whether a row contains string "ANTHRO" or "ART-HIST". A possible code implementaiton is:
var data = [
{ DEPT_ID: 1, ABBR_NAME: "ANTHRO", FULL_NAME: "Anthropology", StudentsNO: 240 },
{ DEPT_ID: 2, ABBR_NAME: "ART-HIST", NAME: "Art History", StudentsNO: 200 },
{ DEPT_ID: 3, ABBR_NAME: "MATH", FULL_NAME: "Mathematics", StudentsNO: 50 },
{ DEPT_ID: 4, ABBR_NAME: "CS", NAME: "Computer Science", StudentsNO: 79 }
];
function isAnthroOrArtHist(element) {
return ["ANTHRO", "ART-HIST"].indexOf(element.ABBR_NAME) >= 0;
}
var newData = data.filter(isAnthroOrArtHist);
I sort of do the same thing where I pull a list of Liquor Inventory to another tab along with a VENDOR name and if the VENDOR name isn't listed, then it hides the row
Here's what I used. You might be able to tweak it to where you need.
function liquorOrderGuideWorking() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('LIQUOR ORDER');
// Rows start at "1" - this will delete the first two rows
//sheet.deleteRows(2, 567);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var liquorInventory = ss.getSheetByName('LIQUOR INVENTORY');
var liquorInventoryRange = liquorInventory.getRange('B6:C573'); //Holds Vendor & Item Name
var liquorInventoryTotalRange = liquorInventory.getRange('I6:I573'); //Holds QTY of each item
var liquorOrder = ss.getSheetByName('LIQUOR ORDER'); //Gets the new sheet
var liquorOrderRange = liquorOrder.getRange('A2:B569'); //Places Vendor & Item Name
var liquorOrderQTYRange = liquorOrder.getRange('C2:C569'); //Places QTY
liquorInventoryRange.copyTo(liquorOrderRange, {contentsOnly:true})
liquorInventoryTotalRange.copyTo(liquorOrderQTYRange, {contentsOnly:true})
var s = ss.getSheetByName('LIQUOR ORDER');
var lastCol = s.getLastColumn();
var lastRow = s.getLastRow();
// assumes headers in row 1
var r = s.getRange(2, 1, lastRow - 1, lastCol);
// Note the use of an array
r.sort([{ column: 1, ascending: true }, { column: 2, ascending: true}]);
var ssHide = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ssHide.getSheetByName('LIQUOR ORDER');
var maxRows = sheet.getMaxRows();
//show all the rows
sheet.showRows(1, maxRows);
//get data from column B
var data = sheet.getRange('A:A').getValues();
//iterate over all rows
for(var i=0; i< data.length; i++){
//compare first character, if blank, then hide row
if(data[i][0].charAt(0) == ''){
sheet.hideRows(i+1);
}
}
Browser.msgBox('Liquor Order Guide','The Liquor Order Guide has been refreshed successfully.', Browser.Buttons.OK);
}