Need to Eliminate Rows where the Value column is blank, Google Sheets - google-apps-script

I have a sheets document that I am unpivoting to make the data more readable. In its unpivoted form there are 6 headers, the last of which are Item and Value. I need to eliminate rows that do not have anything in the Value column.
Here is the equation that I am using.
=ARRAYFORMULA(split(flatten(transpose(query(transpose(Sheet1!A3:E20&"|"),,9^9))&"|"&Sheet1!F2:EU2&"|"&Sheet1!F3:EU20),"|"))
I have been able to take the table I get and use filter to create the table that I want, however, I need this all to happen in one equation as the computation time needed in the initial equation is already too long to process all the information. I am hoping that by filtering the results as they are made that it will also speed up the render time.
Here is the filter I have had success with:
=FILTER(A:G, G1:G<>"")
I am working with over 16,000 rows of data in the initial sheet.
The other strategy I am working on is using an apps script and macro to set the equation above in, set the filter, copy the filter table, paste it in another sheet, then go back, change the previous equation, and repeat. This strategy is also very slow.
Any advice is welcome. I am new to working with sheets so sorry if there is any obvious solution that I am missing.
Thank you for the help.

Delete rows where Value is blank
Assume one header row and data starts of row 2
function myfunk() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("SheetName");
const [hA,...vs] = sh.getDataRange().getDisplayValues();//assumed one header row and data start on row 2
const idx = {};
let d = 0;
hA.forEach((h,i) => {idx[h]=i;});
vs.forEach((r,i) => {
if(r[idx['Value']]=="") {
sh.deleteRow(i + 2 - d++);
}
});
}

Related

Using for and if loops in Google Apps Script

Dear programming Community,
at first I need to state, that I am not quite experienced in VBA and programming in general.
What is my problem? I have created a topic list in google sheets in order to collect topics for our monthly meeting among members in a little dance club. That list has a few columns (A: date of creation of topic; B: topic; C: Name of creator; ...). Since it is hard to force all the people to use the same format for the date (column A; some use the year, others not, ...), I decided to lock the entire column A (read-only) and put a formular there in all cells that looks in the adjacent cell in column B and sets the current date, if someone types in a new topic (=if(B2="";"";Now()). Here the problem is, that google sheets (and excel) does then always update the date, when you open the file a few days later again. I tried to overcome this problem by using a circular reference, but that doesn't work either. So now I am thinking of creating a little function (macro) that gets triggered when the file is closed.
Every cell in Column B (Topic) in the range from row 2 to 1000 (row 1 is headline) shall be checked if someone created a new topic (whether or not its empty). If it is not empty, the Date in the adjacent cell (Column A) shall be copied and reinserted just as the value (to get rid of the formular in that cell). Since it also can happen, that someone has created a topic, but a few days later decides to delete it again, in that case the formular for the date shall be inserted again. I thought to solve this with an If-Then-Else loop (If B is not empty, then copy/paste A, else insert formula in A) in a For loop (checking rows 1 - 1000). This is what I have so far, but unfortunately does not work. Could someone help me out here?
Thanks in advance and best regards,
Harry
function NeuerTest () {
var ss=SpreadsheetApp.getActive();
var s=ss.getSheetByName('Themenspeicher');
var thema = s.getCell(i,2);
var datum = s.getCell(i,1);
for (i=2;i<=100;i++) {
if(thema.isBlank){
}
else {
datum.copyTo(spreadsheet.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_VALUES, false);
}}
}
The suggested approach is to limit the calls to the Spreadsheet API, therefore instead of getting every cell, get all the data at once.
// this gets all the data in the Sheet
const allRows = s.getDataRange().getValues()
// here we will store what is written back into the sheet
const output = []
// now go through each row
allRows.forEach( (row, ind) => {
const currentRowNumber = ind+1
// check if column b is empty
if( !row[1] || row[1]= "" ){
// it is, therefore add a row with a formula
output.push( ["=YOUR_FORMULA_HERE"] )
} else {
// keep the existing value
output.push( [row[0]] )
}
})
Basically it could be something like this:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Themenspeicher');
var range = sheet.getRange('A2:B1000');
var data = range.getValues(); // <---- or: range.getDisplayValues();
for (let row in data) {
var formula = '=if(B' + (+row+2) + '="";"";Now())';
if (data[row][1] == '') data[row][0] = formula;
}
range.setValues(data);
}
But actual answer depends on what exactly you have, how your formula looks like, etc. It would be better if you show a sample of your sheet (a couple of screenshots would be enough) 'before the script' and 'after the script'.

How To Add The Ordinal Count To Values Set In Horizontal Non-Contiguous Ranges In Google Sheets With A Formula Or A Script? EDIT ADDRESS FUNCTION

Problem:
I'm trying to add each ordinal reference to a set of repeating values in each cells just above each value.
The values are organized in horizontal and non-contiguous order.
The illustration example I show below is simple for testing purposes, but the end use should be for hundreds of values/ranges, so it would be optimal to use a script or a simplified version of the formula I found.
Illustration Example:
Other Related Question and Solution:
I found that question and answers that address the same question but for vertical and contiguous values using the following formula as solution:
=COUNTIF(A$1:A1,A1)
=COUNTIF(A$1:A1,A1)&MID("thstndrdth",MIN(9,2*RIGHT(COUNTIF(A$1:A1,A1))*(MOD(COUNTIF(A$1:A1,A1)-11,100)>2)+1),2)
Calculate ordinal number of replicates
My Formula So Far:
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21))&":"&$P$21&(SUM(Q21,I22)-1)))
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21,I22))&":"&$P$21&(SUM(Q21,I22,I26)-1)))
=TRANSPOSE(INDIRECT($P$21&(SUM(Q21,I22,I26))&":"&$P$21&(SUM(Q21,I22,I26,I30)-1)))
I use the above formula and need to copy-paste it in the cell immediately above the 1st cell of each horizontal range.
I need to reference each cell in the SUM Functions part because the spreadsheet will act as a template, with new data sets that will be different each time.
Therefore the cells need to return output in some dynamic way (can't hardcode them).
The formula problem is it requires an ever growing number of cells reference as we get to new ranges. It becomes difficult for hundreds of horizontal ranges, because of the growing inline cells to add to the SUM Functions.
It is also prone to errors. And possibly it can break if rows or columns are added afterwards.
Trials:
I originally didn't think of using the INDIRECT Function (I never needed before). But I don't know any other Google Sheets function able to achieve the end results in a simpler way.
Questions:
What way to avoid the SUM Function method for the same result would you suggest, for a formula solution?
For a formula, what simpler function-s than the INDIRECT and/or SUM would be more efficient?
I also thought of using a script for doing that, but I can't put the whole idea into a manageable script concept process. What would you suggest if a script would be more appropriate?
Many thanks for your help!
The Sample Sheet:
Sample Sheet
EDIT:
I just found about the ADDRESS Function from this answer by Player0 (to help greatly simplify the INDIRECT function row and column references):
Google Sheets: INDIRECT() with a Range
References:
Excel INDIRECT Function
Google Sheets ADDRESS Function
I was able to create a script to show the ordinal number of the replicates but is only respective to one range. EDIT: I have modified it to also accept multiple row ranges. See updated answer below:
Script:
function showOrdinal(range) {
range = "A4:E12";
var values = SpreadsheetApp.getActiveSheet().getRange(range).getValues();
var output = [];
var order, subTotal;
values.forEach((x, i) => {
if(x.flat().filter(String).length) {
subTotal = values.slice(0, i + 1).flat().filter(String);
order = x.filter(String);
if (order[0] != '-') {
var row = order.map(x => {
return getNumberWithOrdinal(subTotal.filter(e => e == x).length);
})
row = [...row, ...Array(x.length - row.length)];
output.push(row);
if(output.length > 2)
output.splice(output.length - 2, 1);
}
order = [...order, ...Array(x.length - order.length)];
output.push(order)
}
else
output.push(x);
});
// console.log(output);
SpreadsheetApp.getActiveSheet().getRange(3, 21, output.length, output[0].length).setValues(output);
// return output;
}
// get ordinal number
// https://stackoverflow.com/a/31615643/14606045
function getNumberWithOrdinal(n) {
var s = ["th", "st", "nd", "rd"],
v = n % 100;
return n + (s[(v - 20) % 10] || s[v] || s[0]);
}
Output:
Note:
The range that is to be passed assumes that the first row should be the first range, not a blank one. And also the last row of the range should contain the last row of the data.
Any unrelated data should be starting with - on the first column so it can be allowed without processing it.

Trying to append rows what am I not grasping?

Summary:
I need to get a range of values from one sheet, then set them into the first empty row of the second sheet. If that row is full, then append to the next.
I can get the script to set values into their corresponding cells, but not append; and I've been able to append a single cell value into an empty cell, but not the entire range. What am I not comprehending?
The goal:
Basically I'm taking user input (lets say race data) and saving it to another sheet for referencing later. (possibly to average race times by weight and height or something)
Research: I'm a bit new to this, but I've also done a good bit of research, such as ben collins free introductory course and other questions from here (just to name a couple):
[Get Range] SpreadsheetApp: getRange and setValues
[Copy Range] How to copy row ranges in Google docs?
Code
Grab my data:
const data1 = sheet1.getRange("A2:F2").getValues(); //grab data
Sets the range where I want it, but doesn't append:
sheet2.getRange("A2:F2").setValues(data1) //gets range and sets the value
So I've tried:
function r () {sheet2.getRange(newRow,1,1,data1.length).setValues(data1)};
return (r);
and even added:
sheet2.appendRow(data1);
(newRow is essentially getLastRow)
The above gives me a row of set values, then appends this in the following row: [Ljava.lang.Object;#f2a6014
-which in my research I've found what it is, but I don't understand why it happens.
I know I'm overlooking something or arranging something incorrectly but I cant figure out what it is that I'm not understanding.
Using your first two example I wrote this script and it works:
function copydata() {
const ss=SpreadsheetApp.getActive();
const sheet1=ss.getSheetByName('Sheet1');
const sheet2=ss.getSheetByName('Sheet2');
const data1 = sheet1.getRange("A2:F2").getValues();
sheet2.getRange("A2:F2").setValues(data1);
}
And this works also assuming that you realize that data1 is only a single row but it's still a two dimensional array so you have to use data1[0] to extract the single dimensional array from the two dimensional array
function copydatatofrom() {
const ss=SpreadsheetApp.getActive();
const sheet1=ss.getSheetByName('Sheet1');
const sheet2=ss.getSheetByName('Sheet2');
const data1 = sheet1.getRange("A2:F2").getValues();
sheet2.getRange("A2:F2").setValues(data1);
sheet2.appendRow(data1[0]);
}

Google Apps Script extract specific columns from sheet and put into new sheet using filter & map -- slow

Thanks in advance for your help. I'm new to apps script.
I have a gsheet with 98 columns and 25000 rows. All I want to do is copy 24 of the 98 columns to a new sheet.
Currently I am using filter & map, and it works:
var data = sourceSheet.getDataRange().getValues(); //read all columns & rows into array
var filterData = data.filter(function(e, j){return j > 0 && e}).map(function(e){return [e[88], e[14], e[13], e[4], e[17], e[87], e[91], e[48], e[57], e[31], e[89], e[82], e[70], e[97], e[47], e[30], e[72], e[71], e[67], e[34], e[33], e[00], e[38], e[39]]}); //extract just the columns I want into a new array
but that 2nd line takes almost an hour to execute! I presume because it is processing every element 1-by-1, even though it is just to return each one.
I haven't tried getValue'ing and setValue'ing columns one at a time because everything I read says limit external calls and do everything in memory. And I can't imagine pushing elements 1-by-1 would be faster than filtering.
Suggestions for faster execution?
function doCopy(SpreadID, OrgSheet, DestSheet, OrgRange, Sql, DestCell) {
var mySpread = SpreadsheetApp.openById(SpreadID);
var myQry = '=QUERY(' + OrgSheet + "!" + OrgRange + ',\"'+ Sql + '\")';
var myDestSheet = mySpread.getSheetByName(DestSheet);
myDestSheet.getRange(DestCell).setFormula(myQry);
}
Sample to call, but the destination sheet must be blank:
doCopy(spreadsheetId,"Sheet1", "Sheet2", "A:G", "Select A, C, F", "A1");
Another way to look at the problem which might be more time-effective and easy to implement is the following :
Duplicate the original sheet into a new one with copyTo(spreadsheet) (https://developers.google.com/apps-script/reference/spreadsheet/sheet#copytospreadsheet). You might also want to check this post for more information about creating a sheet into the same spreadsheet (Google Script to Duplicate Sheet that is Not Active)
Delete the columns you don't want to keep with deleteColumn(columnPosition) or deleteColumns(columnPosition, howMany)(see https://developers.google.com/apps-script/reference/spreadsheet/sheet#deletecolumncolumnposition).
You might also want to start deleting columns from the right side of the sheet to make the implementation easier.
Let me know if it helps.

Finding column number of a cell with specific value

I currently have a code that can get the row number of a cell that contains a specific string. Here's an example of the code:
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("B:B").getValues();
var i=j=firstrow=lastrow=0;
for(i=0;i<values.length;i++)
for(j=0;j<values[i].length;j++) {
if(values[i][j]==5100) firstrow=i+1;
else if(values[i][j]=='EOL') lastrow=i-2;
}
I was wondering if it's possible to do something like above, but for columns, that way my script will not fall apart if an user accidentally move a column.
So, what are doing is using .getRange("B:B") to define that you want all rows in column B. Then using getValues() to return all of those rows as a multidimensional array(obviously this will only have one column - so you probably don't need that other for loop).
So instead you can just use .getRange(row, column) (where row and column are integers greater than 1), this way you can go through the spreadsheet one item at a time using getValue(). So you could initially look through the first row to find the column index you are after, and then look down the rows to find the data you require.
Something like this might work for you:
var ss = SpreadsheetApp.getActiveSheet();
var valueColumn;
for(i=1;i<ss.getLastColumn();i++) {
if (ss.getRange(1,i).getValue() == "ColumnName") {
valueColumn = i;
break;
}
}
//At this point I assume that we have a value in valueColumn, if not this won't work.
var values = ss.getRange(2, valueColumn, ss.getLastRow()).getValues();
var i=firstrow=lastrow=0;
for(i=0;i<values.length;i++) {
if(values[i][0]==5100) firstrow=i+1;
else if(values[i][0]=='EOL') lastrow=i-2;
}
One thing to keep in mind is that arrays are 0 based where as getRange(row,column[,rows]) is 1 based.
My spreadsheet is only small, so speed impacts of doing one call and getting all data is minimal, but if you are actually using a large sheet you might find one method works faster than another.