How to copy a specific row from one sheet to another (Google Apps Script) - google-apps-script

this is my first post, so forgive me if the question is not worded quite the way it is for the platform.
I'm currently working on a Google Apps script that is supposed to search a sheet (name: "[Overview] All Cases") and its rows for a certain value in column Y. This particular value is "No". If a row has this value, this row should be copied to the last row of another sheet ("OPS_FUNNEL").The row should then exist in both sheets. Can anyone help?
I have been through countless threads and have not been able to gather a solution. My solution so far, which does not work, you can see here:
function copy_to_OPS_FUNNEL() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcSheet = ss.getSheetByName("[Overview] All_Cases");
var tarSheet = ss.getSheetByName("OPS_FUNNEL_new");
var lastRow = srcSheet.getLastRow();
for (var i = 2; i <= lastRow; i++) {
var cell = srcSheet.getRange("Y" + i);
var val = cell.getValue();
if (val == 'No') {
var srcRange = srcSheet.getRange("A" + i + ":B" + i);
var sourcevalues = srcRange.getValues();
var tarRow = tarSheet.getLastRow();
var tarRange = tarSheet.getRange("A" + (tarRow + i) + ":B" + (tarRow + i));
tarRange.setValues(sourcevalues);
srcSheet.getRange("Y"+(i+0).toString()).setValue("Yes");
}
else{
}
}
}

Explanation:
You have two goals:
Copy the rows in sheet [Overview] All_Cases that contain "No" in column Y to the OPS_FUNNEL_new sheet.
After the copy is done, change the "No" in sheet [Overview] All_Cases to "Yes".
Three major improvements:
It is not recommended to iteratively call getRange and setRange, read best practices for more info.
You don't need a for loop and if statements to find the data that contains "No" in column Y. Instead you filter this data with one line of code:
const data = srcSheet.getDataRange().getValues().filter(r=>r[24]=='No');
Then, copy it to the target sheet with one line of code:
tarSheet.getRange(tarSheet.getLastRow()+1,1,data.length,data[0].length).setValues(data);
Solution:
function copy_to_OPS_FUNNEL() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const srcSheet = ss.getSheetByName("[Overview] All_Cases");
const tarSheet = ss.getSheetByName("OPS_FUNNEL_new");
const data = srcSheet.getDataRange().getValues().filter(r=>r[24]=='No').map(r => [r[0]]);
if (data.length>0){
tarSheet.getRange(tarSheet.getLastRow()+1,1,data.length,1).setValues(data);
srcSheet.getRange("Y2:Y"+srcSheet.getLastRow()).setValue("Yes");
}
}

Related

Google Sheets Script to import data based on cell value and not duplicate information

I need to pull/import data from "sheet 1" to "sheet 2" based on column 4 being a specific text string. The script should not pull lines that already exist.
I have no idea if this is possible. I can pull the data but it just recopies everything so I have duplicates.
Any help would be super appreciated.
function onEdit() {
var ss = SpreadsheetApp.openById('1Ognzsi6C0DU_ZyDLuct58f5U16sshhBpBoQ8Snk8bhc');
var sheet = ss.getSheetByName('Sheet 1');
var testrange = sheet.getRange('D:D');
var testvalue = (testrange.getValues());
var sss = SpreadsheetApp.getActive();
var csh = sss.getSheetByName('Sheet 1');
var data = [];
var j =[];
for (i=0; i<testvalue.length;i++) {
if ( testvalue[i] == 'Dan') {
data.push.apply(data,sheet.getRange(i+1,1,1,11).getValues());
j.push(i);
}
}
csh.getRange(csh.getLastRow()+1,1,data.length,data[0].length).setValues(data);
}
Sheet 1
Sheet 2
Solution
You should be able to replace your code with this and it will work. You would put this script in the target sheet (Sheet 2), and replace the ID in the first line of the function with the origin (Sheet 1).
I'll leave it up to you to change to an onEdit or to make it a menu item. Right now it can be run from the script editor. onEdit doesn't make sense to me as an appropriate trigger. Maybe you prefer a Time-Driven Trigger. Though a custom menu would be the best way IMO.
function pullData() {
var sourceSs = SpreadsheetApp.openById('[YOUR_SPREADSHEET_ID]');
var sourceRange = sourceSs.getSheetByName('Sheet1').getDataRange();
var sourceHeight = sourceRange.getHeight();
var sourceWidth = sourceRange.getWidth();
var sourceData = sourceSs.getSheetByName('Sheet1').getRange(2, 1, sourceHeight - 1, sourceWidth).getValues();
var targetSs = SpreadsheetApp.getActive();
var targetRange = targetSs.getSheetByName('Sheet1').getDataRange();
var targetHeight = targetRange.getHeight();
var targetWidth = targetRange.getWidth();
var sourceDataChecker = [];
var targetDataChecker = [];
sourceData.forEach((row) => {
sourceDataChecker.push(row[0] + row[1] + row[2] + row[3]);
})
if (targetHeight != 1) {
var targetData = sourceSs.getSheetByName('Sheet1').getRange(2, 1, targetHeight - 1, targetWidth).getValues();
targetData.forEach((row) => {
targetDataChecker.push(row[0] + row[1] + row[2] + row[3]);
});
};
sourceData.forEach((row, i) => {
if (!(targetDataChecker.includes(sourceDataChecker[i]))) {
targetSs.appendRow(row);
};
});
}
Explanation
This script builds an "index" of each row in both sheets by concatenating all the values in the row. I did this because I noticed that sometimes you have "joe" in two rows, and so, you can't simply use column 4 as your index. You are basically checking for any row that is different from one in the target sheet (Sheet 2).
If the target sheet is blank, then all rows are copied.
References
Append Row to end of sheet
Get Data Range (range of sheet that contains data)
Get Range Height (to deal with headers)
Get Range Width
for Each

I need to split a Google Sheet into multiple tabs (sheets) based on column value

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.

Google scripts - issue with string vs cell as function parameter

I'm learning Google script as I go along and came across an issue that I could not find a solution for.
I have a function that takes in several several strings and one cell as params.
I assume passing a cell is not same as passing its value (string too btw)
Please find attached code:
//return id assigned to data(cell) sheetname
function findInColumn(column, data, colName, sheetname) {
/*
data = cell (cell value is 'bag_backpack.png') does not work
var range = SpreadsheetApp.getActiveRange();
var col = range.getColumn();
var row = range.getRow();
var range2 = SpreadsheetApp.getActiveSheet().getRange(row,col+1);
data = range2.getValue(); (cell value is 'bag_backpack.png') <---fail too
data = 'bag_backpack.png'; <---works
*/
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetname);
var column = sheet.getRange(column + ":" + column); // like A:A
var values = [];
for (var index = 0; index < column.getValues().length; index++) {
values.push(column.getValues()[index][0])
}
var colIndex = getByName(colName, sheetname);
var rowIndex = values.indexOf(data);
//grab id value of data
var range = sheet.getRange(rowIndex+1,colIndex+1);
var data = range.getValue();
return data
}
While I value and need your feedback I kindly request your bear in mind I'm a novice at best and I've spend several hours searching before asking here.
Thank you for your help.
Edit: bag_backpack.png is the content of a cell I'm searching in a column to get the row number of said cell.
var rowIndex = values.indexOf(data);
Returns the index I need.
However... if I feed a cell to the function instead of a string like a cell elsewhere with bag_backpack.png as its content or even derive said value from it:
var range = SpreadsheetApp.getActiveRange();
var col = range.getColumn();
var row = range.getRow();
var range2 = SpreadsheetApp.getActiveSheet().getRange(row,col+1);
data = range2.getValue(); (cell value is 'bag_backpack.png') <---fail too
In said case:
var rowIndex = values.indexOf(data);
gives out -1 instead of index desired.
Most of what you have doesn't make sense to me. I'm guessing that this is what you want. Since I can't figure out what you want, perhaps you can tell me what I missed.
function findInColumn(column,sheetname) {
var sh=SpreadsheetApp.getActive().getSheetByName(sheetname);
var rg=sh.getRange(column + ":" + column); /
var vA=rg.getValues();
var values = [];
for (var i=0;i<vA.length; i++) {
values.push(vA[i][0]);
}
return values
}
I want to thank everyone for taking the time to reply.
the issue was NOT with the code but with some of the first filename having uppercase in the id,name sheet while the binder sheet it did not.(at least 5 hours on Tent.png/tent.png)
I'm attaching a link to my finished work in hopes it benefits anyone.
https://docs.google.com/spreadsheets/d/1jCy1I4r6PeY3kUWrhNno2XO18Jghq6_b1kyN3AflidM/edit?usp=sharing

Google Script insert formula in 1 column where cell are black

Hi could someone help me with this script im trying to insert a formula in all the blank cell from (J15) to (J38) here is what ive got but I just cant seem to get it the proper way any help would be greatly appreciated.
function test() {
var ssA = SpreadsheetApp.getActive(); //changed from openById() for my convenience
var ss = ssA.getActiveSheet(); //change from getSheetByName() for my convenience
var range = ss.getRange(15,10,24,1); //row 2 column 7 (G) lastRow 1 column
var data = range.getValues(); //Gets all data
for(var i=0;i<data.length;i++) //this runs over entire selected range
{
if(!data[i][0]) //If true then it's blank
{
data[i](('=Iferror(If(G15="",, if($B$5 = Iferror(query(\'Client
List\'!$A$2:$A, "select A where A =\'"&$B$5&"\'"),""),VLOOKUP($B$5,Client_Rate,2,False),VLOOKUP(D15,Config_Rate_List
,2,False))),"")');)
}
}
range.setFormula(data); //Sets all data.
}
You want to put the formula to "J15:J38". If my understanding is correct, how about this modification?
I thought that it is possible that the cells of "J15:J38" have the values and formulas. So in this modified script, the formula is put to the empty cells which don't have both. The flow of script is as follows.
Retrieve values and formulas from "J15:J38".
Create range list.
Put the formula using setFormula().
I think that there are several solutions for your situation. So please think of this as one of them.
Modified script :
function test() {
var ssA = SpreadsheetApp.getActive(); //changed from openById() for my convenience
var ss = ssA.getActiveSheet(); //change from getSheetByName() for my convenience
var range = ss.getRange(15,10,24,1); //row 2 column 7 (G) lastRow 1 column
var data = range.getValues(); //Gets all data
// The following script was modified.
var formulas = range.getFormulas();
var rangeList = [];
var offset = 15;
for(var i=0;i<data.length;i++) {
if(!data[i][0] && !formulas[i][0]) {
rangeList.push("J" + (offset + i));
}
}
var formula = '=Iferror(If(G15="",, if($B$5 = Iferror(query(\'Client List\'!$A$2:$A, "select A where A =\'"&$B$5&"\'"),""),VLOOKUP($B$5,Client_Rate,2,False),VLOOKUP(D15,Config_Rate_List,2,False))),"")';
ss.getRangeList(rangeList).setFormula(formula);
}
Note :
I was not sure whether the sheet name is Client List or ClientList, because of the line break in your script.
I'm not sure about the formula which was put to cells.
About your title, "black" of "Google Script insert formula in 1 column where cell are black" is "blank"?
Reference :
getRangeList(a1Notations)
If this was not what you want, I'm sorry.
Edit :
Unfortunately, the formulas in an array cannot be put the cells using the range list yet. I think that Sheets API can put various formulas once. But as a simple way, here, I would like to propose the following script.
function test() {
var ssA = SpreadsheetApp.getActive(); //changed from openById() for my convenience
var ss = ssA.getActiveSheet(); //change from getSheetByName() for my convenience
var range = ss.getRange(15,10,24,1); //row 2 column 7 (G) lastRow 1 column
var data = range.getValues(); //Gets all data
// The following script was modified.
var formulas = range.getFormulas();
var val = [];
var offset = 15;
for(var i=0;i<data.length;i++) {
if(!data[i][0] && !formulas[i][0]) {
ss.getRange("J" + (offset + i)).setFormula('=Iferror(If(G' + (offset + i) + '="",, if($B$5 = Iferror(query(\'Client List\'!$A$2:$A, "select A where A =\'"&$B$5&"\'"),""),VLOOKUP($B$5,Client_Rate,2,False),VLOOKUP(D' + (offset + i) + ',Config_Rate_List,2,False))),"")');
}
}
}

How to add a button per row in google spreadsheet?

I have a simple spreadsheet with multiple rows I want to add button to each of it automatically.
On click this button will remove the entire row and move it to another spreadsheet.
I've looked online and couldn't find a way to add buttons to spreadsheets. Some people suggest to insert drawings and assign macros, but looks like the latest version lacks this feature.
I've also read about google app sript. I was able to add buttons to a menu, but not directly to each row of a spreadsheet.
Do you have any advice or suggestion on how to better achieve this?
I suggest the you create a column(say 10) at the end of each row with a List Data Validation of "Move Row" and create an onEdit function like so
function onEdit(eventInfo) {
if (eventInfo.range.getColumn() == 10 && eventInfo.value == "Move Row")
{
var ss = eventInfo.source;
var row = eventInfo.range.getRow();
var range = ss.getRange(row, 1, 10) // this is your range, do with it what you will
}
}
So this script is not entirely my own work, but the results of something I was working on with help.
It will look in a source sheet, look at a column you specify (by number), and look for a value in this sheet.
If matched, it will move that row to another sheet, within the spreadshet, of your choosing.
function moveRowInternalSheet()
{
var sourceSheet = "";
var columnNumberToWatch = ;
var valueToFind = "";
var destinationSheet = "";
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sourceSheet);
var values = sheet.getDataRange().getValues();
var counter = 1; //starts looking in second row, due to header
var archive = [];
while (counter < values.length)
{
if (values[counter][columnNumberToWatch - 1] == valueToFind)
{
archive.push(values.splice(counter, 1)[0]);
}
else
{
// If the value to find is not found. Then increment the counter by 1
counter++;
}
}
var archiveLength = archive.length;
if (!archiveLength) return;
var targetSheet = ss.getSheetByName(destinationSheet);
var lastRow = targetSheet.getLastRow();
var requiredRows = lastRow + archiveLength - targetSheet.getMaxRows();
if (requiredRows > 0) targetSheet.insertRowsAfter(lastRow, requiredRows);
targetSheet.getRange(lastRow + 1, 1, archiveLength, archive[0].length).setValues(archive);
sheet.clearContents();
sheet.getRange(1, 1, values.length, values[0].length).setValues(values);
}