Is it possible to do ImportRange in Google Apps Script? - google-apps-script

I have been playing around with Google Apps Script today and I am trying to code some custom spreadsheet functions. I have done some searching but cannot find an answer to my query.
I know that on a Google Spreadsheet you can use ImportRange in a cell on a spreadsheet like this:
=ImportRange(spreadsheet_key;sheet!range_of_cells)
My questions are is it possible to do something similar in a Google Apps Script and if so, how?
I want to import a range of cells from a sheet on another spreadsheet (not a sheet on the spreadsheet where the script will reside).

Yes, this is perfectly possible. You just need to call SpreadsheetApp.openById and then get the desired sheet and ranges normally.
Please take a look at the documentation : range.getValues() and range.setValues() are very basic GAS methods and are pretty well described.
Read the tutorial as well.

It seems Google, in their infinite wisdom, has altered the behavior of openById and similar functions. They are no longer allowed in the context of custom functions.
See https://code.google.com/p/google-apps-script-issues/issues/detail?id=5174 for more details.
They suggest using IMPORTRANGE as a workaround, but as previously mentioned, this needs to be called within a cell.
Our solution was to use IMPORTRANGE in the sheet, and pass the acquired data into our custom function, as the data set was small. I hope this information helps!

I needed to do this recently. This is what I came up with, simply hard-coding the spreadsheet key and range into ahab's myImportRange function
// to be used in the spreadsheet like so: = myScriptedImportRange( GoogleClock() )
// no need to include key or range because they are in the script here
//
// the third parameter - GoogleClock() - triggers an automatic update every minute.
// updated 2011-07-17 (ahab): better regex to strip sheetname of *outer* single quotes
// updated 2013-01-27 (ben) to hard-code key and range
function myScriptedImportRange( ) {
var key = "PUT YOUR DATA_SPREADSHEET_ID IN HERE"
var sheetrange = "PUT YOUR SHEET AND CELL RANGE IN HERE"
var shra = sheetrange.split("!") ;
if (shra.length==1) shra[1]=shra[0], shra[0]="";
var sheetstring = shra[0].replace( /^'(.*)'$/g , "$1") // was: replace( /'/g , "") ; updated 2011-07-17 (ahab)
var rangestring = shra[1]
var source = SpreadsheetApp.openById( key )
if ( sheetstring.length==0 ) sheet = source.getSheets()[0] ;
else sheet = source.getSheetByName( sheetstring ) ;
return sheet.getRange( rangestring ).getValues();
}
In my case I have a set of private sheets, an intermediate sheet that uses the regular myImportRange and VMERGE with some SQL to combine selections from the private sheets into the intermediate sheet, and then a public sheet that simply has one cell containing = myScriptedImportRange( GoogleClock() )
Note that there is a similar approach here: https://stackoverflow.com/a/11857014
Note also that the ImportRange function and related functions often have a problem of not displaying the imported data when the origin workbook(s) is/are not open. A simple way around this has been described in a comment here: https://stackoverflow.com/a/11786797

Related

Populate rows with the sheets names of an spreadsheet

I have an spreadsheet that contains multiple sheets with some recipes. I'm storing the sheets' titles in the first sheet manually, to reference the recipes titles on another spreadsheet, but this doesn't scale well, so I want to automate the process. I believe I can't do that with any of the built in functions, so I'm trying to build a custom function to do it.
I already know some coding, but my experience with google API is very limited. My current attempt is returning the following exception: "You do not have the permission to call set value". After google this error, I discovered that custom functions seemingly cannot set values to another cells, so I'm here to find an alternative to such a trivial behavior.
This is my current code:
function updateSheetTitles() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var cell = SpreadsheetApp.getActiveSpreadsheet().getCurrentCell();
var row = cell.getRow();
var column = cell.getColumn();
for (var i=0 ; i<sheets.length ; i++){
if(i<1) //For passing trought the first sheet
return;
cell.setValue(sheets[i].getSheetName);
var range = SpreadsheetApp.getActiveSheet().getRange(row+1, column, 1, 1);
cell = SpreadsheetApp.getActiveSheet().setCurrentCell(range);
}
}
And here's and image to illustrate what I want:
This is probably an easy task, but I failed to find a way to build this, so any ideas to accomplish that would be appreciated.
I believe your goal as follows.
You want to retrieve sheet names in the active Google Spreadsheet.
You want to put the sheet names to the active cell.
You want to achieve this using the custom function.
Modification points:
Unfortunately, setValue cannot be used in the custom function. I think that the reason of your issue is due to this.
In your script, at cell.setValue(sheets[i].getSheetName);, the method of getSheetName is not run. If you want to use this method, please add () like getSheetName().
SpreadsheetApp.getActiveSpreadsheet() can be declared one time.
In order to achieve your goal, I would like to propose a custom function for creating an array including the sheet names and returning the array.
When above points are reflected to the sample script, it becomes as follows.
Modified script:
Please copy and paste the following script to the script editor of Google Spreadsheet. And, please put the custom function of =updateSheetTitles() to a cell. By this, the sheet names are returned to the row direction.
function updateSheetTitles() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets().map(e => [e.getSheetName()]);
}
From your replying, when you want to retrieve the sheet names except for 1st and 2nd sheet, you can also use the following script.
function updateSheetTitles() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets().map(e => [e.getSheetName()]).slice(2);
}
References:
Custom Functions in Google Sheets
map()
getSheetName()

How to get a list of comments and replies from a docs and add content to a sheet? [duplicate]

Today I have a question about Google Apps Scripts, specifically for Spreadsheets. I've already looked at the documentation here (yes, documentation on a Sheet within a Spreadsheet), but I haven't been able to find what I'm looking for. Here's the sitch:
1.) When a cell is edited in a Google Spreadsheet, the function I have SETS a NOTE using the function (yes it says cell.setComment(), but in reality it creates a Note. Thanks for being consistent, Google!):
function onEdit() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getActiveSheet();
var cell = sheet.getActiveCell();
cell.setComment("Last modified: + (new Date()));
}
2.) Notes have been 'replaced' by Comments as of... September 5th, 2012? Maybe? I would rather use those instead.
3.) For both types of 'cell notation', there exist functions only to SET the Note/Comment, and not ADD (according to the documentation).
4.) I would like to reference or write a function that has the capability to ADD a new Note/Comment (preferably Comment, they are more easily read), instead of setting the Note/Comment.
5.) You can MANUALLY add a Note/Comment through the GUI in the Spreadsheet itself (right-click a cell and select "Insert Note" or "Insert comment". Because these right-click functions exist leads me to believe that we could write a script to do the same thing, but have it be called auto-magically when the cell has been edited.
6.) This function will be used to keep track of cell revision history. I know that I can create a new spreadsheet and send the revision history to that new spreadsheet easily, but considering that I have 10 spreadsheets that need to be tracked, I would rather not have 10 new spreadsheets to keep track of history. Keeping it in the same spreadsheet will keep things simple.
If anyone could help me out, it would be greatly appreciated!
There is no way to manipulate Comments via Spreadsheet Services - see Issue 36756650. (Star it if you wish.)
Instead, all the "Comments" methods work on Notes.
The following method will append a new "Modified" timestamp to any existing one - not quite as nice looking as an actual Comment would be, unfortunately.
function onEdit() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var cell = sheet.getActiveCell();
var comments = cell.getComment();
// Newline works in msgBox, but not in Note.
comments = comments + "\\nModified: " + (new Date());
//Browser.msgBox(comments);
cell.setComment(comments);
}
Updated answer for dic 2018:
Now Google supports the setNote(String) and setNotes(Object[]) method in the Range class.
the getComment() and setComment() methods no longer exist.
Using Google Drive API in Google Apps Script, we could create Comments in a Google Document.
And after a test, I confirm you can also do it in a Spreadsheet (which is normal because Comments depends only on Drive API)
function insertDriveComment(fileId, comment, context) {
var driveComment = {
content: comment,
context: {
type: 'text/html',
value: context
}
};
Drive.Comments.insert(driveComment, fileId);
}
Keep in mind that you cannot attach programmatically the comment to a cell (or to words in Google Document), because anchors for Document and Spreadsheet Comments are proprietary (check the video at the bottom of the page here)
Hope it could help.
Now you can use setNote() and getNote()
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var cell = sheet.getActiveCell();
var comments = cell.getNote();
cell.setNote(comments);

How to link various Google spreadsheets using IMPORTRANGEs that contain VLOOKUP formulas without getting #N/A returned?

The problem:
When using IMPORTRANGE to pull data from a different spreadsheet, cells in the origin spreadsheet that contain a formule containing VLOOKUP often (but not always) return #N/A (ERROR: Did not find value 'xxx' in VLOOKUP evaluation). In the origin sheet the formulas are calculated correctly and showing a value though. This does not always happen, sometimes it does pull in the proper value.
The intent:
To take data from different spreadsheets, combine them in a different spreadsheet and do some calculations on them. And then pull info from this calculation spreadsheet (or from multiple calculation spreadsheets) into a reporting spreadsheet for some further calculations and formatting.
The set-up:
There are several source data spreadsheets,say dataspreadsheet1, dataspreadsheet2 and dataspreadsheet3. A calculation spreadsheet (calcspreadsheet) is created that creates a copy of sheet1 in each of the data spreadsheets and names these sheets datasheet1, datasheet2 and datasheet3 respectively. The IMPORTRANGE statement used for this is created as follows: importrange(+VLOOKUP("dataspreadsheet1",filelist!A1:C1000,3,FALSE),"sheet1!a1:Z1000") where
filelist!A1:C1000 is a sheet in calcspreadsheet that contains Name, Type and Id in respective columns.
The values in each of these sheets datasheet1-3 are then used for calculations in another sheet, calcsheet1 in the calcspreadsheet. As the main goal of this is to add up daily values from the 3 dataspreadsheets, but those sourcesheets do not all have the same data on the same line a VLOOKUP is used again to make sure additions for a date use the line for the date in datasheet1-3 regardless of its row number. E.g. VLOOKUP($A11,'datasheet1'!$A:$P,4) + VLOOKUP($A11,'datasheet2'!$A:$P,4) + VLOOKUP($A11,'datasheet3'!$A:$P,4) where column A is the date column in all sheets.
This appears to work fine, although upon opening calcspreadsheet it can take a long time for it to go through an update, during which time lots of #N/A's are displayed. Eventually it comes right though.
The problem arise when a reportspreadsheet is created that in turn used an IMPORTRANGE call to pull the info from calcsheet1 in order to be able to work with it. This often, but not always, results in the problem states at the start. The IMPORTRANGE call in this reportspreadsheet is generated in a similar way as that in the calcspreadsheet: =importrange(+VLOOKUP(calc!B1,sheetcodes!A1:C3000,3,FALSE),"sheet1!a1:Z1000") where calc!B1 contains the name of the source spreadsheet (in this calc that would be 'calcspreadsheet' and sheetcodes!A1:C3000 again contains a list of sheets with Name, Type and Id in respective columns
A work-around I tried:
What I did notice that IMPORTRANGE works better on cells that do not contain VLOOKUP So I tried to copy the content of calcsheet to another sheet in calcspreadsheet, called exportsheet but having only the values in there, not formulas and then use IMPORTRANGE on this exportsheet. The copy script used is as follows:
function exportPrep() {
// Get the active spreadsheet and the active sheet
//var ss = SpreadsheetApp.getActiveSpreadsheet();
//var sheet = ss.getSheetByName("stream");
//sheet.activate();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("calcsheet");
var sourceDataRange = source.getDataRange();
var sourceSheetValues = sourceDataRange.getValues();
var sourceRows = sourceDataRange.getNumRows();
var sourceColumns = sourceDataRange.getNumColumns();
var destination = SpreadsheetApp.getActiveSpreadsheet();
SpreadsheetApp.setActiveSheet(destination.setActiveSheet("exportsheet"));
destination.getDataRange().offset(0, 0, sourceRows, sourceColumns).setValues(sourceSheetValues);
}
This seemed to work, but unfortunately the copy script used to copy the value of calcsheet into exportsheet now showed the same behaviour, it would sometimes work and sometimes give the #N/A and so leaves me with the same problem.
My questions:
I've read various posts with similar issues and responses that mentioned this function was temperamental or had a bug. Other stated it is not possible to use dynamic references in IMPORTRANGE. Given that it sometimes works and sometimes not I suspect the function itself might be correct but that there are sync or time-out issues in the set-up.
How can I set up the above functionality. Either with the use of IMPORTRANGE and VLOOKUP at all with some changes/additions, or built in a different way from the get-go.
So I've not done this with importrange but I when I have this issue with Vlookup I wrap the vlookup in an IF and test for the #N/A.
Try something like: =IF(ISNA(VLOOKUP(...)),"",VLOOKUP(...))
=IFERROR(VLOOKUP(B81,'KAM_Q3_OPPS STAGE_(H/I/L/M/N)'!$F$3:$G$111,2,False),)
This is the best way I found to do it and the last two values [ , ) ] make the new output, in this case it is nothing.
IF you wanted 0 put in then you would do [ ,0) ]

Possible to use Google Spreadsheet functions in Google App Script?

I just discovered Google App Scripts, and I'm stumped on something already...
I am trying to write a script for a Google Spreadsheet which finds certain historical stock prices. I found that the FinanceApp service within Google App Scripts has been deprecated, and seemingly replaced by the GOOGLEFINANCE() function within Google Spreadsheets. However, it returns an array, when I need only a single cell, and the array is mucking up the works.
So I'd like to write a short script that calls the GOOGLEFINANCE() spreadsheet function, and finds just the 1 piece of info I need from the array which is returned by GOOGLEFINANCE(). However, I cannot find a way to access Spreadsheet Functions (SUM, VLOOKUP, GOOGLEFINANCE, etc) within a script.
Is there a way to access these functions in a script? Or perhaps, is there a new service which replaces the deprecated FinanceApp service?
Many thanks for any assistance!
You can try this:
var trick = SpreadsheetApp.getActiveSheet().getRange('D2').setValue('=GOOGLEFINANCE("GOOG")').getValue();
Native Spreadsheet functions are not supported in Google Apps Script.
You could eventually use a somewhat cumbersome workaround by reading the value of a cell in which you write a formula (using script in both write and read) but this will be less than practical and / or fast.
You might try the INDEX function combined with GOOGLEFINANCE-
For reference,
=GOOGLEFINANCE("MSFT", "PRICE", "01/01/21")
Returns the array:
Date Close
1/4/2021 217.69
One can add the INDEX function to pick out specific elements from the array using the row,column coordinates of the array.
=INDEX(GOOGLEFINANCE("MSFT", "PRICE", "01/01/21"),2,2)
This returns just the data in row 2, column 2 - 217.69
There is one possible way, with the .setFormula(). This function behave like .setValue() and can be used the following way:
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mySheet = ss.getSheets()[0]
//Code Below selects the first cell in range column A and B
var thisCell = mySheet.getRange('A:B').getCell(1,1);
thisCell.setFormula('=SUM(A2:A4)');
All formulas you write in this function are treated as strings must have ' or " within the .setFormula() input.

How to get sheet that is not first sheet?

I am new to Google scripting, so I apologize if this is a naive question. I do not know how to get a variable reference to a sheet within a spreadsheet that is not the first sheet.
In my spreadsheet, I have two sheets, Agenda and Info. Agenda is the first sheet(index 0) and Info is the second. I can get a reference to Agenda, but I cannot get a reference to Index. This is the code I have tried:
var info_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Info');
info_sheet.getName() always comes out being Agenda, though. What do I do?
There are 2 ways to get access to a specific sheet in a spreadsheet : by its name as you were showing in your code or by its index like in the second example below.
Just run this test to see the results on a spreadsheet with at least 2 sheets (I changed the sheet names to the 'standard default values in US locale' to make the test working for anyone, re-set it to your parameters if needed)
function testSheetNames(){
var info_sheet_example1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var info_sheet_example2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
Logger.log('name method result = '+info_sheet_example1.getName());
Logger.log('index method result = '+info_sheet_example2.getName());
}
That said, your example should return the correct value, I'm not sure why it didn't in your tests.