a really quick question... I want to read rows in sheets one by one, (preferably 'live' so rather than putting it in an array or something). Besides using a loop, how do I read the contents of a whole row? Is it even possible?
You can read about it here
function readData() {
var sht = SpreadsheetApp.getActiveSheet();
var rng = sht.getRange(rownumber, 1, 1, numberofcolums)
var rangeArray = rng.getValues();
//now all your data for that row is in a two Dimensional array [[1,2,3,4,'My Data','etc']]
}
var
spreadsheetID =
"1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms",
rowNum = 1,
rowVals =
SpreadsheetApp.openById(spreadsheetID)
.getRange(rowNum +":"+ rowNum)
.getValues()
;
The other answers to date don't get a row, they get a range of specfic columns within a row. The code in this answer gets the entire unbounded row.
Not sure exactly what your end goal is, but I assume you mean something like this:
var values = sheet.getRange(startRow, startCol, numRows, numCols).getValues(); // returns 2d array
values.forEach(function(row){
Logger.log(row); // prints the contents of the row
});
You could also use the getCell() method of the Range class to iterate over cells in a range. The return type for getCell() is also Range. See this thread for details on the implementation Google Spreadsheets: Iterate over range, append string to each
Related
I have this script which is working well, but i need to edit it to
a) only return new rows since last run
b) only return certain cells instead of whole row
any guidance would be greatly appreciated
function Copy() {
var sourceSheet = SpreadsheetApp.openById('1WAtRDYhfVXcBKQoUxfTJORXwAqYvVG2Khl4GuJEYSIs')
.getSheetByName('Jobs Log');
var range = sourceSheet.getRange(1, 1, sourceSheet.getLastRow(), sourceSheet.getLastColumn());
var arr = [];
var rangeval = range.getValues()
.forEach(function (r, i, v) {
if (r[1] == 'Amber') arr.push(v[i]);
});
var destinationSheet = SpreadsheetApp.openById('137xdyV8LEh6GAhAwSx4GmRGusnjsHQ0VGlWbsDLXf2c')
.getSheetByName('Sheet1');
destinationSheet.getRange(destinationSheet.getLastRow() + 1, 1, arr.length, arr[0].length)
.setValues(arr);
}
In order to only check new data added after last runtime we have to store .getLastRow() value in properties and retrieve it every runtime. We would also have to work under a few assumptions:
In the input data new values are only appended at the bottom and never inserted between other data
Data is never deleted from the input sheet (if you ignore this, then you must also have an update script for the last row that runs after deleting data)
The sheet is not sorted after new data is added but before this script is run.
So you would need something along the lines of
var sourceSheet = SpreadsheetApp.openById('1WAtRDYhfVXcBKQoUxfTJORXwAqYvVG2Khl4GuJEYSIs')
.getSheetByName('Jobs Log');
var lastRow = sourceSheet.getLastRow();
// note that you need to hav the script property initialized and stored
// or adjust the if to also check if prevLastRow gets a value
var prevLastRow = PropertiesService.getScriptProperties().getProperty('lastRow')
if (lastRow <= prevLastRow) {
return; // we simply stop the execution right here if we don't have more data
}
// then we simply start the range from the previous last row
// and take the amount of rows added afterwards
var range = sourceSheet.getRange(prevLastRow,
1,
lastRow - prevLastRow,
sourceSheet.getLastColumn()
);
As for the second question, inside the forEach you need to simply push an array into arr that will contain only the columns you want. So for example
if (r[1] == 'Amber') arr.push(v[i]);
changes into
if (r[1] == 'Amber') arr.push([v[i][0], v[i][3], v[i][2]]);
which will output A D C columns (in that order) for each row.
Finally, the last thing you need to run before the script ends is
PropertiesService.getScriptProperties().setProperty('lastRow', lastRow)
which will let us know where we stopped the next time we run the script. Again, keep in mind that this works only if new data will always be in new rows. Otherwise, you need to do a different method and retrieve 2 arrays of data. 1 for the entire input sheet and 1 for the output sheet. Then you would have to perform 2 if checks. First one to see if your criteria are met and a second one to see if it already exists in the output data.
I know this has been done before, but I'm struggling to get mine working, I want to copy all data from sheet 'Templates' to 'History' sheet.
I want to send the values in a new row at the bottom of history sheet.
Here is my working code, I just need to change where the values are set.
var sheetTemplate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Template');
var sheetHistory = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('History');
var getRange = sheetTemplate.getDataRange();
var DataCopied = SRange.getValues();
sheetHistory.getRange("a2:F7").setValues(DataCopied);
You're using the getDataRange() and getValues() methods, this will return the corresponding values in which data is present in a two-dimensional array, so when you copy the values to the new sheet with setValues() you need to take into account the dimensions of the data retrieved so they match, you need to:
Get the last available row of the History Sheet with getLastRow()
Get the "height" of the DataCopied Array (number of rows) with DataCopied.length
Get the "width" of the DataCopied Array (number of columns) DataCopied[0].length.
Get the range of that dimensions with getRange(row, column, numRows, numColumns).
Set the values of the DataCopied with setValues()
Your code should look like this:
function myFunction(){
var sheetTemplate = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Template');
var sheetHistory = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('History');
var getRange = sheetTemplate.getDataRange();
var DataCopied = getRange.getValues();
// I defined the variables for better understanding
var startRow = sheetHistory.getLastRow()+1; // +1 because remember that while a range index starts at 1, 1, the JavaScript array will be indexed from [0][0].
var startColumn = 1;
var numRows = DataCopied.length;
var numColumns = DataCopied[0].length;
sheetHistory.getRange(startRow, startColumn, numRows, numColumns).setValues(DataCopied);
}
I am trying to import a CSV of approx. 7500 lines from a GMail attachment into Google Sheets and it is taking longer than the allowed 6 minutes to execute.
I am following this tutorial: https://developers.google.com/apps-script/articles/docslist_tutorial#section2 and my code is listed below
The section consuming all the time is the pushing of data into the new sheet:
// Push data into the sheet
for ( var n=0, lenCsv=csvData.length; n<lenCsv; n++ ) {
newsheet.getRange(n+1, 1, 1, csvData[n].length).setValues(new Array(csvData[n]));
}
Is there any way I can improve the performance of this and make it faster?
Thanks in advance.
My code is listed below.
var attachmentData = attachments[k].getDataAsString();
var attachmentClean = attachmentData.split(' ').join(',');
var attachmentCleanA = attachmentClean.split(',');
var csvData = Utilities.parseCsv(attachmentCleanA);
var SheetName = String(csvData[1]).substring(5,7)+ "-" + String(csvData[1]).substring(0,4);
Logger.log("SheetName:"+SheetName);
ss = SpreadsheetApp.openById('XXXXXXXXX');
var sheet = ss.getSheetByName(SheetName);
if (sheet == null){
var newsheet = ss.insertSheet(SheetName);
}else{
sheet.clearContents();
var newsheet = sheet;
}
Logger.log("Entering "+csvData.length+" rows");
// Push data into the sheet
for ( var n=0, lenCsv=csvData.length; n<lenCsv; n++ ) {
newsheet.getRange(n+1, 1, 1, csvData[n].length).setValues(new Array(csvData[n]));
}
This line:
var csvData = Utilities.parseCsv(attachmentCleanA);
Creates a two dimensional array. Google Documentation - parseCsv
If your csvData variable truly is a 2D array, then all you need to do is use this line of code without the for loop:
newsheet.getRange(1, 1, csvData.length, csvData[0].length).setValues(csvData);
The range starts in row one, column one, and sets a range that is the length of the outer array of the csvData, and the number of columns to the number of elements in the first inner array.
Note that if your data has inner arrays of different lengths, setValues() will produce an error.
If all of your inner arrays are not the same length, then you can not avoid looping through every inner array. But, I don't know what your data looks like. If each line of data in your CSV has the same number of vales in the row, then you don't need the for loop.
So, improving the part of the code that is taking all the time, is totally dependent upon whether the rows are all the same length or not.
You shouldn't need the new Array() part in the setValues() method. If the csvData array truly is a good 2D array, then it's already an array. You don't need to create another array from something that's already an array.
I want to merge several sheets into 1 spreadsheeet and want to paste all data below each other.
My current code looks like that:
function updateMaster() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var allSheets = ss.getSheets();
var repArray = new Array();
// build array of all sheets
for (i in allSheets) {
repArray.push(allSheets[i].getName());
}
// store all sheets in array
var sheetArray = [];
// loop through all rep sheets
for (var j in allSheets) {
// get each sheet
var tempSheet = ss.getSheetByName(repArray[j]);
// get sheet data
var dataRange = tempSheet.getDataRange().getValues();
// remove the first header row
dataRange.splice(parseInt(0), 1);
// append sheet data to array
var sheetArray = sheetArray.concat(dataRange);
}
// Time to update the master sheet
var mSheet = ss.getSheetByName("Master");
// clear the whole sheet
mSheet.clear({contentsOnly:true});
// write to the Master sheet via the array
mSheet.getRange(1, 1, sheetArray.length, 4).setValues(sheetArray); //I GET THE ERROR BECAUSE OF THIS LINE
// force spreadsheet updates
SpreadsheetApp.flush();
// pause (1,000 milliseconds = 1 second)
Utilities.sleep("200");
// delete empty rows at bottom
var last = mSheet.getLastRow();
var max = mSheet.getMaxRows();
if (last !== max) {mSheet.deleteRows(last+1,max-last);}
}
However, I get an error, because other sheets have not the same column length.
The error message:
Incorrect range width, was 5 but should be 4
Here I get the error:
mSheet.getRange(1, 1, sheetArray.length, 4).setValues(sheetArray);
I basically, just want to post the data below. Any recommendation how I could make the column length variable?
Here is a link to my spreadsheet:
https://docs.google.com/spreadsheets/d/1TuHaqs20PvNwOJ46bQOAwN3Nt5vyRl7Win1Lul4m-e8/edit?usp=sharing
I appreciate your replies!
The sheetArray array has inner row elements of different lengths. Some inner arrays have 4 elements, some have 5 elements. You can't set values from an array with different inner element lengths. If you change the column width to 5, the error states that it should be 4, if you change it to 4, it says that it should be 5. You need to either add an extra empty element to the range with 4 columns, or write each sheet to the master individually.
Get one of the inner arrays, and find the length of an inner array. That is your column length.
On this line:
var dataRange = tempSheet.getDataRange().getValues();
You are using the getDataRange() method. That causes the range to be different dimensions for each sheet that has a different number of columns. If you set the column width to the biggest number of columns in the sheet with the most columns, then all your inner elements would be the same length. Your code would write a lot of empty cells. But don't know if there is any downside to that .
var columsToWriteTo = sheetArray[0].length
mSheet.getRange(1, 1, sheetArray.length, columsToWriteTo).setValues(sheetArray);
The above code gets the first inner array of the two dimensional sheetArray, and gets the length of the inner array.
The other answer pointed the issue but did not provide a solution.
What you should simply do is to make the final array homogeneous before setting its value with setValues().
Below is a code demo that does it by adding empty items in rows that are too short. I added comments in code to explain
function ArrayNormalize() {
var sh = SpreadsheetApp.getActive();
var data1 = sh.getSheets()[0].getDataRange().getValues(); // get data from sheet1
var data2 = sh.getSheets()[1].getDataRange().getValues();// same from sheet2 that has a different width
var combine = data1.concat(data2);
Logger.log("combine = "+JSON.stringify(combine));
//get Max array width
var maxWidth=0
for(var n=0;n<combine.length;n++){
Logger.log(n+' - '+combine[n]+" length = "+combine[n].length);
if(combine[n].length>=maxWidth){maxWidth=combine[n].length};
}
Logger.log("maxWidth = "+maxWidth);
//normalize
for(var n=0;n<combine.length;n++){
while(combine[n].length<maxWidth){
combine[n].push('');// while row is shorter add empty "cells" (=array elements)
}
}
sh.insertSheet().getRange(1,1,combine.length,combine[0].length).setValues(combine);// insert the result in new sheet to check the result but you can insert it anywhere else of course
}
I've been searching for quite a while so hopefully no one else has asked this.
I have a Google Spreadsheet with two sheets, one a sort of database containing form submissions and the other a way for users to interact with submissions one at a time.
Basically I want users to be able to make changes to a submission and save them back to the same line that they came from in the original sheet.
I have the code to send the changes back but I can't figure out how to get the coordinates of the correct row:
function saveChanges() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var range = source.getRange("A40:BL40");
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
// This copies the data in A40:BL40 in the source sheet to
// D4:F6 in the second sheet
range.copyValuesToRange(destination, 1, 64, 16, 16);
}
At the moment the data is just written to the coordinates "1, 64, 16, 16" which just points to a currently empty row - ideally I'd change that to a variable with the right coordinates.
The value of cell A40 is a unique ID and ideal for searching the second sheet but I can't figure out how.
I'm very new to Javascript so any help would be greatly appreciated.
To find your matching value in the form response sheet, you must loop through the range to find a match. There are a number of ways to do that, I'll show a couple.
Here's a version of your saveChanges() function that will get all the data from your destination sheet, look through it's column A for a match to the value in A40, then update the data in that row.
function saveChanges() {
var uniqueIdColIndex = 0; // Col "A" has unique ID, is element 0 in row array
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var sourceData = source.getRange("A40:BL40").getValues();
var destData = destination.getDataRange().getValues();
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
for (var rowIndex=0; rowIndex < destData.length; rowIndex++) {
if (sourceData[0][uniqueIdColIndex] == destData[rowIndex][uniqueIdColIndex]) {
// Found our match
destination.getRange(rowIndex+1,1,sourceData.length,sourceData[0].length)
.setValues(sourceData);
break; // Done, exit loop
}
}
}
Here's another way to do it. This time, we don't read all the data in the destination sheet, only the info in column A. To be able to take advantage of array lookup methods, the two-dimensional array retrieved via .getValues() needs to be transposed first - so we use a helper function to do that. (I'm using the transpose() function from this answer.)
function saveChanges() {
var uniqueIdColIndex = 0; // Col "A" has unique ID, is element 0 in row array
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheets()[0];
var destination = ss.getSheets()[1];
var sourceData = source.getRange("A40:BL40").getValues();
// Get column A from destination sheet
var destDataTrans = transpose(destination.getRange(1, 1, destination.getLastRow(),1).getValues());
// Find coordinates of the row where value of cell A40 matches a cell in A:A in second spreadsheet
var destRow = destDataTrans[0].indexOf(sourceData[0]) + 1; // +1 to adjust to spreadsheet rows
if (destRow > 0) {
// Found our match
destination.getRange(destRow,1,sourceData.length,sourceData[0].length)
.setValues(sourceData);
}
}
The second approach has fewer lines of code, but should be a bit slower than the first one because of the transpose() function which touches every element in column A before performing a search with .indexOf(). (The first approach searched in place, and exited once a match was found, so it actually does less work.)
In both examples, I've tried to limit the calls to google's services as much as possible. Alternatively, you could read info from the spreadsheets inside the search loop, which would be much slower, but would avoid the +1 / -1 mental gymnastics needed to keep 0-based arrays aligned with 1-based rows and columns.