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
}
Related
So I have a table that will have data to the last row except column B. (for example, cols A, C, D etc. stop at row 40, but B stops at maybe row 25). I want to programmatically keep the series going starting at the last cell with data in B and autofill down to the last row in the spreadsheet. (Hopefully a script doing this will recognize the series and not just copy the same data to all the empty cells. When I do it manually it works.) I have something started here but I can't figure out how to call out the range of where to start the series. I get an error on line 7 "Exception: Range not found".
function fillDownFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var sc = ss.getRange("B1:B").getValues();
var scr = sc.filter(String).length;
var lr = ss.getLastRow();
var fillDownRange = ss.getRange(scr,2,lr)
ss.getRange(scr).copyTo(fillDownRange);
}
In addition to the answers already provided, Apps Script already has an autoFill() method that you can use to do this. With this you just have to define a source and a destination range. This works the same as selecting the range and dragging down your mouse manually on the corner.
function fillDownFunction() {
var ss= SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var lastsheetrow = ss.getLastRow() //last row with data
//last row in the autofill range with data
var lastrangerow = ss.getRange("B1:B").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow()
var sourcerange = ss.getRange(2, 2,lastrangerow-1) //in your screenshot, this would be B2:B6
var destinationrange = ss.getRange(2, 2, lastsheetrow-1) //This would be B2:B12
sourcerange.autoFill(destinationrange, SpreadsheetApp.AutoFillSeries.DEFAULT_SERIES)
}
Note that I skipped the header row and offset the range lengths by -1 to compensate. This is because the autofill logic uses the entire range, so it would also take into account "Item2" and repeat it every 6 rows as "Item3", "Item4" and so on. A disadvantage in this case, but may prove useful if you plan to use autofill in more complex ways in the future.
Here is one way you could have the last fill value copy down. The function grabs all of the values, maps the row that needs to fill down and then copies that last value down the rest of the sheet.
function fillDownFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var tab = ss.getActiveSheet();
var sc = tab.getDataRange().getValues();
var lastRow = sc.length;
var values = sc.map(function(r) { return r[1]; }).filter(i => i);
var lastFillRow = values.length;
var fillDownValue = values.slice(-1);
tab.getRange(lastFillRow + 1, 2, lastRow - lastFillRow).setValue([fillDownValue]);
}
I'm using Google Sheets and I have this code below to copy the last row (via Form On Submit trigger) onto another sheet (not tab). I'm not sure how to copy over the last row and skip the data in Column C. It's currently copying the entire row to another sheet successfully, fyi. Any help would be appreciated, thank you!
function CopyToAnotherSheet() {
// Delaying this script so that there is enough time to get the Google Doc URL before it starts to fill out the template file
Utilities.sleep(10000); // 10 second delay
// Get Source Spreadsheet
var source = SpreadsheetApp.getActiveSpreadsheet();
// Get Source Sheet from Spreadsheet
var source_sheet = source.getActiveSheet();
// Get Last Row
var lastRow = source_sheet.getLastRow();
// Get Last Column
var lastColumn = source_sheet.getLastColumn();
// Get Last Row of Data
var lastRowOfData = source_sheet.getRange(lastRow, 1, 1, lastColumn).getValues();
// Creates a one dimensional array
var oneD_array = lastRowOfData.join().split(",");
// Get the Value of the Manufacturer Cell
var cellValue = source_sheet.getRange(lastRow,3).getValues();
// Copy Last Row to First Sheet
if ( cellValue == "First" ) {
var target = SpreadsheetApp.openById("xxxyyyzzz");
var target_sheet = target.getSheetByName("First");
target_sheet.appendRow(oneD_array);
}
// Copy Last Row to Second Sheet
if ( cellValue == "Second" ) {
var target = SpreadsheetApp.openById("aabbcc");
var target_sheet = target.getSheetByName("Second");
target_sheet.appendRow(oneD_array);
}
}
One option you can do is to remove column C value in your 1-d array. Instead of skipping it.
Sample code:
var lastRowOfData = source_sheet.getRange(lastRow, 1, 1, lastColumn).getValues().flat();
Logger.log("Before removal: "+lastRowOfData)
// Remove index 2 (Column C value) in the array
lastRowOfData.splice(2,1)
Logger.log("After removal: "+lastRowOfData)
What it does?
Using array.flat(), change 2-d array to 1-d array
Using array.splice(), remove an element in the array
Output:
Execution log
5:10:50 AM Notice Execution started
5:10:51 AM Info Before removal: a,b,c,d,e
5:10:51 AM Info After removal: a,b,d,e
5:10:52 AM Notice Execution completed
Note:
I removed the oneD_array variable in the sample code. If you really want a separate variable for your 1-d array you can use this one:
var oneD_array = lastRowOfData.flat();
oneD_array.splice(2,1);
I am trying to take the Data in the scan it columns and conditionally filter it into the commercial or manufacturing sheet.
I tried writing a formula as an if function basically saying if it equals "Company" put it in manufacturing column, if not(if the rest) then put into commercial column.
I tried coding it backend in app script, but had an issue grabbing the last row from scan it and correctly adding the item #, vendor, and quantity into the last row of either Commercial or Manufactured.
The scan it section is an importrange function so more data will be entered into it once the code is working correctly.
I ran into dead ends with both. This is obviously an advanced code and I only did a little bit of coding in college. Below is a script that I had been messing with trying to get it to filter the data properly, but I couldn't get it to run. Any help would be appreciated!
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1')
//Get last rows for MFG
var mfgvalues = sheet.getRange(3, 5, sheet.getLastRow()).getValues();
//var mfglast = mfgvalues.filter(String).length;
//Get last rows for Comm
var commvalues = sheet.getRange(3, 1, sheet.getLastRow()).getValues();
//var commlast = commvalues.filter(String).length;
//Get last rows for Scann
var scanvalues = sheet.getRange(2, 12, sheet.getLastRow()).getValues();
//var scanlast = scanvalues.filter(String).length;
//Filter Accordingly
if (scanvalues == "IMMCO") {
var scanrange = sheet.getRange("L2:N").getLastRow().getValues();
//var scanlastrow = scanrange.getLastRow().getValues();
var mfgrow = sheet.getRange("E3:G").getLastRow();
scanlastrow.copyTo(mfgrow)
} else {
var scanrange = sheet.getRange("L:N").getLastRow.getValues();
//var scanlastrow = scanrange.getLastRow().getValues();
var commrow = sheets.getRange("A3:C").getLastrow()
scanlastrow.copyTo(commrow)
}
}
https://docs.google.com/spreadsheets/d/1D0FvVoTADi3t3ENPceB14QYYI4lNI6C3xj2OF-St7Is/edit?usp=sharing
GOOGLE SHEET OUTPUT WANTED
Option1:
Using FILTER() to filter your data set if vendor name is equal to "COMPANY" as mentioned in your description
Formula in Cell A3:
=filter(L3:N,LEN(L3:L)>0,UPPER(M3:M)<>"COMPANY")
Formula in Cell E3:
=filter(L3:N,LEN(L3:L)>0,UPPER(M3:M)="COMPANY")
Output:
Option2:
Using apps script to filter your data set.
Sample Code:
function filter(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1');
//Get item count for Scann
var scanCnt = sheet.getRange('L3:L').getValues().flat().filter(String).length;
Logger.log(scanCnt);
//Get last rows for Comm
var commLastRow = sheet.getRange('A1:A').getValues().flat().filter(String).length;
Logger.log(commLastRow);
//Get last rows for MFG
var mfgLastRow = sheet.getRange('E1:E').getValues().flat().filter(String).length;
Logger.log(mfgLastRow);
//Get Scann data if item count is not zero
if(scanCnt){
var commItem = [];
var mfgItem = [];
var dataRange = sheet.getRange(3,12,scanCnt,3);
var data = dataRange.getDisplayValues();
//Loop all scann data and separate mfg with comm
Logger.log(data.length)
data.forEach(item => {
Logger.log(item);
if(item[1].toUpperCase() == "COMPANY"){
mfgItem.push(item);
}else{
commItem.push(item);
}
});
Logger.log(mfgItem);
Logger.log(commItem);
//Append comm items
sheet.getRange(commLastRow+1,1,commItem.length,commItem[0].length).setValues(commItem);
//Append mfg items
sheet.getRange(mfgLastRow+1,5,mfgItem.length,mfgItem[0].length).setValues(mfgItem);
//Optional: Clear content of scann column
dataRange.clearContent();
}
}
What it does?
Get the item count to filter in Scan it column by selecting the range L3:L. Get its value. Change 2-d array to 1-d array using array.flat(). Use array.filter() to remove empty values. Then get its length
Get the last row of Commercial(ColumnA) and Manufactured(ColumnE) column.
If item count obtained in step1 is not zero. Then get the data range and its value
Loop all data value (by row) one-by-one. Check if vendor index is equal to "COMPANY" then add it to mfgItem array. Else, add it to commItem array
Append mfgItem and commItem under Manufactured Column and Commercial Column
(Optional) Clear the data under Scan-it Column
Note:
You can check the execution log to further understand the procedure done. I included some debug logs in the sample code.
Output:
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.
Let's say I have a lot of columns and one of them contains "impressions" string (on row 3). What I need to do is to:
1) Find the cell with "impressions" string
2) Get column number or i.e. "D"
3) Based on what I got paste a formula into i.e. D2 cell which gets AVERAGE from a range D4:D*last*
I couldn't find it anywhere so I have to ask here without any "sample" code, since I have no idea on how to achieve what I want. (3rd one is easy but I need to get that "D" first)
There's no way to search in Google Apps Script. Below is a function that will accomplish the first 2 parts for you (by iterating over every cell in row 3 and looking for "impressions"):
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Sheet1'); // insert name of sheet here
var range = sheet.getDataRange(); // get the range representing the whole sheet
var width = range.getWidth();
// search every cell in row 3 from A3 to the last column
for (var i = 1; i <= width; i++) {
var data = range.getCell(3,i)
if (data == "impressions") {
return(i); // return the column number if we find it
}
}
return(-1); // return -1 if it doesn't exist
}
Hopefully this will allow you to accomplish what you need to do!
The indexOf method allows one to search for strings:
function findColumnNumber() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet() //whatever tab the code is run on
var data = sheet.getDataRange().getValues();
var header_row_num = 1; // TODO: change this to whichever row has the headers.
var header = data[header_row_num -1] //Remember JavaScript, like most programming languages starts counting (is indexed) at 0. For the value of header_row_num to work with a zero-index counting language like JavaScript, you need to subtract 1
//define the string you want to search for
var searchString = "impressions";
//find that string in the header and add 1 (since indexes start at zero)
var colNum = header.indexOf(searchString) + 1;
return(colNum);