IMPORTRANGE excluding all rows that come after the first empty row - google-apps-script

I'd like to know how can I can import a range excluding all rows from the source that come after the first empty row.
For example, let's say I have a sheet with 200 rows, but the row 101 is empty, but from 102 to 200 all rows have values. I'd like to import only rows 1:100.
I need to do it independently of how many rows has the source sheet, because we regularly import data and the number of valid rows grows on every import.

it would be like this:
=ARRAYFORMULA(
IMPORTRANGE("ID_or_URL", "Sheet3!F1:F"&MIN(IF(
IMPORTRANGE("ID_or_URL", "Sheet3!F1:F")="", ROW(A:A), ))))

Adding to what's already here, if you would like to do this more generically using Apps Script, you can bind this to the sheet and run as you need.
Assuming that Column A is being used for the data in the Sheet to Copy to, you can find the first row which is blank by running
function obtainFirstBlankRow() {
var sheet = SpreadsheetApp.getActive().getSheetByName('SHEET-TO-COPY-TO');
// search for first blank row
var col = sheet.getRange('A:A');
var vals = col.getValues();
var count = 0;
while (vals[count][0] != "") {
count++;
}
return count + 1;
}
This will return a number corresponding to the first row in which the cell in the A column is blank - you can change which column it checks if you need by replacing A:A in sheet.getRange() to the range you need.
You can then use:
function myFunction() {
var startRow = 1; //assuming you want to copy from row 1
var endRow = obtainFirstBlankRow();
var startCol = 1; // assuming you want to copy starting at column A
var endCol = 26; // assuming copying values up to column Z
var startRowPaste = 1; //assuming you want to copy starting at row 1
var startColPaste = 1; // assuming you want to copy starting at column A
//example range to copy from
var rangeToCopy = SpreadsheetApp.openById('IDOfSheetToCopyFrom').getSheetByName('SheetToCopyFrom')
var rangeToCopy = rangeToCopy.getRange(startRow, startCol, endRow, endCol);
//make sure the range you're copying to is the same size
var newRange = SpreadsheetApp.openById('IDOfSheetToCopyTo').getSheetByName('SheetToCopyTo')
var newRange = newRange.getRange(startRowPaste, startColPaste, rangeToCopy.getNumRows(), rangeToCopy.getNumColumns());
newRange.setValues(rangeToCopy.getValues());
}
To copy the data to the new Range.

Related

Google app script for loop is extremely slow

I have a pivot table set up in a google sheet file where I've labeled the sheet pivot table 1. I want to get each value of the first column of the pivot, duplicate each values 12 times in an array and paste these values in the 3rd column of sheet 5. However, it seems extremely slow to do with the script never really completing (just takes 10+ minutes before I've cancelled it).
The pivot has approximately 3000 lines, which would result in a 3000 * 12 = 36000 array.
Any thoughts on how I can optimize this?
function test2() {
// activating current spreadsheet for use
var spreadsheet = SpreadsheetApp.getActive();
//empty array
var array_dept = []
// returns (integer #) the last row of the pivot table 1 sheet that has content
var last_row = spreadsheet.getSheetByName("Pivot Table 1").getLastRow();
// Get value in pivot table 1 from range of row 1 (dept name), column 1, all the way to last row
// Then paste it in sheet5 from row 1, column 3, all the way to the last row defined above
for (var i = 1; i < last_row; i++ )
{
//get value and then paste it in a destination
var value_dept = spreadsheet.getSheetByName("Pivot Table 1").getRange(i,1).getValue();
array_dept.fill(value_dept, -12 + (12*i) , 12*i)
}
destination_dept = spreadsheet.getSheetByName("Sheet5").getRange(1,3, last_row);
destination_dept.setValues(array_dept);
}
You don't need use a loop if you know the first row and the last row on the source column. You can just define the range:
var pivotRange = pivot.getRange(1,1,last_row)
var targetRange = target.getRange(1,3,last_row)
doc ref; this is just one of five methods to define a range.
In the OP script, there would be 3000xgetRange + 3000xgetValue. In this answer there are: 2xgetRange and 1 x getValue. This should account for a substantial amount of script processing. Of course, we know nothing of the rest of the spreadsheet (its size, formula, functions, triggers, etc). But all other things being equal, this should improve the performance of the script.
function test2() {
// activating current spreadsheet for use
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
var pivotSheetName = "Pivot Table 1"
var pivot = spreadsheet.getSheetByName(pivotSheetName)
//temporary array
var array_dept = []
// returns (integer #) the last row of the pivot table 1 sheet that has content
var last_row = pivot.getLastRow();
//Logger.log("DEBUG: last row in the pivot table:"+last_row)
var pivotRange = pivot.getRange(1,1,last_row)
// Logger.log("DEBUG: the range for the pivot range = "+pivotRange.getA1Notation())
var pivotData = pivotRange.getValues()
//Then paste it in sheet5 from row 1, column 3, all the way to the last row defined above
var targetSheetName = "Sheet5"
var target = spreadsheet.getSheetByName(targetSheetName)
var targetRange = target.getRange(1,3,last_row)
// Logger.log("DEBUG: the range for the target range = "+targetRange.getA1Notation())
targetRange.setValues(pivotData)
Logger.log("Done")
}

Unable to complete data move within apps script

Ive been messing with this google apps script for far too long and need some help.
I have a table on a sheet called options that starts on col A line 31 and is 3 col wide.
Col a is all checkboxes. I was able to write a script that checks to see which checkboxes are checked.
For each checked box it copies that rows data in b:c into an array.
Then opens an existing tab called Worksheet and is supposed to paste them in the first empty cell it finds in column b.
function createNamedRanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Worksheet");
var range = sheet.getRange("B2:C");
var namedRange = ss.setNamedRange("outputRange", range);}
function processSelectedRows() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Prompt Options");
var data = sheet.getDataRange().getValues();
var checkedRows = [];
for (var i = 30; i < data.length; i++) {
var row = data[i];
var checkbox = sheet.getRange(i + 1, 1).getValue() == true;
if (checkbox){
checkedRows.push([row[1], row[2]]);
} }
var worksheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Worksheet");
var pasteRange = SpreadsheetApp.getActiveSpreadsheet().getRangeByName("outputRange");
pasteRange.offset(worksheet.getLastRow(), 0).setValues(checkedRows);
}
The first row on the worksheet tab are headers. The first array to copy over is 11 rows. When I ran the script. I got an error that sat there was only 1 row in the range and I had 11 rows of data. Ok, I figured I neeeded to name a range. This table will be a different size every time. So I named this range outoutRange and no matter what size i make it I get error messages.
This is my latest error message and it is hitting the very last line of code
Exception: The number of rows in the data does not match the number of rows in the range. The data has 11 but the range has 1007.
You assistance is appreciated
Modification points:
If your Worksheet is the default grid like 1000 rows and 26 columns, I think that pasteRange is all rows like 1000. I thought that this might be the reason for your current issue.
In order to retrieve the last row of the columns "B" and "C" of "Worksheet" sheet, how about the following modification?
From:
pasteRange.offset(worksheet.getLastRow(), 0).setValues(checkedRows);
To:
var lastRow = pasteRange.getLastRow() - pasteRange.getDisplayValues().reverse().findIndex(([b, c]) => b && c);
worksheet.getRange(lastRow + 1, 2, checkedRows.length, checkedRows[0].length).setValues(checkedRows);
By this modification, the values of checkedRows is put to the next row of the last row of columns "B" and "C" of "Worksheet" sheet.

Check number of rows in named range and if more than 2, execute different script

Currently I have a script that will add a specified number of rows into a named range. Due to the fact that the named range will always have 1 row to start, adding in "3" rows will really only add 2. This is the expected outcome for the first time you run the script. Obviously if the script has been run already, and you are expecting to add 3 more rows, having it only add 2 is frustrating. I cannot seem to wrap my head around how this works.
Here is the functional code:
//Take the Account and number of holes from
// the sidebar and insert rows to the proper named range
function insertRowNext(account,n_rows) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName('Next Week'); // Change to your sheet name
//Replace space with underscore
account=account.replace(/ /g,"_");
var nameRange = 'Next Week!'+account;
n_rows-=1;
if(n_rows>0){
//add row
var range = ss.getRangeByName(nameRange);
ws.insertRowsBefore(range.getLastRow(),n_rows);
//Show all rows in the namedRange
ws.showRows(range.getRow(),range.getNumRows()+n_rows);
}
}
Here is the code that I modified but it broke the script.
// Take the Account and number of holes from
// the sidebar and insert rows to the proper named range
function insertRowNext(account,n_rows) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ws = ss.getSheetByName('Next Week');
//Replace space with underscore
account=account.replace(/ /g,"_");
if (n_rows == 2){
n_rows -= 1
var range = ss.getRangeByName(nameRange);
ws.insertRowsBefore(range.getLastRow(),n_rows);
//Show all rows in the namedRange
ws.showRows(range.getRow(),range.getNumRows()+n_rows);
}else if (n_rows < 2){
n_rows = 0
var range = ss.getRangeByName(nameRange);
ws.insertRowsBefore(range.getLastRow(),n_rows);
//Show all rows in the namedRange
ws.showRows(range.getRow(),range.getNumRows()+n_rows);
}else{
alert("unexpected number of rows -- please check template");
//throw error
}
}
I just need the script to check how many rows are in the selected named range, and if it is 2, subtract one from the amount to add. If the amount of rows in the named range is >2, do not subtract the amount of holes.
I don't know how your sheet is organized to produce such kind of behavior when inserting rows, but if you really want to check, you can use this code block:
var range = ss.getRangeByName(nameRange);
var rangeRows = range.getNumRows();
if (rangeRows == 2) {
ws.insertRowsBefore(range.getRow(),n_rows-1);
}
else if (rangeRows > 2) {
ws.insertRowsBefore(range.getRow(),n_rows);
}
This should be the output if n_rows = 3:

Google Sheets Script - paste values based on time trigger

I'm trying to do a few things with a piece of script but cannot get it all down.
In row 2 of my sheet I have a series of consecutive dates based on the first of each month (British notation) e.g 01/08/2016, 01/09/2016, 01/10/2016. I then have a formula in rows 14 and 15 which I would like to be fixed (copy / paste value) when today's date matches that in row 2.
I feel that I need to run a few things -
Schedule a script to run once per day to check if any value in row 2 is equal to today's date. If true then...
Copy / paste values of the numbers in rows 14 and 15 and the column where the date matches.
Maybe an index/match is needed to verify part 1 but I'm really in the dark on how to do it.
Thanks for any assistance.
I think that I've now managed to crack it. In row 1 I have put in a helper formula which checks the date in row 2 against today's date and puts "TRUEMONTH" in the cell if it matches. My clunky code is then as follows (it still needs some tidying up for the sheet refs etc) -
function ColCheck() {
var name = "TRUEMONTH";
var name1 = "PASTE_ME_AS_VALUE";
var range = SpreadsheetApp.getActiveSheet().getDataRange()
var values = range.getValues();
var range1 = SpreadsheetApp.getActiveSheet().getDataRange()
var values1 = range1.getValues();
for (var row in values) {
for (var col in values[row]) {
if (values[row][col] == name) {
for (var row1 in values1) {
for (var col1 in values1[row1]) {
if (values1[row1][col1] == name1) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var row = parseInt(row1) + 1;
var col = parseInt(col) + 1;
var source = ss.getActiveSheet().getRange([row],[col]);
source.copyTo(ss.getActiveSheet().getRange([row],[col]), {contentsOnly: true});
}
}
}
}
}
}
}

Find string and get its column

Let's say I have a lot of columns and one of them contains "impressions" string (on row 3). What I need to do is to:
1) Find the cell with "impressions" string
2) Get column number or i.e. "D"
3) Based on what I got paste a formula into i.e. D2 cell which gets AVERAGE from a range D4:D*last*
I couldn't find it anywhere so I have to ask here without any "sample" code, since I have no idea on how to achieve what I want. (3rd one is easy but I need to get that "D" first)
There's no way to search in Google Apps Script. Below is a function that will accomplish the first 2 parts for you (by iterating over every cell in row 3 and looking for "impressions"):
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1'); // insert name of sheet here
var range = sheet.getDataRange(); // get the range representing the whole sheet
var width = range.getWidth();
// search every cell in row 3 from A3 to the last column
for (var i = 1; i <= width; i++) {
var data = range.getCell(3,i)
if (data == "impressions") {
return(i); // return the column number if we find it
}
}
return(-1); // return -1 if it doesn't exist
}
Hopefully this will allow you to accomplish what you need to do!
The indexOf method allows one to search for strings:
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet() //whatever tab the code is run on
var data = sheet.getDataRange().getValues();
var header_row_num = 1; // TODO: change this to whichever row has the headers.
var header = data[header_row_num -1] //Remember JavaScript, like most programming languages starts counting (is indexed) at 0. For the value of header_row_num to work with a zero-index counting language like JavaScript, you need to subtract 1
//define the string you want to search for
var searchString = "impressions";
//find that string in the header and add 1 (since indexes start at zero)
var colNum = header.indexOf(searchString) + 1;
return(colNum);