I'm struggling on a child task within a function I'm working on.
For each sheet in sheets, I would like to get the data in range A16:D, combine into one big array and then out put the combined data into a new sheet. But each time I try to select each data range it comes as undefined?
How can I get the data from each sheet in sheets and then add into one big array?
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ranges = [];
function combineData() {
var activeSheetName = ss.getActiveSheet().getName();
var sheets = ss.getSheets();
sheets.forEach(function(e) {
var sheetName = e.getName();
if (sheetName != activeSheetName && sheetName != 'Report Configuration' && sheetName != 'Sheet1')
var wee_data = e.getRange('A16:D').getValues(); // TROUBLE HERE, WEE_DATA IS UNDEFINED
for(var j = 0; j<wee_data.length; j++) {
store.push(wee_data[j]);
}
ranges.push(wee_data);
});
if (ranges.length) {
Logger.log('range length is: ' + ranges.length);
adjustSheetLength(); // ensure there are enough rows in the destination sheet.
// figure out how to output data array "ranges" into a sheet "combined".
}
}
function adjustSheetLength(){
var comb = ss.getSheetByName('combined');
var lastRow = 4;
var maxRows = comb.getLastRow();
comb.deleteRows(lastRow, maxRows-lastRow);
var datapullSize = ranges.length;
comb.insertRows(4, datapullSize-2);// insert exactly the number of rows you need.
}
I think your issue is in the .forEach callback function. When I debugged those functions I got a not allowed in callback exception.
Change the foreach to a for in I think that'll fix your issue.
for( var i in sheets ) {
Logger.log(sheets[i]);
...
}
Related
Goodday,
I have 2 sheets. For each ID on sheet1 I need to verify if that ID is also on sheet2 AND if Date Processed is blank.
If both condition are true > today's date should be set in Date Processed.
I've managed to do so for just 1 value (A2) from sheet1.
What I need is a way to go through all the values in sheet1. Ideally the row in sheet1 would also get deleted (not sure if that is possible)
This is my code till now
function myMatch(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var ss = file.getSheetByName("Sheet1");
var ws = file.getSheetByName("Sheet2");
var wsData = ws.getDataRange().getValues();
var mySearch = ss.getRange("A2").getValue();
for(var i = 0; i < wsData.length; i++){
if(wsData[i][1] == mySearch && wsData[i][2] == "")
{
ws.getRange(i+1,3).setNumberFormat('dd"-"mmm"-"yyyy').setValue(new Date());
}
}
}
Your help is really appreciated as I have been trying and searching for a solution for 2 days now. Thank you
I know it doesn't makes much sense. Muhammet's code works and looks just fine. But, rather for fun and educational purposes, here is another "functional" solution:
function myFunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const s1 = ss.getSheetByName('Sheet1');
const s2 = ss.getSheetByName('Sheet2');
// get data from Sheet1 and Sheet2
const s1_data = s1.getDataRange().getValues().slice(1,);
const s2_data = s2.getDataRange().getValues().slice(1,);
// get IDs from data of Sheet1
const IDs = s1_data.map(x => x[0]);
const IDs_to_delete = []; // here will be IDs to delete
// function checks and fill a row and adds ID to array to delete
const write_date = (id,row) => {
if (row[1] == id && row[2] == '') {
IDs_to_delete.push(id);
row[2] = new Date();
}
}
// change rows within data of Sheet 2
IDs.forEach(id => s2_data.forEach(row => row = write_date(id,row)));
// clear and fill Sheet 2
s2.getDataRange().offset(1,0).clearContent();
s2.getRange(2,1,s2_data.length,s2_data[0].length).setValues(s2_data);
// remove rows from data of Sheet 1
const s1_data_new = s1_data.filter(row => !IDs_to_delete.includes(row[0]));
// clear and fill Sheet 1 with new data
s1.getDataRange().offset(1,0).clearContent();
s1.getRange(2,1,s1_data_new.length,s1_data_new[0].length).setValues(s1_data_new);
}
The only improvements over Muhamed's code is that this implementation removes processed rows from Sheet1. And, I believe, it will work faster for huge lists, because it doesn't use getRange() and setValue() on every found cell but fills all cells of the sheet at once with setValues() method.
You need a loop for this. Use
var mySearchs = ss.getRange('A2:A').getValues();
and loop through all values of this array.
function myMatch(){
var file = SpreadsheetApp.getActiveSpreadsheet();
var ss = file.getSheetByName("Sheet1");
var ws = file.getSheetByName("Sheet2");
var wsData = ws.getDataRange().getValues();
var mySearchs = ss.getRange('A2:A').getValues();
mySearchs.forEach((v) => {
for(var i = 0; i < wsData.length; i++){
if(wsData[i][1] == v && wsData[i][2] == "")
{
ws.getRange(i+1,3).setNumberFormat('dd"-"mmm"-"yyyy').setValue(new Date());
}
}
})
}
I am using the splice Script to run code over numerous tabs
function Slice_TABS() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheets = ss.getSheets().slice(7); // get 8th sheet onwards
sheets.forEach(ss=>{
Donor();
})}
and am trying to use the below code to bring filtered results up to 10 per SKU. When i name the sheet the code works fine but i need it to run on the ActiveSpreadsheet. I am getting the following error
" Type Error: sheet.getRange is not a function" (BOLD Below)
function GetNSKUSAG11() {
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheets();
*//Building the matching set.
**var matchingValues = sheet.getRange('AF3:AF').getValues().filter(value => value[0] !=
'').map(nonBlankValue => [nonBlankValue[0], 0]);***
//Filtering out the desired number of values
var values = sheet.getRange("LOCATIONS!$A$3:$B").getValues().filter(value =>
advancedFilter(value[0], matchingValues));
let cols = values[0].length;
let rows = values.length;
//Printing out the found rows starting from AI
sheet.getRange(2, 36, rows, cols).setValues(values);
}
function advancedFilter(value, toMatch) {
let matched = toMatch.filter(couple => couple[0] === value);
if (matched.length > 0) {
if (matched[0][1]<10) { //Here you can decide when to stop returning SKUs
matched[0][1] += 1;
return true;
}
}
return false;
}
I think I am close.
Answer to this question - thanks to Tanaike is
Change the code as per below
var sheet = ss.getSheets()
to
var sheet = ss.getActiveSheet()
I have searched many possible answers but cannot seem to find one that works. I have a Google Sheet with about 1600 rows that I need to split into about 70 different tabs (with about 20-30 rows in each one) based on the value in the column titled “room”. I have been sorting and then cutting and pasting but for 70+ tabs this is very tedious.
I can use the Query function but I still need to create a new tab, paste the function and update the parameter for that particular tab.
This script seemed pretty close:
ss = SpreadsheetApp.getActiveSpreadsheet();
itemName = 0;
itemDescription = 1;
image = 2;
purchasedBy = 3;
cost = 4;
room = 5;
isSharing = 6;
masterSheetName = "Master";
function onOpen() {
var ui = SpreadsheetApp.getUi();
ui.createMenu('Update Purchases')
.addItem('Add All Rows To Sheets', 'addAllRowsToSheets')
.addItem('Add Current Row To Sheet', 'addRowToNewSheet')
.addToUi();
}
function addRowToNewSheet() {
var s = ss.getActiveSheet();
var cell = s.getActiveCell();
var rowId = cell.getRow();
var range = s.getRange(rowId, 1, 1, s.getLastColumn());
var values = range.getValues()[0];
var roomName = values[room];
appendDataToSheet(s, rowId, values, roomName);
}
function addAllRowsToSheets(){
var s = ss.getActiveSheet();
var dataValues = s.getRange(2, 1, s.getLastRow()-1, s.getLastColumn()).getValues();
for(var i = 0; i < dataValues.length; i++){
var values = dataValues[i];
var rowId = 2 + i;
var roomName = values[room];
try{
appendDataToSheet(s, rowId, values, roomName);
}catch(err){};
}
}
function appendDataToSheet(s, rowId, data, roomName){
if(s.getName() != masterSheetName){
throw new Error("Can only add rows from 'Master' sheet - make sure sheet name is 'Master'");
}
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
var roomSheet;
if(sheetNames.indexOf(roomName) > -1){
roomSheet = ss.getSheetByName(roomName);
var rowIdValues = roomSheet.getRange(2, 1, roomSheet.getLastRow()-1, 1).getValues();
for(var i = 0; i < rowIdValues.length; i++){
if(rowIdValues[i] == rowId){
throw new Error( data[itemName] + " from row " + rowId + " already exists in sheet " + roomName + ".");
return;
}
}
}else{
roomSheet = ss.insertSheet(roomName);
var numCols = s.getLastColumn();
roomSheet.getRange(1, 1).setValue("Row Id");
s.getRange(1, 1, 1, numCols).copyValuesToRange(roomSheet, 2, numCols+1, 1, 1);
}
var rowIdArray = [rowId];
var updatedArray = rowIdArray.concat(data);
roomSheet.appendRow(updatedArray);
}
But I always get an unexpected token error on line 51 or 52:
var sheetNames = [sheet.getName() for each(sheet in ss.getSheets())];
(And obviously the column names, etc. are not necessarily correct for my data, I tried changing them to match what I needed. Not sure if that was part of the issue.)
Here is a sample of my data: https://docs.google.com/spreadsheets/d/1kpD88_wEA5YFh5DMMkubsTnFHeNxRQL-njd9Mv-C_lc/edit?usp=sharing
This should return two separate tabs/sheets based on room .
I am obviously not a programmer and do not know Visual Basic or Java or anything. I just know how to google and copy things....amazingly I often get it to work.
Let me know what else you need if you can help.
Try the below code:
'splitSheetIntoTabs' will split your master sheet in to separate sheets of 30 rows each. It will copy only the content not the background colors etc.
'deleteTabsOtherThanMaster' will revert the change done by 'splitSheetIntoTabs'. This function will help to revert the changes done by splitSheetIntoTabs.
function splitSheetIntoTabs() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var header = rows[0];
var contents = rows.slice(1);
var totalRowsPerSheet = 30; // This value will change no of rows per sheet
//below we are chunking the toltal row we have into 30 rows each
var contentRowsPerSheet = contents.map( function(e,i){
return i%totalRowsPerSheet===0 ? contents.slice(i,i+totalRowsPerSheet) : null;
}).filter(function(e){ return e; });
contentRowsPerSheet.forEach(function(e){
//crate new sheet here
var currSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet();
//append the header
currSheet.appendRow(header);
//populate the rows
e.forEach(function(val){
currSheet.appendRow(val);
});
});
}
// use this function revert the sheets create by splitSheetIntoTabs()
function deleteTabsOtherThanMaster() {
var sheetNotToDelete ='Master';
var ss = SpreadsheetApp.getActive();
ss.getSheets().forEach(function(sheet){
if(sheet.getSheetName()!== sheetNotToDelete)
{
ss.deleteSheet(sheet);
}
});
}
I was using Kessy's nice script, but started having trouble when the data became very large, where the script timed out. I started looking for ways to reduce the amount of times the script read/wrote to the spreadsheet (rather than read/write one row at a time) and found this post https://stackoverflow.com/a/42633934
Using this principle and changing the loop in the script to have a loop within the loop helped reduce these calls. This means you can also avoid the second call to append rows (the "else"). My script is a little different to the examples, but basically ends something like:
`for (var i = 1; i < theEmails.length; i++) {
//Ignore blank Emails and sheets created
if (theEmails[i][0] !== "" && !completedSheets.includes(theEmails[i][0])) {
//Set the Sheet name = email address. Index the sheets so they appear last.
var currentSheet = theWorkbook.insertSheet(theEmails[i][0],4+i);
//append the header
//To avoid pasting formulas, we have to paste contents
headerFormat.copyTo(currentSheet.getRange(1,1),{contentsOnly:true});
//Now here find all the rows containing the same email address and append them
var theNewRows =[];
var b=0;
for(var j = 1; j < rows.length; j++)
{
if(rows[j][0] == theEmails[i][0]) {
theNewRows[b]=[];//Initial new array
theNewRows[b].push(rows[j][0],rows[j][1],rows[j][2],rows[j][3],rows[j][4],rows[j][5],rows[j][6],rows[j][7]);
b++;
}
}var outrng = currentSheet.getRange(2,1,theNewRows.length,8); //Make the output range the same size as the output array
outrng.setValues(theNewRows);
I found a table of ~1000 rows timed out, but with the new script took 6.5 secs. It might not be very neat, as I only dabble in script, but perhaps it helps.
I have done this script that successfully gets each room and creates a new sheet with the corresponding room name and adding all the rows with the same room.
function myFunction() {
var sheet = SpreadsheetApp.getActiveSheet();
// This var will contain all the values from column C -> Room
var columnRoom = sheet.getRange("C:C").getValues();
// This var will contain all the rows
var rows = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
//Set the first row as the header
var header = rows[0];
//Store the rooms already created
var completedRooms = []
//The last created room
var last = columnRoom[1][0]
for (var i = 1; i < columnRoom.length; i++) {
//Check if the room is already done, if not go in and create the sheet
if(!completedRooms.includes(columnRoom[i][0])) {
//Set the Sheet name = room (except if there is no name, then = No Room)
if (columnRoom[i][0] === "") {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("No Room");
} else {
var currentSheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet(columnRoom[i][0]);
}
//append the header
currentSheet.appendRow(header);
currentSheet.appendRow(rows[i]);
completedRooms.push(columnRoom[i][0])
last = columnRoom[i][0]
} else if (last == columnRoom[i][0]) {
// If the room's sheet is created append the row to the sheet
var currentSheet = SpreadsheetApp.getActiveSpreadsheet()
currentSheet.appendRow(rows[i]);
}
}
}
Please test it and don't hesitate to comment for improvements.
I'm very new to coding/Google Appscript so forgive me. I am trying to copy data between spreadsheets which matches certain criteria. My problem is, if the array returns no rows which match I get an error 'TypeError: Cannot read property "length" from undefined'. Is there a way to skip copying the data if the array is null?
function myFunction() {
var sourcess = SpreadsheetApp.openById();
var sourcedata = sourcess.getSheetByName('debt data'); //source sheet
var testrange = sourcedata.getRange('Q:Q'); //range to check
var testvalue = (testrange.getValues());
var testrange2 = sourcedata.getRange('R:R');
var testvalue2 = testrange2.getValues();
var targetss = SpreadsheetApp.openById();
var targetdata = targetss.getSheetByName('Debtors'); //destination sheet
var data = [];
var j =[];
var lastRow = targetdata.getLastRow();
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] != '123' && testvalue2[i] == 'abc') {
data.push.apply(data,sourcedata.getRange(i+1,1,1,20).getValues());
j.push(i);
}
}
targetdata.getRange(lastRow+1,1,data.length,data[0].length).setValues(data);
}
Ranges Like this: sourcedata.getRange('Q:Q'); often result in a lot of null items because they collect all of the cells down to Max Rows.
It would be better to use sourcedata.getRange(1,17,sourcedata.getLastRow(),1);
This: if ( testvalue[i] != '123' && testvalue2[i] == 'abc')
needs to be like this: if ( testvalue[i][0] != '123' && testvalue2[i][0] == 'abc')
Even single column arrays require two indices.
I would write this:
data.push.apply(data,sourcedata.getRange(i+1,1,1,20).getValues());
like this:
data.push.(sourcedata.getRange(i+1,1,1,20).getValues()[0]);
Actually, I wouldn't have done it that way at all. I would do something like this:
var vA=sourcedata.getRange(1,1,sourcedata.getLastRow(),20).getValues();
and then data.push(vA[i]);//and that gets the whole row of 20 columns as a flattened array
Here's an example of something similar to what you are doing I think.
My Source Sheet Looks Like this:
My Destination Sheet Looks Like this:
And this is my code:
function copyDataIfAB() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet187');
var rg=sh.getDataRange();
var vA=rg.getValues();
var hA=vA[0];
var hObj={};
var sA=[];
hA.forEach(function(e,i){if(e){hObj[e]=i}});
vA.forEach(function(r,i){
if(vA[i][hObj['HDR2']]=='A' && vA[i][hObj['HDR10']]=='B') {
sA.push(vA[i]);
}
})
var tss=SpreadsheetApp.openById('Id');
var tsh=tss.getSheetByName('Sheet9');
tsh.getRange(tsh.getLastRow()+1,1,sA.length,sA[0].length).setValues(sA);//appending it to the next empty row on the sheet
}
I am trying to copy data from one sheet, paste it into another and then delete the row that have a "flag" attached to them (Column D). When I execute this code, it just deletes the flag and not the whole row. What is going on here? The functions seem to execute in the wrong order.
Tried using Utilities.sleep(), as well as
separating both into different functions
using a for loop writing out all the columns I want deleted
applying flush to the functions before executing the next
//copies the code
function myFunction() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url)
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange("K:N")
var values = range.getValues()
var target = ss.getSheetByName("worklist")
target.getRange("A:D").setValues(values)
SpreadsheetApp.flush()
Utilities.sleep(5 * 1000)
}
//deletes the rows with flags in them
function myFunction2() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url)
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange("K:N")
var values = range.getValues()
var target = ss.getSheetByName("worklist")
Utilities.sleep(6 * 1000)
var oldnews = target.getRange("D:D")
for (i = oldnews.length; i > 0; i--){
if (oldnews[i] !== undefined) {
target.getRange(i,1)
target.getRange(i,2)
target.getRange(i,3)
target.getRange(i,4)
}
}
SpreadsheetApp.flush()
}
It's not really clear what your asking. So I just guessed at some of this.
function myFunction() {
var ss = SpreadsheetApp.openById("id");
var sheet = ss.getSheetByName('Bad Emails Input')
var range = sheet.getRange(1,11,sheet.getLastRow(),4);
var values = range.getValues()
var target = ss.getSheetByName("worklist")
target.getRange(1,1,values.length,values[0].length).setValues(values);
SpreadsheetApp.flush();
var d=0;
for(var i=0;i<values.length;i++) {
if(values[i][3]) {
target.deleteRow(i+1-d++);
}
}
}
Try the below code:
function myFunction() {
var url = 'https://docs.google.com/spreadsheets/d/1ie2Fyj2piVV8XC0XUGX-fTAHcQ0UUUdec4Mtr1QdXtQ/edit#gid=293227072'
var ss = SpreadsheetApp.openByUrl(url);
var sheet = ss.getSheetByName('Bad Emails Input');
var range = sheet.getRange("K:N");
var values = range.getValues();
var target = ss.getSheetByName("worklist");
target.getRange("A:D").setValues(values);
SpreadsheetApp.flush();
//If you have a header row change the below to A2:D and count to 2
var data=ss.getSheetByName("worklist").getRange("A:D").getValues(),count=1;
for (i in data) {
var rowdata=data[i];
if (!rowdata[0] || rowdata[0]==''){break;}
if (rowdata[3] == 'flag') {//change the flag to however it is in column D
ss.getSheetByName("worklist").deleteRow(count);
count++;
}
}
}