Problems with code listing unique items in a list and copying them into new worksheet - javascript

I'm trying to run the following code on a spreadsheet. The column of names is the 4th column. I'm attempting to run through that list of names, pushing each new unique name to an array (listOfNames), and then add a new worksheet (NamesList), and finally add the array to cell A1 of the new worksheet. When I run the code below, all I get is a blank popup with an OK and Cancel button. When I view the log, it is blank as well. I'm quite new at this, and I feel like I'm missing something obvious... just not sure what it is. Am I misunderstanding something specific to GAS rather than JS?
var sheet = SpreadsheetApp.getActiveSheet();
var listOfNames = new Array ();
function copyNames() {
var data = sheet.getDataRange().getValues();
for (i=0; i<=sheet.getLastRow(); i++){
var tempName = sheet.getDataRange(i,4).getValue();
for (i=0; i<=listOfNames.length; i++){
if (tempName != listOfNames[i]){
listOfNames.push(tempName);
logger.log(listOfNames);
}
}
}
sheet.insertSheet(ListOfEDs);
sheet.getRange('a1').setValue(listOfEDs);
}
Edit: I'm starting to see that this will push values multiple times to the list... so maybe it's just back to the drawing board all together. I have found other code that would create a list of unique elements, but wasn't really sure how that code worked. I thought I'd try to figure it out myself so I'd at least understand it.
EDIT 2: Ok... I tried some new code, but I'm still getting a blank message box, and nothing on the log. I wasn't sure if having i be the iterator for a for loop within a for loop was a bad thing, so I switched it to j. Also, I know there's the remove duplicates example, and have been looking at that, but am unsure why one needs to use join.
function copyNames() {
var sheet = SpreadsheetApp.getActiveSheet();
var listOfNames = new Array ();
var data = sheet.getDataRange().getValues();
for (i=2; i<=data.length; i++){ //starting at 2 because first row is a header
var tempName = data[i][4];
for (j=0; j<=listOfNames.length+1; j++){
if (tempName != listOfNames[j]){
listOfNames.push(tempName);
logger.log(listOfNames);
}
}
}
sheet.insertSheet("ListOfNames");
sheet.getRange('a1').setValue(listOfNames);
}

As far as I understand you went help instead of a ready solution, so here you go.
First:
Try to avoid using global variables. Keep all your variables inside of your functions. Otherwise you will have issues when you add more functions to your spreadsheet. Your Code should start like this:
function copyNames() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var listOfNames = new Array ();
Second:
Try to decrease the amount of service calls to spreadsheet. it takes several seconds for your script to go to your sheet and take the values. Instead of doing it each time, take these values once and push them in to a variable.
In your case, you already did that, however did not use that variable and kept using service calls. Check this line:
var data = sheet.getDataRange().getValues();
Now all of the data on that sheet is in a variable called data.
sheet.getDataRange(i,4).getValue();
is the same as
data[i][4];
The only difference is that in the first case it will take approximately 2 seconds to get that value, while in the second case only a few milliseconds.
Same goes for
sheet.getLastRow();
Either call it once and push it into a variable and use that, or in your case just use
data.length;
Third:
listOfNames is an empty array, so it's length is 0.
This line
for (i=0; i<=listOfNames.length; i++)
will not even run, as both i and listOfNames.length is 0.
That is why you logger does not give any output. Your script never get's to that line.
Fourth:
You do not have a variable called ListOfEDs, therefore your last two rows of code just give an error. Your script does not know what is ListOfEDs as it doesn't exist.
Hope this helps.

Related

Google Apps Script Better Way to Get Unique Values

I have working code that takes data from two non-adjacent columns in a Google Spreadsheet, looks for unique values in the first column, and if unique creates a new array with the unique value from the first column and corresponding value in the second column. The problem is, the data I am using is already somewhat long (413 rows) and will only get longer over time. It takes about 1-2 minutes for the code to run through it. I've been looking for a shorter way to do this and I've come across the filter() and map() array functions which are supposedly faster than a for loop but I can't get them implemented correctly. Any help with these or a faster method would be greatly appreciated. The code I have right now is below.
function getkhanassignments(rows) {
var assignmentsraw = [];
var temparray = [];
var previousassignment = datasheet.getRange(50,1).getValue();
for(i=0, j=0;i<rows-1;i++) {
if(datasheet.getRange(50+i,1).getValue() != previousassignment) {
previousassignment = datasheet.getRange(50+i,1).getValue();
assignmentsraw[j] = new Array(2);
assignmentsraw[j][0] = datasheet.getRange(50+i,1).getValue();
assignmentsraw[j][1] = datasheet.getRange(50+i,8).getValue();
j++;
}
}
Logger.log(assignmentsraw);
return assignmentsraw;
}
The answers I've found elsewhere involve just getting unique values from a 1d array whereas I need unique values from a 1d combine with corresponding values from another 1d array. The output should be a 2d array with unique values from the first column and their corresponding values in the second column.
Solution:
The best practice of looping through ranges in Google Apps Script is to dump the range values into a 2D array, loop through that array, and then return the output array back to Google Sheets.
This way, there would be no calls to Sheets API inside loops.
Sample Code:
function getkhanassignments(rows) {
var assignmentsraw = [];
var table1 = datasheet.getRange(50,1,rows).getValues();
var table2 = datasheet.getRange(50,8,rows).getValues();
var previousassignment = table1[0][0];
assignmentsraw.push([table1[0][0],table2[0][0]]);
for(i=0; i<rows; i++) {
if (table1[i][0] != previousassignment) {
assignmentsraw.push([table1[i][0],table2[i][0]]);
previousassignment = table1[i][0];
}
}
Logger.log(assignmentsraw);
return assignmentsraw;
}
References:
Class Range
push()

Filter function will not delete my empty rows - Google App Script

I want to import rows from one google sheet to the other, however source sheet imports a number of empty rows. Now I use a filter function to get rid of these rows but they will not disappear, can anyone tell me why?
var a = SpreadsheetApp.openByUrl("url").getSheetByName("Admin Use Only").getRange(4,1,6,21).getValues();
var b = SpreadsheetApp.getActive().getSheetByName('Credit_Detail');
b.getRange(b.getLastRow() +1, 1, a.length,21).setValues(a);
//filter function below:
var otarget=b.getRange(2,1,b.getLastRow()-1, 26).getValues();
var data=otarget.filter(function(r){
return !r.every(function(cell){
return cell === "";});
});
Logger.log(data);
b.getRange("A2:Z").clearContent();
b.getRange(3,1,data.length,data[0].length).setValues(data);
here's how I would do it. First, create an variable to store the array of the source. then run a for loop scanning the first column for empties. something like: for (var i = 0, i < data.length; i++) { if (data[i][0] != '') { XXXX } }
XXXX means that you can either put a code to create a new set of array which can be passed to the target sheet at once or use append row to transfer non blank rows to the target sheet one by one.
Note: Creating a new array to store non-empty rows would speedup the execution time if you are dealing with large data, thousands of rows.

How Can I Use setValue() Based Off Of 2 Arrays That Match Value?

I should start with letting you know that I'm an extreme novice in JS. My background is almost solely in SQL/VBA. So, any tips you could provide would be greatly appreciated, whether it's in coding or in Stack Overflow etiquette.
Problem Background:
I've got a Script that I use for sending outbound e-mails from Google Form responses, hosted within a Google Sheet and set to OnFormResponse(). Typically, I'm only asked to send back specific bits of information from the form responses within a HTML template e-mail. However, the business case I have now is that I need to look up the values from another sheet, where an adjacent column's value matches a form response value. With the value that's matched, I need to set the value of a specific column/row (F:F) within the Form Response sheet with it.
Example:
Here's a simplified version of what the Form Responses sheet looks like, along with the formula that I would typically use:
Here's what the other tab, 'Unique Databases!', looks like:
So, my understanding of JavaScript arrays is that on the Form Responses Sheet, I would load all columns (A:E in this example) into a variable, and get the values. Then, get columns A:B of 'Unique Databases!', which loads those values into another array. If that is accurate, how do you compare the index of 1 array against the index of another, and return an adjacent match?
Thanks!
You can try this function:
function dbmanager(dbname) {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Unique Databases!');
var rg=sh.getDataRange();
var vA=rg.getValues();
var r='';
for(var i=0;i<vA.length;i++){
if(dbname==vA[i][0]){
r=vA[i][1];
break;
}
}
return r;
}
I'm not sure you need to use a apps script to make a comparison like this. Perhaps a much faster way would be to use a query in the sheet. Something like this maybe:
=QUERY(Sheet1!A1:B3,"SELECT B WHERE A ='"&E2&"'",0)
The first part of the query is looking up the unique databases data. The second part selects column B in the unique databases data where column A is equal to the data base name in the form responses data. Note this query goes in column F of the responses data.
Another alternative using code might be something like this. with the code below running.
function getFormData(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet2');
var getRange = sheet.getRange('E2:E');
var data = getRange.getValues();
var lookup = getLookupData();
data.forEach(function(item,index){
sheet.getRange(index + 2 , 6).setValue(lookup[item])
})
}
function getLookupData() {
var obj = {};
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
var range = sheet.getRange('A2:B6');
var data = range.getValues();
data.forEach(function(item){
obj[item[0]] = item[1];
})
Logger.log(obj)
return obj;
}

Google apps script getRange() range not found error

I feel like I'm going about this in all the wrong way. I'm trying to automate some of my workload here. I'm cleaning up spreadsheets with 4 columns (A-E), 2000+ rows. Column B contains website URLs, column D contains the URL's business name, generated from another source.
Sometimes the tool doesn't grab the name correctly or the name is missing, so it populates the missing entries in column D with "------" (6 hyphens). I've been trying to make a function that takes an input cell, checks if the contents of the cell are "------", and if it is the function changes the contents of the input cell to the contents of the cell two columns to the left (which is generally a website url). This is what I've come up with.
function replaceMissing(input) {
var sheet = SpreadsheetApp.getActiveSheet();
//sets active range to the input cell
var cell = sheet.getRange('"' + input + '"');
//gets cell to fill input cell
var urlCell = sheet.getRange(cell.getRow(), cell.getColumn() - 2);
//gets contents of input cell as String
var data = cell.getValue();
//gets contents of urlCell as String
var data2 = cell.getValue();
//checks if input cell should be replaced
if (data === "------") {
//set current cell's value to the value of the cell 2 columns to the left
cell.setValue(data2);
}
}
When I attempt to use my function in my sheet, the cell is returning the error
Error Range not found (line 4).
I'm assuming, based on similar questions people have asked, that this is how you use the A1 notation of the function with an argument. However, that doesn't seem to be the case, so I'm stuck. I also don't think my solution is very good period.
1) It's somewhat ambiguous in GAS documentation, but custom functions have quite a few limitations. They are better suited for scenarios where you need to perform a simple calculation and return a string or a number type value to the cell. While custom functions can call some GAS services, this practice is strongly discouraged by Google.
If you check the docs for the list of supported services, you'll notice that they support only some 'get' methods for Spreadsheet service, but not 'set' methods https://developers.google.com/apps-script/guides/sheets/functions
That means you can't call cell.setValue() in the context of a custom function. It makes sense if you think about it - your spreadsheet can contain 1000s of rows, each with its own custom function making multiple calls to the server. In JavaScript, every function call creates its own execution context, so things could get ugly very quickly.
2) For better performance, use batch operations and don't alternate between read / write actions. Instead, read all the data you need for processing into variables and leave the spreadsheet alone. After processing your data, perform a single write action to update values in the target range. There's no need to go cell by cell when you can get the entire range using GAS.
Google Apps Script - best practices
https://developers.google.com/apps-script/guides/support/best-practices
Below is a quick code example that runs onOpen and onEdit. If you need more flexibility in terms of when to run the script, look into dynamically-created triggers https://developers.google.com/apps-script/reference/script/script-app
Because your spreadsheets have lots of rows, you may hit the execution quota anyway - by using triggers you can work around the limitation.
Finally, if a cell containing '----' is a rare occurrence, it might be better to create another array variable with new values and row numbers to update than updating the entire range.
Personally, I think the single range update action would still be quicker, but you could try both approaches and see which one works best.
function onOpen(){
test();
}
function onEdit() {
test();
}
function test() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('yourSheetName');
//range to replace values in
var range = sheet.getRange(2, 4, sheet.getLastRow() - 1, 1);
//range to get new values from
var lookupRange = range.offset(0, -2);
//2d array of values from the target range
var values = range.getValues();
//2d array of values from the source range
var lookupValues = lookupRange.getValues();
//looping through the values array and checking if array element meets our condition
for (var i=0; i < values.length; i++) {
values[i][0] = (values[i][0] == '------') ? lookupValues[i][0] : values[i][0];
}
// one method call to update the range
range.setValues(values);
}

Data Scraping With ImportHTML in Apps Script & Google Sheets

Goal: I am trying to pull data from a website and use it to create a big table. I can tell that I'm very close to getting this to work, but I've reached a roadblock.
Background:
I have a google sheet with three pages. (1) Titled "tickers" is a list of every ticker in the S&P 500, in rows A1-A500. (2) Titled actionField is just a blank page used during the script. (3) Titled resultField will hold the results. The website I am pulling from is (http://www.reuters.com/finance/stocks/companyOfficers?symbol=V) Though, I want the script to work (with minor modification) for any data accessible through importHtml.
Script:
The script I currently have is as follows:
function populateData() {
var googleSheet = SpreadsheetApp.getActive();
// Reading Section
var sheet = googleSheet.getSheetByName('tickers');
var tickerArray = sheet.getDataRange().getValues();
var arrayLength = tickerArray.length;
var blankSyntaxA = 'ImportHtml("http://www.reuters.com/finance/stocks/companyOfficers?symbol=';
var blankSyntaxB = '", "table", 1)';
// Writing Section
for (var i = 0; i < arrayLength; i++)
{
var sheet = googleSheet.getSheetByName('actionField');
var liveSyntax = blankSyntaxA+tickerArray[i][0]+blankSyntaxB;
sheet.getRange('A1').setFormula(liveSyntax);
Utilities.sleep(5000);
var importedData = sheet.getDataRange().getValues();
var sheet = googleSheet.getSheetByName('resultField');
sheet.appendRow(importedData)
}
}
This successfully grabs the ticker from the tickers page. Calls importHtml. Copies the data. And appends SOMETHING to the right page. It loops through and does this for each item in the ticker list.
However, the data being appended is as follows:
[Ljava.lang.Object;#42782e7c
[Ljava.lang.Object;#2de9f184
[Ljava.lang.Object;#4b86a4d0
That displays across many columns, for as many rows as there are iterations in the loop.
How do I successfully append the data?
(And any advice on improving this script?)
The appendRow method is not suitable here. As it only appends one row, its argument is expected to be a 1D array of values.
What you get from getValues is normally a 2D array of values, like [[a,b], [c,d]]. Even if it's just one row, getValues will return [[a,b]]. The only exception is a single-cell range, for which you get just the value in that cell. It's never a 1D array.
If just one row is needed, use, e.g., appendRow(importedData[0]).
Otherwise, insert the required number of rows and assign the 2D array of values to them.
var sheet = googleSheet.getSheetByName('resultField');
var lastRow = sheet.getLastRow();
sheet.insertRowsAfter(lastRow, importedData.length);
sheet.getRange(lastRow + 1, 1, importedData.length, importedData[0].length)
.setValues(importedData);

Categories