Multiple Google Sheets Async Requests in Google Apps Script

While we can efficiently read and update multiple ranges in Google Sheets with the Google Sheets API Advanced Service in Google Apps Script, there is often occasions where I want to run multiple batch updates on different Google Sheet files.

As you have probably discovered, updating multiple sheets consecutively can be time-consuming and frustrating.  I wanted a way to read and update my Google Sheets asynchronously.

Why?

Let’s say I have a bunch of Google Sheets, one each for my users. Perhaps on a daily cadence, I want to gather aggregate data for their manager. Perhaps my manager wants me to add some new spreadsheet features to the current Google Sheets tools. I need an efficient way of updating and reading these sheets.

So was I successful?

Well, mostly.

I discovered that I could run batch updates on up to around 60 Google Sheets somewhat asynchronously.

How did I do this?

I discovered that while “UrlFetchApp.fetchAll()” does not claim to be asynchronous, benchmark research by Kanshi TANAIKE suggests that “fetchAll()” behaves asynchronously.

In conjunction with the Google Sheets API’s “spreadsheets.values” batchGet and batchUpdate requests, we can do a pretty good job updating up to around 60 Google Sheets very quickly.

Let’s dive in!

Continue reading “Multiple Google Sheets Async Requests in Google Apps Script”

Get Google Sheet Tab Name and ID with Google Sheets API Advanced Service with Apps Script

The Google Sheets API Advanced Service has a strange habit of switching between using Google Sheet names like 'Sheet1' and the sheet ID that you would see in the URL, like 482103711.

This can be problematic when working with complex processes in the Sheets API.

One of the first things I do when running a complex process is to make an initial call to the Sheets.Spreadsheets.get() method to generate an array of all of the sheet tabs by name and sheet ID. Yes, it is an additional call to the API but if we do it initially in conjunction with any other core requests from the spreadsheet then we only need to do it one time.

Continue reading “Get Google Sheet Tab Name and ID with Google Sheets API Advanced Service with Apps Script”

Append – Google Sheets API Advanced Service for Apps Script

In my last written tutorial, I covered how to create a Google Sheets Row Archiver with Google Sheets API Advanced Service and Apps Script and the very last task of this service was to append rows into an archive Google Sheet.

The standard approach to appending data in Google Apps Script is to use the apendRow() method inside the Sheet class using the SpreadsheetApp Service.

This approach is limited to appending a single row to the bottom of the selected sheet and the append will only occur after the last row in the entire sheet where there is text.

There are, of course, some other approaches to appending data and finding the last row. Here are some that I have covered:

Sheets API Spreadsheets.values.append()

As of the time of this tutorial, there are a number of documentation errors and bugs in the constructor classes in Google Apps Script for the append method for the Sheets API advanced service.

You can find more here on my bug reports:

The Video Tutorial

You can grab a copy of the starter sheet here!

The video:

 

The Benefits of Sheets API Append Method

Append multiple rows

With the advanced service, you can append multiple rows of data to your sheet.

Insert Rows or Overwrite Existing Rows

We can decide if we want to insert new rows after the end of our existing data or overwrite the existing empty spaces. This is particularly handy when used in conjunction with the below.

Select a target range

Say you have a dataset that you want to update that has a bunch of summary data below it. With the append method, you can select the range of the data all the way down to just above your summary data and append to it.

Insert data as Raw Data or User Formatted

You can also choose to either display the data as raw data input as is, or allow Google Sheets to format the data as if you are manually inputting it into each cell.

An Example

We will use this example to illustrate how the method works in the remainder of the tutorial.

Google Sheets API advanced service example sheet

Here we have a sheet of appendix data (Get it).

The most recent data ends on line 24. We also have a set of summary data starting on line 44.

Our doctor usually sends batches of data about the appendix of each patient they see in a week. They will append that cumulative data to the sheet at the end of each week.

Now have a quick look at the code.

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

The Append Method Parameters

The basic append method looks like this:

Sheets.Spreadsheets.Values.append({values: 2d_array}, sheet_ID, range_to_append, payload_object)

In our example, you can see that we have placed this method in a JavaScript Try…Catch statement to handle any returned errors gracefully (lines 20-31).

The ‘append’ method returns an object that looks like this:

Probably the most important bit of information that you will get back from this object is the updatedRanges property. Everything else you will be able to get before making the append request. This property will give you the range of the updated data in A1Notation. From this, you may want to apply formatting or data validation.

2d Array of Data to Append

The first parameter is a little counterintuitive in that you must first declare an object which contains the values property. This property contains the 2d array of values that will be appended.

In our example, we mix up our 2d array of data to illustrate how some of the parameters may affect the data presented in the sheet. (lines 3-8)

More on this later

Spreadsheet ID

Next, we insert the Google Sheet ID of our spreadsheet.

We set this in our ssID variable. Line 2

Range To append to

The third parameter is the range to append to. This one is a little weird too but after you understand it, you’ll see that it is super handy.

Providing this range allows the Sheets API to search for the first empty row after the last row of data only in the range that you select.

This means that it will ignore other columns not within its search range that may have an end position much further down the sheet.

It also means that you can set your append between two sets of data in the same column. I illustrate this in our example above where we have a set of data running from A4:D24 and then a set of summary data from A44:B50, but we will append only after the first set of data.

In our example, we set this range to Sheet1:A5:A45.

Payload Object

The payload object or as Google calls it, the parameters, contain an object of optional parameters that you can include in your project to alter the outcome of the append or return your data in a different way.

After some considerable tests, I found that many of the parameters do not affect how the data is displayed on the sheet. However, there are three of note.

valueInputOption (mandatory)

One mandatory parameter must be included in each append call:

valueInputOption: "USER_ENTERED" // USER_ENTERED or RAW line 12

Using the USER_ENTERED method will evaluate the 2d array and append it as if the user manually entered it into Google Sheet. So this 2d array:

… will look like this:

Google Sheets API USER_ENTERED
Google Sheets API USER_ENTERED

Here you can see that the string number has been converted to an actual number in the sheet and all of the formulas have been executed.

Whereas, if we selected the RAW option then the data would look like this:

Google Sheets API RAW
Google Sheets API RAW

Now, the data is represented almost exactly as we had it in the code. The only difference is that the numbers have been influenced by our Google Sheets format to display ‘cm’ after the number in column B when a number is found. Note that the number in B28 is in fact text because it was a string in the code.

Further, all formulas are escaped with a “'” to display them as text.

Finally, the dates are now strings and no Google Sheets Date Number Values. These too are escaped with a  “'“.

insertDataOption (optional)

One of the cool things with this ‘append’ method is that you can choose to either overwrite existing empty cells or insert whole new rows.

The insertDataOption allows us to do this by selecting between either “OVERWRITE” or “INSERT_ROWS”.

“OVERWRITE” simply appends over the current rows so our result in the example would look like this:

Google Sheets API insertDataOption OVERWRITE
Google Sheets API insertDataOption OVERWRITE

Notice that the new data is written over the empty spaces below the orange line at A25:D28.

Now, let’s run the script again with “INSERT_ROWS” this time noting where the orange line is and also where the summary down the bottom starts:

Google Sheets API insertDataOption INSERT_ROWS
Google Sheets API insertDataOption INSERT_ROWS

You can see now that 4 rows have been inserted directly below the last row of data. This has pushed the orange line down 4 rows. The data is then inserted into these new rows.

You can also see now that the summary data down the bottom has also moved down 4 rows.

includeValuesInResponse

You can also opt-in to include the values that you just added to your sheet back into your response object. So it might look like this:

If you chose the “USER_ENTERED” valueInputOption then the returned results will be transformed as you can see in the sample above.

By default, this option is set to false.

Also, note that using this option will slow down your process because it needs to bring the appended array back in its response to you.

Conclusion

As you can see the Google Sheets API Append method is a much more versatile approach to appending data in a Google Sheet in Apps Script. Of course, the API is not limited to Apps Script, the API can be used in other programming languages too.

I would love to hear how you used this ‘append’ method in your own projects. It really gives inspiration and insight for others. Feel free to make a comment below.

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

~ Yagi.

Creating a Google Sheets Row Archiver with Google Sheets API Advanced Service and Apps Script

I have a client project coming up shortly where I will be diving back into the Google Sheets API Advanced Service in Google Apps Script. As part of my refamiliarisation with the API, I was thinking about the minimum number of API calls it would take to efficiently create a Google Sheets line archiver based on a set of multi-column parameters.

Here’s the example and the starter sheet if you want to play along.

The Sample Data Google Sheet: The Space Ship Manufacturing Fleet Requests

Stardate 78183.10, it’s surprising spreadsheets have not been improved upon in the almost half millennia since their origin, but they are still everpresent while the inevitable decay of other software products is a little more than the dust of a bygone memory.

We’ve decided to create an archiving automation for our spaceship-building bids for our new space fleet. We want to remove any bids from the sheet that have been declined and are before “2025-04-30”, (Damn it! Timeline inconsistencies already!!!!).

Once, we find these rows to archive, we want to remove the selected rows and append them to our ‘Archive’ sheet.

Check out the sheet:

Archiving Script for Google Sheets in Apps Script and Sheets API advanced serviceThe Starter Sheet

So, how many API calls?

Continue reading “Creating a Google Sheets Row Archiver with Google Sheets API Advanced Service and Apps Script”

Find and Hide Rows in Google Sheets with Apps Script

Using the Spreadsheet App Class’ Text Finder Class to find and hide rows in Google Sheets containing target text in a cell can be a fast way to hide values. In many situations, this may be a faster way to hide rows based on cell value in Google Sheets with Google Apps Script than iterating through a range and extracting rows.

In this tutorial, we will cover 3 approaches to using the Text Finder class to hide rows. Each may be useful in its own circumstances.

This tutorial accompanies the YouTube video series of the same name. You can find the links to each of the related videos in each of the sections along with the starter Google Sheets so that you can play along.

V1 – Basic Find and Hide Rows based on cell values in Google Sheets

Starter Sheet

Version 1 – Starter Sheet

The Video

https://youtu.be/alI2f7w7xjU

The Code

Unlike when formatting rows and cell activation, – as we did in the previous tutorial – our fasted approach here is to hide and unhide sheets while looping through all the found cells.

In this basic approach, our function contains 3 parameters:

  • text – The text to search.
  • sheetName – The sheet name to search.
  • isHide – An optional argument set to true by default to hide values or false manually to unhide them.

Lines 10-11 – We first collected our current Google Sheet workbook and then select the sheet tab we will be working in.

Lines 13-14 – Then we use the Text Finder class in the Spreadsheets App to search for our target text and then use the findAll() method to get an array constructor of all the found value cell ranges. You can learn more about this in the first tutorial in this series here:

Find All Values in Google Sheets with Apps Script

Line 16 – Next we iterate through each found row with a JavaScript forEach() loop.

Line 17 – On each iteration, we collect the row number. We do the same thing here in our previous tutorial when activating and formatting entire rows.

Lines 19-23 –  Lastly we need to check if the user has set isHide to false to show the rows or true (or not used) to hide the row. We then call the sheet and apply either the Spreadsheets Apps Hide Rows (hideRows()) method or (showRows()) Show Rows method. These methods can take a single row as an argument. We provide this with the row variable.

This is a quick and easy solution to understand. However, it does not perform well with a large dataset. It will also try and hide rows multiple times when a found cell is on the same row as a previously found cell.

The function, just like the other two examples, can be called from another function with:

hideAllRowsWithVal(text, sheetName, boolean)

e.g.:

hideAllRowsWithVal("koala", "Sheet1", true)

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

V2 – Find and Hide Rows based on cell values in Google Sheets with Range Grouping to improve performance

Starter Sheet

Version 2 – Starter Sheet

The Video

https://youtu.be/CeWUAOK7Ui8

Released Monday 12 Feb 2023. Subscribe (Top right) to get a notification for when this video comes out. 

The Code

In larger, ranges we might have a lot of situations where our found text is on multiple adjacent rows.

Find and hide rows by grouping ranges in Google Sheets with Apps Script

In the image above, we can see that rows 30 and 31, 38-43, and 46-49 can all be batched together into a single range.

Furthermore, rows with the found text (Koala) on the same row can be ignored, the consecutive times that they are found.

Let’s go ahead and fix our code to make it more efficient.

Note that lines 9-16 of the code are the same as the previous function. Refer to this for an explanation.

The Hide Range METHOD

Line 22 – After we have collected our ranges of found rows we will create a hideRange() method. This will either hide or show the range based on the isHide argument.

This function takes a single object parameter that contains a start row and a number of rows.

Lines 24 – 26 – Unlike the previous version, we use the Hide Rows and Show Rows extra parameter to include the number of rows deep to hide.

Row Range variable

Lines 32 – 35 – This mutable variable stores the start row and row depth as we collected each range of rows to hide in our sheet.

Iterate through all cells

Line 37 – Here, we start the forEach loop that iterates through each found cell range. We include the index (idx) in our arguments in our arrow function as well here.

Line 39 – Next, we grab the row number with the Get Row (getRow()) method.

Line 40 – Then we get the next possible row in the current range collection by adding the start row with the row depth. This will be used in a moment ot compare against the currently iterated row.

On the first iteration

Lines 42-46 – On our first loop through our found cells, all we need to do is add our first found row and add one to our depth.

There is another cell on the same row

Lines 47-51 – If there is another cell on the same row, then we don’t need to do anything and we simply return the function for that iteration.

There is a cell directly below the previous one

Lines 52-55 – If the next found row is only one cell down (adjacent), then we just want to add one to the number of rows of the existing rowRange variable.

Lines 57-60 – If the row is the last found row, then we can run the hideRange function on our update range set.

A new range begins

If the next found row is not directly below the previous one. We need to:

  1. Line 63 – Run the hideRange on the current rowRange variable set.
  2. Lines 65-66 – Reset the rowRange variable adding in the current row to the start range and one to the depth.

Lines 69-71 – Again, if we are on the last found row, we can just run hideRow to end the loop.

Create and Publish a Google Workspace Add-on with Apps Script Course

Need help with Google Workspace development?

Go something to solve bigger than Chat GPT?

I can help you with all of your Google Workspace development needs, from custom app development to integrations and security. I have a proven track record of success in helping businesses of all sizes get the most out of Google Workspace.

Schedule a free consultation today to discuss your needs and get started or learn more about our services here.

V3 – Find and Hide Rows based on cell values in Google Sheets using the Google Sheets Advanced Service API and Range Grouping

The Video

Released Thursday 13 Feb 2023. Subscribe (Top right) to get a notification when this video comes out. 

Starter Sheet

Version 3 – Starter Sheet

The Code

I found version two to be remarkably efficient. However, if you want to do a lot of editing to the sheet tab then you might want to consider using the Google Apps Script Advanced Sheet Service API.

Main Variables

We need to add a few more variables to our main variables here.

Line 12 – We will need to get the spreadsheet ID to use in our batch update.

Line 13 – The sheet name does not work the same as an identifier in the advanced service, so we will need to extract the sheet ID too.

A request list

Line 21 – The Sheets batch request property requires a list of requests as its value. Here we will store an array of all the requests that we want to batch together to hide each row range of the Google Sheet.

Update Dimension Properties

Line 27 – The appendRequest(setRange) function pushes a new request to the requests property.

When hiding and displaying rows or columns in a Google Sheet with the Advanced Service we are updating a dimension property. These properties can be a number of different field types, but for us, we want to hide rows.

Let’s  take another look at the layout of this JSON object:

Lines 3-8 – The first sub-property is the range object. This object requires a sheet ID. The dimension is either the columns or rows.

Then we need to set the start index. Note that the cell ranges will start from zero instead of 1. We must then subtract 1 from our start row to get the correct start index of our row range.

Finally, we set the end row. This will be one row after our desired end row. Think, ‘up to, but not including this row’.

Lines 9-11 – Next, we identify the property that we want to change. For hiding and showing rows this is the "hiddenByUser" property, where the value is a boolean. This conveniently fits with our ishide parameter.

Line 12 – Weirdly, we then need to declare that we are using the "hiddenByUser" property by adding it to the fields list.

Store the current row range

Lines 46-49 – Just like the previous version, we need to store each range before sending it to appendRequest(). Unlike the previous version, our last property is the end row rather than the number of rows.

Starting the loop

Lines  52-54 –Now we can commence our loop through the found cells.

Here, the first task is to collect all the row numbers.

On the first iteration

Lines 56-60 – On the first loop of our cell array, we just want to add the current row number to the start and end row of rowRange.

If the next row is on the same row

Lines 61 – 65 – If we have more than one result on the same row we want to ignore it and return the current loop.

If its the next row below

Line 69 – We add one to our end row value in rowRange.

Lines 72-74 – If we are on our last found row, send it to appendRequest().

Create a new row range

Line 77 – If the current row is not the next row down then we send appendRequest() with our current rowRange.

Lines 79-80 – Next, we create a new rowRange.

Line 83-85 – If the current row is the last row in the array, we sent it to appendRequest().

The Batch request

Line 90. Here we use the batch request of the spreadsheet resource. The batch request takes an object with a requests property. This property, in turn, requires an array of JSON object requests.

For its second argument, we reference the Spreadsheet ID.

 

Some notes

If you know that there may not be any matches in your text finder then you might wish to add a return agent after the allOccurrences variable:

if(allOccurences.length === 0) return;

Another good measure might be to use a try/catch statement in the third version when running the batch call, just in case there is an error with the API server.

If you have found the tutorial helpful, why not shout me a coffee ☕? I'd really appreciate it.

~ Yagi