Google script loop check for duplicate before writing data - javascript

I have a script which reads data from a site, stores the data in an array variable and then writes the data to a google sheet.
Per item id (JSON format), the data which is read is of the form:
[timestamp number text1 text2]
and these details are duplicated across different ids in a for loop.
What i'm left with on the sheet is per row (one item in each cell):
timestamp(id1) number1(id1) text1(id1) text2(id1) timestamp(id2) number1(id2) text1(id2) text2(id2) timestamp(id3) number1(id3)...etc
each row will contain only a single value for timestamp, however the timestamp variable is written multiple times. Is it possible to adapt my script to check column A of the bottom row on my sheet and only write the new row if the timestamp in the current bottom row is different to the timestamp in the new row about to be written.
for loop iterates through json file and stores data in "values" variable.
{
{.....
let values = [];
values.push(timestamp, number1, text1, text2); //final line of for loop
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("test");
var range = ss.getRange(3, 1, 1, values.length);
if (range.isBlank()) {
range.setValues([values]);
} else {
ss.appendRow(values);
}
}
2 Requests:
a) I would like the timestamp variable to only be written once, in column A.
b) I would like the script to check the last written row to ensure that the timestamp value printed in column A is different to the value about to be written in the new row. If it is the same, do not write to the row.
Thanks

So for Request A: You need to change the array you are passing to the setValues()method. If you already know which columns these are, then you can modify the array by replacing the existing value with an empty string.
const outputRow = [ … ]; // This is where your current output is set
const emptyTheseColumns = [3, 6, 9]; // columns C, F, I
const cleanedOutputRow = outputRow.map( (item, index) => {
const column = index + 1; // columns start at 1, index at 0
// return empty string for stated columns
if( emptyTheseColumns.indexOf( column ) != -1){
return ""
}
// otherwise return the current value
return item
});
// then use the new array in setValues( cleanedOutputRow )

Related

Apps Script for Google Sheets: setValues parameters

I've been writing a script to copy some data from an input sheet to a database to keep track of some data. I've managed to successfully write the code for linear arrays (only one row) but when I try to copy an entire 15x15 cells range I get an error stating the parameters are not correct (suggesting the dimension of the arrays are not correct) but I can't seem to understand why.
I tried both copying directly the entire 15x15 range and creating a for loop to copy row by row 15 times but I can't mangage to make it work.
Here is the main structure:
// active spreadsheet and source + target sheets
const activeSheet = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = activeSheet.getSheetByName('Data Entry');
const dstTOTSheet = activeSheet.getSheetByName('DataBaseTOT');
var firstEmptyRowTOT = dstTOTSheet.getLastRow()+1;
For loop test:
for (var i=0; i=14;i=i+1) {
// source cells
var RoundInfo = srcSheet.getRange(10+i, 2, 1, 15); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT + i, 21, 1, 15); // I am starting from column 21 because of some other data
// set value
dstTOTRoundInfo.setValues(RoundInfo);
}
Direct 15x15 test:
// source cells
var RoundInfo = srcSheet.getRange("B10:P24"); // 15x15 B10:P24
// target cells
var dstTOTRoundInfo = dstTOTSheet.getRange(firstEmptyRowTOT, 21, 15, 15);
// set value
dstTOTRoundInfo.setValues(RoundInfo);
It is actually not that difficult to understand the 2D array concept for spreadsheet, it works like this:
// inner arrary is always one row of values:
// the 1st value (array index: 0) of the inner-array is always the cell value of the 1st column of the given row.
const row_1 = ['row_value_1-1','row_value_1-2','row_value_1-3','row_value_1-4'];
const row_2 = ['row_value_2-1','row_value_2-2','row_value_2-3','row_value_2-4'];
const row_3 = ['row_value_3-1','row_value_3-2','row_value_3-3','row_value_3-4'];
// the outter-array is always the container of every rows you have in range,
// the leangth of rows (which is the leangth of each inner-array) must be the same for this structure to work in spreadsheet.
const data = [
row_1, // the 1st value (array index: 0) of the outter-array is always the 1st row,
row_2, // the 2nd value (array index: 1) of the outter-array is always the 2nd row, etc.
row_3
];
// when you loop over such kind of 2D array, it is easier to understand with a "for... of" loop than the classic "for" loop:
for (const row of data) { // << this means "for each row of the given data"
for (const col of row) { // << this means "for each column of the given row"
// this structure will go through each cell of row 1, than each cell of row 2, etc., until all rows of the given data are iterated.
console.log(col);
}
}
/**output:
row_value_1-1
row_value_1-2
row_value_1-3
row_value_1-4
row_value_2-1
row_value_2-2
row_value_2-3
row_value_2-4
row_value_3-1
row_value_3-2
row_value_3-3
row_value_3-4
*/
// and if you need to works with the indexes of some cell, you can get the index by this:
for (const [rIndex,row] of data.entries()) {
for (const [cIndex,col] of row.entries()) {
console.log(rIndex,cIndex,col);
}
}
/** output:
0 0 row_value_1-1
0 1 row_value_1-2
0 2 row_value_1-3
0 3 row_value_1-4
1 0 row_value_2-1
1 1 row_value_2-2
1 2 row_value_2-3
1 3 row_value_2-4
2 0 row_value_3-1
2 1 row_value_3-2
2 2 row_value_3-3
2 3 row_value_3-4
*/

How do I iterate through my google sheets rows, update an array, and show results in another sheet?

I am trying to write a Google Apps Script function which scans every row of my spreadsheet, and if column 36 has a specific string, will look at column 31, column 23, etc. of that row to create an array with the data in these columns.
I think it will ultimately require an array updating function inside an if statement inside a loop.
Loop through every row of column 36
If string matches target string
Add row's data to an array
Update another sheet with data from this array
Thank you!
Here is what I've tried so far:
`
function myFunction() {
var sheet = SpreadsheetApp.getActive().getSheetByName("Database");
var range = sheet.getRange(2, 36).getValue();
Logger.log(range);
}
`
Here is the code, replace the string of Target Sheet Name, the specific String and etc on your own.
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// DataBase sheet:
const dbSheet = ss.getSheetByName("Database");
// Target sheet for placing your outputs:
const tgSheet = ss.getSheetByName("Target Sheet Name");
// Get all values as a 2D array from DataBase sheet:
const sourceValues = dbSheet.getDataRange().getValues();
// Declare result array.
const results = [];
// Loop through every row:
for (const row of sourceValues) {
const specificString = 'Your String';
// check the value of column 36 of each row.
const check = row[36] === specificString;
// if condition check returns true, form an array with column 31, column 23 and 'etc' of this row, than push it into the result array.
if(check) results.push([row[31],row[23],'etc']);
}
// print the result onto your Target Sheet, start from A1.
tgSheet.getRange(1,1,results.length,results[0].length).setValues(results);
}
Move selected rows to another sheet
function elfunko(specificvalue) {
const ss = SpreadsheetApp.getActive();
const vs = ss.getSheetByName("Database").getDataRange().getValues().filter(r => r[35] == specificvalue);
const sh = ss.getSheetByName("Sheet1");
sh.getRange(sh.getLastRow() + 1, 1, vs.length, vs[0].length).setValues(vs);
}

Move Specific Rows depending on Filtering Keywords within unknown amount of rows using Google Sheets Apps Scripts

I do SEO, and therefore I have a lot of keywords flowing around in different spreadsheets. I'd like a way to filter these into seperate sheets based on specific filters, but I can't for the life of me, figure out how to do this in Google Apps Script.
Criteria I set myself for this to work out:
A list of strings and their corresponding volumes are entered in column 1+2.
A list of filter-words are written in column 3.
The script has to create a new sheet for each of the filter words and move the strings + volumes into these different sheets if the string contains a filter word.
Example:
Filter words: Apple, Banana, Pineapple
String: "The Apple Was Big", Volume: "100"
The script would move the string and volume into the sheet called "Apple" on row 1
(Beware, I'm in no means experienced in coding)
I believe you can use the following structure:
for(let i = 0; i <= column3RowAmount; i++){ //Run as long as there are more filter words
create(column3Row[i]); //create a new sheet with the name of the filter word
for(let j = 0; j <= column1RowAmount; j++){ //Run as long as there are more keywords
if(column1Row[j].indexOf(column3Row[i]) >= 0){ //If the Row in column 1 contains the filter word
column1Row[j].moveToSheet(column3Row[i]); // Make sure not to move Column 3, but only 1+2
}
}
}
Example sheet: https://docs.google.com/spreadsheets/d/15YIMyGmmfZdy094gwuJNxFmTd8h7NOLnA8KevZrGtdU/edit?usp=sharing
Explanation:
Your goal is to create a sheet for every filter-word in column C. Then copy the data in columns A, B but only the rows that include the filter-word to the corresponding sheet.
For starters, you need to get the filter-word list. You can get the full range of column C and filter out the empty cells:
const sh_names = sh.getRange('C1:C').getValues().flat().filter(r=>r!='');
Similarly, you need to get the data in columns A and B:
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
The next step is to iterate over sh_names and for every element / filter-word, check if a sheet with that name exists. If it does not exist, then create a sheet with that name, if it exists then skip the creation part:
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
The next step is to filter data on the rows that include the filter-word:
let f_data = data.filter(r=>r[0].includes(s));
Finally, check if the length of the data is bigger than 0, otherwise there is not data to use and set the values of data to the corresponding sheet:
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data)
Solution
function myFunction() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Ark1');
const filter_sh = ss.getSheetByName('Filter');
const data = sh.getRange('A1:B'+sh.getLastRow()).getValues();
const sh_names = filter_sh.getRange('A1:A'+filter_sh.getLastRow()).getValues().flat();
sh_names.forEach(s=>{
if(!ss.getSheetByName(s)){
ss.insertSheet().setName(s);}
let sheet = ss.getSheetByName(s);
let f_data = data.filter(r=>r[0].includes(s));
if(f_data.length>0){
sheet.getRange(sheet.getLastRow()+1,1,f_data.length,f_data[0].length).setValues(f_data);}
});
}
This function will place all of your results into column 4 next to the appropriate word rather than creating a page for each word. So it runs much faster.
function stringswords() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const sr=2;
const rgd=sh.getRange(sr,1,sh.getLastRow()-sr+1,2);
const data=rgd.getDisplayValues();
const rgw=sh.getRange(sr,3,sh.getLastRow()-sr+1,1);
const words=rgw.getDisplayValues().flat();
const wiObj={};
words.forEach(function(w,i){wiObj[w]=i});
const rgr=sh.getRange(sr,4,sh.getLastRow()-sr+1,1);
rgr.clearContent();
var results=rgr.getValues();
words.forEach(function(w,i,A){
data.forEach(function(r,j,D) {
if(data[j][0] && data[j][0].indexOf(w)!=-1) {
results[wiObj[w]][0]+=Utilities.formatString('String:%s Vol:%s\n',data[j][0],data[j][1]);
}
});
});
rgr.setValues(results);
}
Image of Data and output:

Writing array to single row

I have created an API which iterates through JSON format data, reading 2 items per ID. I'm storing this data in an array called values[].
How about this modification?
Modification points:
In order to put the values of values to one row, please modify values.push([timestamp, price]); to values.push(timestamp, price);. By this, each value is put in values which is one dimensional array.
In order to put values from the row 3, in this modification, it checks whether the row 3 for putting values is empty.
When above points are reflected to your script, it becomes as follows.
Modified script:
From:
values.push([timestamp, price]);
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("rawStockData");
ss.getRange("A3:BH3").setValues(values);
}
To:
values.push(timestamp, price); // Modified
}
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("rawStockData");
var range = ss.getRange(3, 1, 1, values.length); // Modified
if (range.isBlank()) { // Added
range.setValues([values]);
} else {
ss.appendRow(values);
}
}
References:
push()
appendRow(rowContents)

How to compare two sheets and delete/add any column with a distinct value in row 1? Google Script

I want to compare two sheets (based on header values in row 1) and delete any column with a unique value (without a match). For example, Assuming Sheet1, Row 1 data and Sheet 2, Row 1 are uniform, if a user adds/deletes a column within any sheet, I want to always match the number of columns in both sheets with their values
Screenshots of sheets headings.
IF both sheets looks like this
And a user adds a new Column N
Or delete column N
How can I ensure that both sheet matches by deleting the odd/distinct column in Sheet 1?
I have tried modifying this code below but I can't just get the unique one out. This code only look for headers with a defined value.
function deleteAloneColumns(){
var sheet = SpreadsheetApp.getActiveSheet();
var lastColumnPos = sheet.getLastColumn();
var headers = sheet.getRange( 1 ,1, 1, lastColumnPos ).getValues()[0];
for( var i = lastColumnPos ; i < 1; i--){
if( headers[i] === "alone" ) sheet.deleteColumn(i);
}
SpreadsheetApp.getUi().alert( 'Job done!' );
}
Any help to compare and delete the column with the unique value will be appreciated.
Problem
Balancing sheets based on header row values mismatch.
Solution
If I understood you correctly, you have a source sheet against which validation is run and two primary use cases: user adds a new column named differently than any other column (if you want to check that the column strictly matches the one in sheet1, it is easy to modify) in source sheet or deletes one that should be there.
const balanceSheets = (sourceShName = 'Sheet1',targetShName = 'Sheet2') => {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const s1 = ss.getSheetByName(sourceShName);
const s2 = ss.getSheetByName(targetShName);
const s2lcol = s2.getLastColumn();
//keep all vals from source to reduce I/O
const s1DataVals = s1.getDataRange().getValues();
const s2Vals = s2.getRange(1, 1, 1, s2lcol).getValues();
const h1Vals = s1DataVals[0];
const h2Vals = s2Vals[0];
//assume s1 is source (validation) sheet
//assume s2 is target sheet that a user can edit
//case 1: target has value not present in source -> delete column in target
let colIdx = 0;
h2Vals.forEach(value => {
const isOK = h1Vals.some(val => val===value);
isOK ? colIdx++ : s2.deleteColumn(colIdx+1);
});
//case 2: target does not have values present in source -> append column from source
h1Vals.forEach((value,index) => {
const isOK = h2Vals.some(val => val===value);
!isOK && s2.insertColumnAfter(index);
const valuesToInsert = s1DataVals.map(row => [row[index]]);
const numRowsToInsert = valuesToInsert.length;
s2.getRange(1,index+1, numRowsToInsert,1).setValues(valuesToInsert);
});
};
Showcase
Here is a small demo of how it works as a macros:
Notes
Solving your problem with two forEach is suboptimal, but I kept number of I/O low (it can be lowered further by, for example, moving deleteColum out of the loop while only keeping track of column indices).
The script uses ES6 capabilities provided by V8, so please, be careful (although I would recommend migrating as soon as possible - even if you encounter bugs / inconsistencies , it is worth more than it costs.
UPD made script more flexible by moving sheet names to parameter list.
UPD2 after discussing the issue with deleteColumn() behaviour, the answer is updated to keep column pointer in bounds (for those curious about it - forEach kept incrementing the index, while deleteColumn reduced bounds for any given index).
Reference
insertColumnAfter() method reference

Categories

Resources