top of page

Copying data between GSheets with AppScript



In our team, we have a lot of Google Sheets 📃 and we often need to show the same content between sheets. (e.g. 2 Sheets both need data in tab Average Daily Order. One is for internal analysis, another is a report for leaders.) In this post, I’d like to share some common solutions to this problem instead of manually copy-pasting data (That’s too time-consuming…)

  • importrange

  • AppScript: getValues + setValues

  • AppScript: batchGet + batchUpdate


Our Goal

Displaying the same data in Original Data’s transaction tab to Backup Data



ImportRange

In most cases, importRange will work just fine. (FYI, here is a nice article sharing solutions to most importRange errors )

=IMPORTRANGE("url_of_original_sheet","transaction!A:N")


Conditional Import

If we want to import specific columns or data fitting certain conditions, QUERY is a handy tool

=QUERY(IMPORTRANGE("url_of_original_sheet","transaction!A:N"), "select Col1, Col3, Col7")

import specific columns


=QUERY(IMPORTRANGE("url_of_original_sheet","transaction!A:N"), "select * where Col3='CC Payment'")

only import data meeting certain criteria


But when there is too many formula/data or having too many people editing the original sheet, importRange may not work 😥😕🤒 If stability is highly emphasized, we need to use AppScript 💎


AppScript

With AppScript, we can get data in one sheet, paste it in another sheet, then use trigger to specify its update frequency.

  • Pros: Backup Sheet is *always alive* ✨

  • Cons: Backup Sheet may not be real-time updated (Depends on how we set our trigger. We can notify ourselves when update fails 😉)

Let’s open the AppScript Project in our Original Sheet and get started


Method getValues + setValues

In Code.gs, paste and modify the following code

function Backup()
{
    let sheet = SpreadsheetApp.getActiveSpreadsheet()
                                     .getSheetByName("transaction");
    const backupSheetUrl = 'https://docs.google.com/spreadsheets/d
               /1SSHx4RiytJoV3X6EgtSb0bNow1YIt8txfrllEIZF9Pc/edit';
    
    // `Sheet2` is where we want to paste our Original Data
    let backupSheet = SpreadsheetApp.openByUrl(backupSheetUrl)
                                    .getSheetByName("Sheet2");
    let values = sheet.getDataRange().getValues();
    
    // Clear data but keep format in backupSheet
    // you can use `backupSheet.clear()` to clear both data and format
    backupSheet.clearContents();
    
    // paste data into Backup Sheet
    backupSheet.getRange(1,1,values.length,values[0].length)
                                        .setValues(values);
}

Now save and click Run to see if it works


After execution completes, Sheet2 in Backup Data is then updated :-)


Let’s create a trigger to execute this function automatically ⏲ (Personally, I do not recommand setting triggers to run less than every 15 minutes. In the code above, we clear all data and paste data afterwards, so you may see data suddenly disappear for 0.5 seconds each time when running Backup function.)



Method batchGet + batchUpdate

Method 1 is good enough for most cases. However, when we are dealing with large data, setValues could become extremely slow and even timedout (AppScript limits each execution to run 6 minutes maximum.) Another reason to use batchGet/Update is when the data range is not continuous, method 2 will be a better choice

We need to add Google Sheet API first:

Services → ➕ → Google Sheet API → Add


More usages of batchGet/Update can be found here.


function batchUpdate()
{
 // https://docs.google.com/spreadsheets/d/{this_is_Sheet_id}/edit#gid=0
    let originalSheetId="18vPr_Kghkv16gFp0KBptzY3yu8J4fp6b9NobppeHMWY";
    let backupSheetId="1SSHx4RiytJoV3X6EgtSb0bNow1YIt8txfrllEIZF9Pc";
    let sheet = SpreadsheetApp.openById(originalSheetId)
                        .getSheetByName("transaction");
    let rowNum = sheet.getLastRow();
    
    // get data in certain range from original sheet
    let response = Sheets.Spreadsheets.Values.batchGet(
        originalSheetId,{ranges: ['transaction!A1:N'+rowNum]});
    let values = response.valueRanges[0].values;
    
    // If your data range is not continuous, your batchGet range will 
    be:
    // ranges: ['transaction!A1:C' + rowNum, 'transaction!F1:N' + 
    rowNum, ...]
    // response.valueRanges[n].values is the corresponding values of 
    each range
    
    // specify data range and new values we want to set in Backup Sheet
    let request=
    {
        'valueInputOption': 'USER_ENTERED',
        'data': [
        {
            'range': 'Sheet3!A1:N'+values.length,
            'majorDimension': 'ROWS',
            'values': values
         }]
     };
     
     // Again, if the range to update is not continuous, `data` in the 
     request above will be:
     // 'data': [{'range': 'Sheet3!A1:C' + values1.length, 
     'majorDimension': 'ROWS', 'values': values1},
     //          {'range': 'Sheet3!F1:N' + values2.length, 
     'majorDimension': 'ROWS', 'values': values2}, ...]
     
     SpreadsheetApp.openById(backupSheetId).getSheetByName("Sheet3")
                                     .getDataRange().clearContent();
     
     // flush() will force gsheet to apply all pending changes.
     // If not added, sometimes update values will run before 
     clearContent :(
     SpreadsheetApp.flush();
     Sheets.Spreadsheets.Values.batchUpdate(request, backupSheetId);
}

Save and run the new function, we can see Sheet3 in Backup Data is now updated :D


We did not use copyTo in the example above because our task is to copy data to *different* GSheet. If you only need to copy-paste data within the same Gsheet, copyTo will do it for you



Source: Medium - Wendee


The Tech Platform

www.thetechplatform.com

1 comment
bottom of page