Is it possible to pull different data sets from one column? - javascript

I've been trying to write some code that looks down one column with strings based on some simple formulas. I can't seem to get it to recognize the different sets of data and paste them where I want them.
I have tried re writing the code a few different ways in which is looks at all the data and just offsets the destination row by 1. But it does not recognize that it is pull different data.
Below is the code that works. What it does is starts from the 1st column 2nd row (where my data starts). The data is a list like;
A
1 Customer1
2 item1
3 item2
4 Item3
5
6 Customer2
7 Item1
The formulas that I have in those cells just concatenates some other cells.
Using a loop it looks through column A and find the blank space. It then "breaks" whatever number it stops on, the numerical A1 notation of the cell, it then finds the values for those cells and transposes them In another sheet in the correct row.
The issue I am having with the code this code that has worked the best is it doesn't read any of the cells as blank
(because of the formulas?) and it transposes all to the same row.
function transpose(){
var data = SpreadsheetApp.getActiveSpreadsheet();
var input =data.getSheetByName("EMAIL INPUT");
var output = data.getSheetByName("EMAIL OUTPUT");
var lr =input.getLastRow();
for (var i=2;i<20;i++){
var cell = input.getRange(i, 1).getValue();
if (cell == ""){
break
}
}
var set = input.getRange(2, 1, i-1).getValues();
output.getRange(2,1,set[0].length,set.length) .
.setValues(Object.keys(set[0]).map ( function (columnNumber) {
return set.map( function (row) {
return row[columnNumber];
});
}));
Logger.log(i);
Logger.log(set);
}
What I need the code to do is look through all the data and separate the sets of data by a condition.
Then Transpose that information on another sheet. Each set (or array) of data will go into a different row. With each component filling across the column (["customer1", "Item1","Item2"].
EDIT:
Is it Possible to pull different data sets from a single column and turn them into arrays? I believe being able to do that will work if I use "appendrow" to tranpose my different arrays to where I need them.

Test for the length of cell. Even if it is a formula, it will evaluate the result based on the value.
if (cell.length !=0){
// the cell is NOT empty, so do this
}
else
{
// the cell IS empty, so do this instead
}
EXTRA
This code takes your objective and completes the transposition of data.
The code is not as efficient as it might/should because it includes getRange and setValues inside the loop.
Ideally the entire Output Range could/should be set in one command, but the (unanswered) challenge to this is knowing in advance the maximum number rows per contiguous range so that blank values can be set for rows that have less than the maximum number of rows.
This would be a worthwhile change to make.
function so5671809203() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var inputsheetname = "EMAIL_INPUT";
var inputsheet = ss.getSheetByName(inputsheetname);
var outputsheetname = "EMAIL_OUTPUT";
var outputsheet = ss.getSheetByName(outputsheetname);
var inputLR =inputsheet.getLastRow();
Logger.log("DEBUG: the last row = "+inputLR);
var inputrange = inputsheet.getRange(1, 1,inputLR+1);
Logger.log("the input range = "+inputrange.getA1Notation());
var values = inputrange.getValues();
var outputval=[];
var outputrow=[];
var counter = 0; // to count number of columns in array
for (i=0;i<inputLR+1;i++){
Logger.log("DEBUG: Row:"+i+", Value = "+values [i][0]+", Length = "+values [i][0].length);
if (values [i][0].length !=0){
// add this to the output sheet
outputrow.push(values [i][0]);
counter = counter+1;
Logger.log("DEBUG: value = "+values [i][0]+" to be added to array. New Array Value = "+outputrow+", counter = "+counter);
}
else
{
// do nothing with the cell, but add the existing values to the output sheet
Logger.log("DEBUG: Found a space - time to update output");
// push the values onto an clean array
outputval.push(outputrow);
// reset the row array
outputrow = [];
// get the last row of the output sheet
var outputLR =outputsheet.getLastRow();
Logger.log("DEBUG: output last row = "+outputLR);
// defie the output range
var outputrange = outputsheet.getRange((+outputLR+1),1,1,counter);
Logger.log("DEBUG: the output range = "+outputrange.getA1Notation());
// update the values with array
outputrange.setValues(outputval);
// reset the row counter
counter = 0;
//reset the output value array
outputval=[];
}
}
}
Email Input and Output Sheets

Related

Automate Hyperlink Creations

I'm trying to automate hyperlink creations on my GSheet.
Here's my script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C");
var ticketID = range.getValue();
Logger.log(ticketID);
for(i = 0; i < ticketID.length; i++){
if(ticketID.length === 0){
ticketID.setValue('')
} else if(ticketID.length > 4){
ticketID.setValue('=HYPERLINK' + '("https://mylink.com/'+ticketID+'";'+ticketID+')');
}
}
}
It does nothing but when I change ticketID.setValue by sheet.getRange("C2:C").setValue it put the whole range in the url. We can see with Logger.log(ticketID) that the whole range is selected.
So according to this result, i'm missing how to get the value of each cell individualy in the range and then check if they are long enought to create an individual url. Do I need to use something like range[i] somewhere? I'm lost.
I believe your goal as follows.
You want to retrieve the values from the cells "C2:C".
When the length of value is more than 4, you want to create a formula of HYPERLINK.
When the length of value is less than 4, you don't want to put the formula.
You want to put the formulas to the cells "C2:C".
Modification points:
When range of var range = sheet.getRange("C2:C") is used, the value of var ticketID = range.getValue() is the value of cell "C2". When you want to retrieve values from the cells "C2:C", please use getValues instead of getValue.
In this case, the retrieved value is 2 dimensional array.
When range.getValue() is the string value, ticketID of var ticketID = range.getValue() is also the string. So I think that when ticketID.setValue('##') is run, an error occurs.
In your script, setValue is used in a loop. In this case, the process cost will become high.
And, when sheet.getRange("C2:C" + sheet.getLastRow()) is used instead of sheet.getRange("C2:C"), the process cost will become low a little.
When above points are reflected to your script, it becomes as follows.
Modified script:
function ticketURLGenerator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Data");
var range = sheet.getRange("C2:C" + sheet.getLastRow());
var ticketIDs = range.getValues();
var values = ticketIDs.map(([c]) => [c.toString().length > 4 ? `=HYPERLINK("https://mylink.com/${c}";"${c}")` : c]);
range.setValues(values);
}
In this modification, the values are retrieved from the cells of "C2:C" + sheet.getLastRow(), and an array including the formulas and values is created, and then, the array is put to the cells.
And I used the template literal for creating the formula.
Note:
In this case, please use this script with enabling V8 runtime.
References:
getLastRow()
getValues()
map()
Template literals
You just need to apply the HYPERLINK operation to the tickets that their length is more than 4. To achieve that, you can use map() to iterate over all the elements in your list.
Solution:
function ticketURLGenerator() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("Data");
const ticketR = sheet.getRange("C2:C"+sheet.getLastRow());
const ticketIDs = ticketR.getDisplayValues().flat();
const hLinks = ticketIDs.map(ti=>{
if(ti.length>4) {
return [`=HYPERLINK("https://mylink.com/${ti}"; ${ti})`]}
else {return [ti]}
})
ticketR.setValues(hLinks);
}

Sync row data based on unique ID in google sheets scripts

I currently have a code that does something very similar to this, but im not sure the small change i need to make to have it work correctly. Right now, the code below compares two rows of unique IDs and if the IDs are the same, it copies the cell in the "Comments" column to the other sheet.
function setComments() {
var ss = SpreadsheetApp.getActive(),
compare1 = "", compare2 = "",
outputSheet = ss.getSheetByName("Sheet2"),
sourceSheet = ss.getSheetByName("Sheet1"),
range1 = outputSheet.getDataRange(),
range2 = sourceSheet.getDataRange(),
lastCol1 = range1.getNumColumns(),
lastCol2 = range2.getNumColumns(),
values1 = range1.getValues(),
values2 = range2.getValues(),
// get the range of the titles
titleSection1 = outputSheet.getRange(1,1,1, lastCol1),
titleSection2 = sourceSheet.getRange(1,1,1, lastCol2),
// get the values from the titles
titles1 = titleSection1.getValues(),
titles2 = titleSection2.getValues(),
// get the column # for "ID" and "comment"
idCol1 = titles1[0].indexOf("ID"),
idCol2 = titles2[0].indexOf("ID"),
commentsCol1 = titles1[0].indexOf("comment"),
commentsCol2 = titles2[0].indexOf("comment");
// get the IDs from range1
for (i = 1; i < values1.length; i++) {
compare1 = values1[i][idCol1];
// get the IDs from range2
for (j = 1; j< values2.length; j++){
compare2 = values2[j][idCol2];
// if same ID, change the values array
if (compare1 == compare2) {
values1[i][commentsCol1] = values2[j][commentsCol2];
}
}
}
// set values based on the values array
range1.setValues(values1);
}
Instead, if there is a change made to any cell on sheet 1, it will find the identical cell based on unique ID in the other sheet and sync the change. What change do i need to make to have this work?
For example, if I change what the office is in the row of ID 1 of sheet 1, it will make the identical change for ID 1 in sheet 2.
Here is an example sheet of what im working with:
Sheet 1:
ID Comment Number Office Clinician
1 good 22345 Dallas
2 bad 12345 Denton
3 good 95954 Lubbock
4 bad 20204 FT.W
5 bad 11111 Denton
6 good 02944 Preston
Sheet 2:
ID Comment Number Office Clinician
1 good 22345 Dallas
3 good 95954 Lubbock
5 bad 11111 Denton
You have one data set on Sheet 1, and a subset of that data set on Sheet1. The key field is the "ID". If there is a change to data on Sheet 1, and the ID relating to that change is found on Sheet 2, then you want to update the relevant dataset on Sheet 2.
The key aspects of this answer are:
onEdit(e): This is an simple Trigger.
e.range: "range" is an Event Object. By using the attribute 'e'; it is possible to recover a substantial amount of information about the changes. In addition, the event objects can be further used to obtain more information - such as (in this case) the Row, Column and Sheet Name.
filter(String).length: Sometimes getting the last row of data is problematic. The answer gets all the data in Column A, and uses the Javascript "array.filter" method. In this case is simply counts the values as strings, andf the resulting value is equal to the Last Row of data.
the "IF" statement evaluates for several attributes:
Is the edit on Sheet1?
Is the edit on a row between the header and last row?
Is the edit in a column that contains data?
the operator is "&&" which requires that each of the attributes must return true.
targetdata.map(function(e){return e[0];});: the script gets ALL the data on Sheet2, but uses the Javascript array.map method to generate a subject of just the values in Column 1 (the ID column).
targetCol1.indexOf(sourceID);: The script uses the Javascript "array.indexOf"; if the ID is found, it will return the index number of the row on Sheet2; if the value isn't found, it will return "-1". The enables a logic statement to be written that will be executed only if the value is not "-1"
target.getRange(+result+1,1,1,sourceLC).setValues(sourcedatatocopy): the last lines of the script get the values in the edited row on Sheet 1, and then update the values on Sheet2. Note: it updates all the values for the matched ID - rather than identifying the changed field and updating only that field.
function onEdit(e) {
// setup spreadsheet and sheet names
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourceSN = "Sheet1";
var source = ss.getSheetByName(sourceSN);
var targetSN = "Sheet2";
var target = ss.getSheetByName(targetSN);
// get row, column and sheet of the edited sheet
var row = e.range.getRow();
var col = e.range.getColumn();
var editedsheet = e.range.getSheet().getSheetName();
// Logger.log("DEBUG: editedsheet = "+editedsheet)
// get the ID column from Sheet 1
var SourceCol1Vals = ss.getRange("A2:A").getValues();
var SourceLR = SourceCol1Vals.filter(String).length;
// assign value to last column
var sourceLC = 5;
// test for sheet, row range and column range
if (editedsheet == sourceSN && row >=2 && row <=SourceLR && col <=5){
//Logger.log("DEBUG: this is sheet 1 & the right row and the right Col")
// get the data for this row
var sourcedata = source.getRange(row,1,1,5).getValues();
// get the data from target
var targetdata = target.getDataRange().getValues();
// get only the ID column
var targetCol1 = targetdata.map(function(e){return e[0];});
// get the sourse ID
var sourceID = sourcedata[0][0];
// Logger.log("DEBUG: source ID = "+sourceID)
// search for source ID on the Target list
var result = targetCol1.indexOf(sourceID);
// Logger.log("DEBUG: id was found at "+result);
if (result !=-1){
// if -1 then couldn't find the ID, otherwise it returns the index number where it finds the match
// get the data for the Source ID
var sourcedatatocopy = source.getRange(row,1,1,sourceLC).getValues();
// update the darget for the sourceID data.
target.getRange(+result+1,1,1,sourceLC).setValues(sourcedatatocopy)
}
} else{
// Logger.log("DEBUG: not sheet 1 or right row or right col");
}
return;
}

Add/Subtract Inventory Data in MULTIPLE columns, not just ONE column

What should I do to change the code below (at the very bottom) so that it works for multiple column pairs, not just one column pair?
I am tracking inventory in a sheet, something like this:
ColA ColB ColC ColD ColE ColF ColG ColH
Category Item R+/- G+/- B+/- Red Green Blue
AAA A 1 0 0
AAA B 2 1 0
I want to be able to type numbers into ColC, ColD and ColE and then click a button to subtract those numbers from the totals in ColF, G and H, respectively.
I found a similar question with a great answer here, for ONE column pair:
Google Sheets - How to create add and subtract buttons to track inventory. The code got me started. I'm pretty sure I needed to update the getRange stuff from what it was (shown immediately below) to what is now listed in the whole function code further down. (I also changed some names/variables to better match my inventory needs.)
var updateRange = sheet.getRange(2, 3, maxRows); // row, column, number of rows
var totalRange = sheet.getRange(2, 4, maxRows);
But what do I do with the for section so that it works for all three column pairs, not just for ColC & ColF? I tried adding a "var col in updateValues" but it didn't like col (or column); besides i wasn't sure how to nest it with the var row that's already there. (I did notice that if I changed the 0 after each [row] to 1, it would do the 2nd columns. But it didn't like it when I did "var col in updateValues" and then "updateValue[0][col]".)
function subtractUpdateBulk() {
var sheet = SpreadsheetApp.getActiveSheet();
var maxRows = sheet.getMaxRows();
var updateRange = sheet.getRange(2, 3, maxRows, 3); // row, column, # of rows, # of cols
var totalRange = sheet.getRange(2, 6, maxRows, 3);
var updateValues = updateRange.getValues();
var totalValues = totalRange.getValues();
for (var row in updateValues) {
var updateCellData = updateValues[row][0];
var totalCellData = totalValues[row][0];
if (updateCellData != "" && totalCellData != "") {
totalValues[row][0] = totalCellData - updateCellData;
updateValues[row][0] = "";
}
}
updateRange.setValues(soldValues);
totalRange.setValues(totalValues);
}
If you offer code alone is great. A bit of explanation to go with it would be even better, so I understand the WHY and can hopefully apply it elsewhere.
Your code was pretty close.
This is the substitute code to manage three columns of data movements and three columns of totals. Most of the code is self-explanatory but I'll focus on a couple of points. Its good that you're interested in the why as well as the how, and I've left some DEBUG lines that hopefully with assist.
1) I setup the spreadsheet and sheet using "standard" commands. In this case, I used getSheetByName to ensure that the code always would execute on the desired sheet.
2) I didn't use getMaxRows because this returns "the current number of rows in the sheet, regardless of content.". So if your spreadsheet has 1,000 rows but you've only got, say, 20 rows of data, getmaxRows will return a value of 1,000 and force you to evaluate more rows than are populated with data. Instead I used the code on lines 30 and 30 var Avals and var Alast which use a javascript command to quickly return the number of rows that have data. I chose Column A to use for this, but you could change this to some other column.
3) Rather than declare and get values for two ranges (updateRange and totalRange), I declared only one data range totalRange and got the values for all 6 columns. getValues is a fairly time costly process; by getting values for all rows and all columns, you can then pick and choose which columns you want to add together.
The command is:
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6);
The syntax (as you noted) is row, column, # of rows, # of cols.
The start row is the row following the header => (NumHeaderRows + 1).
The start column is Column C => 3.
The number of rows is the data rows less the header rows => (Alast - NumHeaderRows)
The number of columns is ColC, ColD, ColE, ColF, ColG => 6
4) for (var row in totalValues) {
This was a new one for me, and its so simple, so I keep it.
5) I used two arrays, just as you did. I used one array (RowArray) to build the values for each row, and the second array (MasterArray)is cumulative.
BTW, in your code soldValues isn't ever declared and no values are ever assigned to it.
6) The most important thing is the calculation of the adjustments on each line:
For the sake of clarity, I declared three variables totalRed, totalGreen and totalBlue, and showed how the totals were calculated for each value. This wasn't strictly necessary (I could have just pushed the formulas for each new totals), but they enable you to how each movement is calculated, and the column numbers used in each case.
function so_53505294() {
// set up spreadsheet
// include getsheetbyname to ensure calculations happen on the correct sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("Sheet1");
//set some variables
var NumHeaderRows = 1; // this is the number of header rows - user changeable
var totalRed = 0;
var totalGreen = 0;
var totalBlue = 0;
// arrays used later in loop
var RowArray = []; // row by row values
var MasterArray = []; // row by row, cumulative values
// get number of rows of data
var Avals = ss.getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// Logger.log("the last row = "+Alast);// DEBUG
// define the entire data range
var totalRange = sheet.getRange(NumHeaderRows + 1, 3, Alast - NumHeaderRows, 6); // row, column, # of rows, # of cols
//Logger.log("the TotalRange = "+totalRange.getA1Notation());//DEBUG
// get the data fior the entire range
var totalValues = totalRange.getValues();
// loop through thr rows
for (var row in totalValues) {
// clear RowArray at the start of each new row
RowArray = [];
// calculate the new totals
totalRed = totalValues[row][0] + totalValues[row][3];
totalGreen = totalValues[row][1] + totalValues[row][4];
totalBlue = totalValues[row][2] + totalValues[row][5];
//Logger.log("row = "+row+", Movement RED = "+totalValues[row][0]+", Old Stock RED = "+totalValues[row][3]+", New RED = "+totalRed); //DEBUG
//Logger.log("row = "+row+", Movement GREEN = "+totalValues[row][1]+", Old Stock GREEN = "+totalValues[row][4]+", New GREEN = "+totalGreen); //DEBUG
//Logger.log("row = "+row+", Movement BLUE = "+totalValues[row][2]+", Old Stock BLUE = "+totalValues[row][5]+", New BLUE = "+totalBlue); //DEBUG
// update the RowArray for this row's values
RowArray.push(0, 0, 0, totalRed, totalGreen, totalBlue);
// update the MasterArray for this row's values
MasterArray.push(RowArray);
}
// Update the data range with the new Master values.
totalRange.setValues(MasterArray);
}

Retrieving a specific cell value from Google app script sheet using JavaScript

I'm trying to retrieve a specific value from a google app script sheet. I have a sheet where it stores information. I want to loop through that sheet based on row and retrieve all values that match and meet the conditions given.
For example
if (row[4].toString().toLowerCase() == anotherRow[4]){
//then display all rows which match that specific value
// e.g:
row[4]. Display everything that matches anotherRow only;
}
this is what I can't get my head around displaying all rows that meet that criteria only, currently I'm able to display all row[4], row[4] is the column
This script that will get a range of data and store it as an array of arrays.
function getMultipleValues(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
// getRange exp. -
//sheet.getRange(first row, first column, last row, last column)
// Get a grid of data 4 rows and two columns
var range = sheet.getRange(1,1,4,2);
var dataGrid = range.getValues()
Logger.log(dataGrid)
//[["Fruit","quantity"],["pears",1],["apples",1],["grapes",3]]
//Get a column of data
var range = sheet.getRange(1,1,4,1);
var dataColumn = range.getValues()
Logger.log(dataColumn)
//[["Fruit"],["pears],["apples"],["grapes"]]
//If the column length is unknown. Get everything.
//Be sure to delete rows that are not needed in the sheet when using this.
var range = sheet.getRange("A1:A");
var allOfColumnA = range.getValues()
Logger.log(allOfColumnA)
//[["Fruit"],["pears],["apples"],["grapes"],[],[],[],[]]
}
Building on Supertopaz' answer, it looks like the filter method of an array will work to remove rows that don't match your criteria. For example, given a set of data like this:
Source data for function
The following function will match the data to the criteria you specify, and write the matches to a second sheet.
function writeMatches() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheetByName('Sheet1');
var sheet2 = ss.getSheetByName('Sheet2');
var criteria = sheet1.getRange(1,2,1,1).getValue(); // returns a single value if the range is a single cell
var arData = sheet1.getRange(4,1,5,2).getValues(); // returns a 2d array
var arOutput = [];
var rangeOutput;
arOutput = arData.filter(function(row){
return row[1].toLowerCase() === criteria
/* The callback function iterates through the array.
When this expression evaluates to true, the
element is included in the output array.
The callback function can also supply an optional
index argument, if you want to compare symmetric
arrays.
*/
});
rangeOutput = sheet2.getRange(1,1,arOutput.length,arOutput[0].length);
rangeOutput.setValues(arOutput);
}

Paste values from one sheet to another and remove duplicates

I have two worksheets in my google spreadsheet:
Input data is coming into the Get Data worksheet via the importxml function.
However, I would like to copy all values of the Get Data sheet to the Final Data sheet and if there are duplicates(in terms of rows) append the unique row.
Here is what I tried:
function onEdit() {
//get the data from old Spreadsheet
var ss = SpreadsheetApp.openById("1bm2ia--F2b0495iTJotp4Kv1QAW-wGUGDUROwM9B-D0");
var dataRange = ss.getSheetByName("Get Data").getRange(1, 1, ss.getLastRow(), ss.getLastColumn());
var dataRangeFinalData = ss.getSheetByName("Final Data").getRange(1, 1, ss.getLastRow(), ss.getLastColumn());
var myData = dataRange.getValues();
//Open new Spreadsheet & paste the data
newSS = SpreadsheetApp.openById("1bm2ia--F2b0495iTJotp4Kv1QAW-wGUGDUROwM9B-D0");
Logger.log(newSS.getLastRow());
newSS.getSheetByName("Final Data").getRange(newSS.getLastRow()+1, 1, ss.getLastRow(), ss.getLastColumn()).setValues(myData);
//remove duplicates in the new sheet
removeDups(dataRangeFinalData)
}
function getId() {
Browser.msgBox('Spreadsheet key: ' + SpreadsheetApp.getActiveSpreadsheet().getId());
}
function removeDups(array) {
var outArray = [];
array.sort(lowerCase);
function lowerCase(a,b){
return a.toLowerCase()>b.toLowerCase() ? 1 : -1;// sort function that does not "see" letter case
}
outArray.push(array[0]);
for(var n in array){
Logger.log(outArray[outArray.length-1]+' = '+array[n]+' ?');
if(outArray[outArray.length-1].toLowerCase()!=array[n].toLowerCase()){
outArray.push(array[n]);
}
}
return outArray;
}
Below you can find the link to a sample spreadsheet:
Sample Sheet
My problem is that the data does not get pasted.
I appreciate your replies!
tl;dr: See script at bottom.
An onEdit() function is inappropriate for your use case, as cell contents modified by spreadsheet functions are not considered "edit" events. You can read more about that in this answer. If you want this to be automated, then a timed trigger function would be appropriate. Alternatively, you could manually invoke the function by a menu item, say. I'll leave that to you to decide, as the real meat of your problem is how to ensure row-level uniqueness in your final data set.
Merging unique rows
Although your original code is incomplete, it appears you were intending to first remove duplicates from the source data, utilizing case-insensitive string comparisons. I'll suggest instead that some other JavaScript magic would help here.
We're interested in uniqueness in our destination data, so we need to have a way to compare new rows to what we already have. If we had arrays of strings or numbers, then we could just use the techniques in How to merge two arrays in Javascript and de-duplicate items. However, there's a complication here, because we have an array of arrays, and arrays cannot be directly compared.
Hash
Fine - we could still compare rows element-by-element, which would require a simple loop over all columns in the rows we were comparing. Simple, but slow, what we would call an O(n2) solution (Order n-squared). As the number of rows to compare increased, the number of unique comparison operations would increase exponentially. So, let's not do that.
Instead, we'll create a separate data structure that mirrors our destination data but is very efficient for comparisons, a hash.
In JavaScript we can quickly access the properties of an object by their name, or key. Further, that key can be any string. We can create a simple hash table then, with an object whose properties are named using strings generated from the rows of our destination data. For example, this would create a hash object, then add the array row to it:
var destHash = {};
destHash[row.join('')] = true; // could be anything
To create our key, we're joining all the values in the row array with no separator. Now, to test for uniqueness of a row, we just check for existence of an object property with an identically-formed key. Like this:
var alreadyExists = destHash.hasOwnProperty(row.join(''));
One additional consideration: since the source data can conceivably contain duplicate rows that aren't yet in the destination data, we need to continuously expand the hash table as unique rows are identified.
Filter & Concatenate
JavaScript provides two built-in array methods that we'll use to filter out known rows, and concatenate only unique rows to our destination data.
In its simple form, that would look like this:
// Concatentate source rows to dest rows if they satisfy a uniqueness filter
var mergedData = destData.concat(sourceData.filter(function (row) {
// Return true if given row is unique
}));
You can read that as "create an array named mergedData that consists of the current contents of the array named destData, with filtered rows of the sourceData array concatenated to it."
You'll find in the final function that it's a little more complex due to the other considerations already mentioned.
Update spreadsheet
Once we have our mergedData array, it just needs to be written into the destination Sheet.
Padding rows: The source data contains rows of inconsistent width, which will be a problem when calling setValues(), which expects all rows to be squared off. This will require that we examine and pad rows to avoid this sort of error:
Incorrect range width, was 6 but should be 5 (line ?, file "Code")
Padding rows is done by pushing blank "cells" at the end of the row array until it reaches the intended length.
for (var col=mergedData[row].length; col<mergedWidth; col++)
mergedData[row].push('');
With that taken care of for each row, we're finally ready to write out the result.
Final script
function appendUniqueRows() {
var ss = SpreadsheetApp.getActive();
var sourceSheet = ss.getSheetByName('Get Data');
var destSheet = ss.getSheetByName('Final Data');
var sourceData = sourceSheet.getDataRange().getValues();
var destData = destSheet.getDataRange().getValues();
// Check whether destination sheet is empty
if (destData.length === 1 && "" === destData[0].join('')) {
// Empty, so ignore the phantom row
destData = [];
}
// Generate hash for comparisons
var destHash = {};
destData.forEach(function(row) {
destHash[row.join('')] = true; // could be anything
});
// Concatentate source rows to dest rows if they satisfy a uniqueness filter
var mergedData = destData.concat(sourceData.filter(function (row) {
var hashedRow = row.join('');
if (!destHash.hasOwnProperty(hashedRow)) {
// This row is unique
destHash[hashedRow] = true; // Add to hash for future comparisons
return true; // filter -> true
}
return false; // not unique, filter -> false
}));
// Check whether two data sets were the same width
var sourceWidth = (sourceData.length > 0) ? sourceData[0].length : 0;
var destWidth = (destData.length > 0) ? destData[0].length : 0;
if (sourceWidth !== destWidth) {
// Pad out all columns for the new row
var mergedWidth = Math.max(sourceWidth,destWidth);
for (var row=0; row<mergedData.length; row++) {
for (var col=mergedData[row].length; col<mergedWidth; col++)
mergedData[row].push('');
}
}
// Write merged data to destination sheet
destSheet.getRange(1, 1, mergedData.length, mergedData[0].length)
.setValues(mergedData);
}

Categories

Resources