Sheet Script to Dedupe Rows: Unexpected Behavior - google-apps-script

I have a script which posts data from one sheet to another and then de-dupes the data. The dedupe script appears to only dedupe SOME of the data. For instance, rows 2-13 remain duplicates while rows after row 35 are deduped. Rows at the top are posted from the insert script.
How can I get the script to:
a) Dedupe the full range of data.
b) Remove the new duplicate rows posted from the insert script rather than rows that were already there.
I've tried setting the full range of the sheet to be checked, but this seems to interfere with the post to the new data. This is the simplest script I've managed to find/develop.
function Run(){
insert();
dedupe();
}
function insert() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = ss.getSheetByName('Candidate Refresh'); // change here
var des = ss.getSheetByName('Candidate Listing'); // change here
var sv = source
.getDataRange()
.getValues();
sv.shift();
des.insertRowsAfter(1, sv.length);
des.getRange(2, 1, sv.length, source.getLastColumn()).setValues(sv);
}
function dedupe() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Candidate Listing');
var data = sheet.getDataRange().getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var duplicate = false;
for(j in newData){
if(row.join() == newData[j].join()){
duplicate = true;
}
}
if(!duplicate){
newData.push(row);
}
}
sheet.clearContents();
sheet.getRange(1, 1, newData.length,
newData[0].length).setValues(newData);
}
I would expect new data to be posted, the full range of data to be checked for duplicates, and new data posted by the script to be removed if it is found to be a duplicate.

So I think my original post did work from the de-dupe perspective but I also needed it to only look at one value so I found the below script to do so which works much better.
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getLastRow();
var firstColumn = sheet.getRange(1, 2, rows, 1).getValues();
firstColumn = firstColumn.map(function (e) {return e[0]})
for (var i = rows; i >0; i--) {
if (firstColumn.indexOf(firstColumn[i-1]) != i-1) {
sheet.deleteRow(i);
}
}
}

Related

Google Apps Script: Prevent duplicate copies based on two columns

I am working with some colleagues who want the following to happen within a Google Sheet:
A Google Form contains a question that asks which Counselor a student is assigned to (among other questions)
Forms are submitted throughout the year by students
When a form is submitted, the form data goes into a Google Sheet in a Responses sheet
The Counselors would like a copy of each row to appear in another sheet within the main Sheet, based on the name of the Counselor
In their own sheets, each Counselor needs to be able to manipulate the data (sorting, highlighting rows, adding notes to the row/submission) ←hence a copy is needed instead of a query
I have the following script that copies the rows in the correct Counselor sheet, and does not copy a row into a Counselor sheet if it already appears. However, if a Counselor modifies anything in the row, the script will make a duplicate row (with the original data) the next time it is run, perhaps because it sees the modified row as not an exact match.
Is there a way to modify my script so it can check against a unique part of a row in the Responses sheet (the columns at indexes 0 and 1 together in the same row create a unique entry) in any part of a Counselor sheet before it creates a copy? In other words, it would not create a duplicate row if the Counselor modifies anything except for columns 0 and 1.
function copyData() {
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[4]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var range = sheet.getDataRange();
var data = range.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm stuck at this point and am not sure how to proceed.
NOTE: I have code to add a button to the menu list for the Counselors to run this script as needed since the forms can be submitted at any time. Using "onFormSubmit" does not work because there is a potential for multiple students to submit the form at the same time, which I've seen can cause a row or two to not be copied over.
If I understand your question correctly, you want to find a way to avoid duplicated rows, even if you edit them.
In order to do that, you have to define a value for each row that won't change and that is unique. My suggestion would be the following :
Installable trigger with the function custom_onFormSubmit
In the function get Uid (unique ID), and add it to each row submitted
Edit your code in order to search duplicate only with this Uid
First, add this function your Google Form Apps Script:
//add unique ID at a defined column each time a google form is submitted
function custom_onFormSubmit(e){
var uuid = e.triggerUid;
//alternatily you can use:
//var uuid = Utilities.getUuid();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var row = range.getLastRow();
sheet.getRange(row, 10).setValue(uuid); //column 10 is for example, adapt to your need
}
------ EDIT: alternative function without trigger onFormSubmit, add this function before
function check_insert_uuid(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName([SHEETNAME]);
var range = sheet.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][10] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 10, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
------ END EDIT -------
Then you just have to edit your function copyData
FROM:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow.join() == row.join()) {
duplicate = true;
break;
}
}
TO:
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[10] == row[10]) { //same example of column index 10
duplicate = true;
break;
}
}
References:
Installable Triggers
Google Form Events
Apps Script getuuid (Unique ID are not 100% unique in time and space, but will certainly answer your project)
Based on the help from #waxim-corp, here is the final script that accomplishes my goal:
function onOpen(e) {
let ui = SpreadsheetApp.getUi();
ui.createMenu("🤖 Copy Data 🤖")
.addItem("Let's Do This!", 'checkForID')
.addToUi();
};
function checkForID(){
var ss = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var range = ss.getDataRange();
var values = range.getValues();
for (var x = 0; x < values.length; x++) {
if (values[x][0] == "") {
let uuid = Utilities.getUuid();
range.offset(x, 0, 1, 1).setValue(uuid);
}
}
SpreadsheetApp.flush(); //force new data to sync before copyData
copyData(); //call copy function
}
function copyData(){
var formResponses = SpreadsheetApp.getActive().getSheetByName("Form Responses 1");
var formValues = formResponses.getDataRange().getValues();
formValues.shift(); // remove the header row
formValues.forEach(function(row) {
var sheetName = row[5]; // the value of "My College Counselor is" column
var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
var rangeC = sheet.getDataRange();
var data = rangeC.getValues();
var duplicate = false;
for (var i = 0; i < data.length; i++) {
var currentRow = data[i];
if (currentRow[0] == row[0]) {
duplicate = true;
break;
}
}
if (!duplicate) {
sheet.appendRow(row);
}
});
}
I'm sure it could be more efficient, but it works well.

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.

How to remove duplicates per row and loop by column in Google Apps Scripts?

I have a set of data that is categorised by months every 3 columns. In other words, e.g. columns A to C is September, columns D to F is October and so on. I would like help on removing duplicates in each row for each month, so that if data in one row in columns A and B and C matches another row, the duplicate cells in that row is deleted. This is then repeated for the next month (next 3 columns D, E and F).
So far I've only managed to find scripts that remove duplicate rows in a specific range but not how to remove duplicate cells in rows and loop every few columns.
This is the code I have so far for removing duplicate rows in a specific range:
function deleteDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = "Master";
var sourceSheet = ss.getSheetByName(source);
var sourceRange = sourceSheet.getRange(3, 1, sourceSheet.getLastRow(), 3)
var sourceData = sourceRange.getValues();
var keepData = new Array();
var deleteCount = 0;
for(i in sourceData) {
var row = sourceData[i];
var duplicate = false;
for(j in keepData) {
if(row[0] == keepData[j][0] &&
row[1] == keepData[j][1] &&
row[2] == keepData[j][2]) {
duplicate = true;
}
}
if(!duplicate) {
keepData.push(row);
}
}
sourceRange.clear();
sourceSheet.getRange(3, 1, keepData.length,
keepData[0].length).setValues(keepData);
}
I'm pretty new to scripting so any help with modifying this script or new inputs is greatly appreciated. Thanks.
Update: Here's an example data in my google sheets if it makes it clearer.
https://docs.google.com/spreadsheets/d/1IDcBlzFj6992RvMXS3TOyqaS5HsSRqjxk5HXsnYrGkA/
Try this:
There's probably a lot of easier ways to do it. But I liked the problem so here's a way to do it. This example generates is own random data so you need to take that out before running it on your dataset. But if I pasted it correctly and you copy it correctly and make a Sheet2 for it to work on then it should run okay for you. If not, please be prepared to debug it on your own.
By the way it also has some extraneous notes and result so that I could figure out if it was working or not. Good Luck. Also I haven't tried it but I don't think this cares if the months appear in consecutive columns.
function removeDupesInGroups(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet2');
generateContent(3,1,100,9);
var rg=sh.getRange(1,1,sh.getLastRow(),9);
var vA=rg.getValues();
var hdr={};
var dA=[];
for(var j=0;j<vA[0].length;j++){
if(!hdr.hasOwnProperty(vA[0][j])){
hdr[vA[0][j]]=[];
hdr[vA[0][j]].push(j);
}else{
hdr[vA[0][j]].push(j);
}
}
var n=0;
for(key in hdr){
var m=key;
var uA=[];
for(var i=2;i<vA.length;i++){
var tA=[];
for(var j=0;j<vA[i].length;j++){
var elements=hdr[key].join(',');
if(hdr[key].indexOf(j)>-1){
tA.push(vA[i][j]);
}
}
if(i<vA.length){
if(!isArrayContained(uA,tA)){
uA.push(tA);
}else{
sh.deleteRow(i - n + 1);
dA.push([Utilities.formatString('n=%s,i=%s,deleeting row %s,key=%s,group=%s',n,i,i-n+1,key,tA.join('|'))]);
sh.getRange(1, 10).setValue(Utilities.formatString('n=%s,i=%s,deleeting row %s,group=%s',n,i,i-n+1,tA.join(',')));
n++;
}
}
}
}
var resultrg=sh.getRange(sh.getLastRow()+2,1,dA.length,1).setValues(dA);
}
function isArrayContained(c,t){
for(var i=0;i<c.length;i++){
var isEqual=true;
for(var j=0;j<c[i].length;j++){
var t1=c[i][j];
var t2=t[j];
if(c[i][j]!=t[j]){
isEqual=false;
break;
}
}
if(isEqual){
return true;
}
}
return false;
}
function generateContent(row,col,rows,cols){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet2');
sh.getDataRange().clearContent();
sh.getRange(1,1,2,9).setValues([['Jan','Jan','Jan','Feb','Feb','Feb','Mar','Mar','Mar'],['Header','Header','Header','Header','Header','Header','Header','Header','Header']])
var rg=sh.getRange(row,col,rows,cols);
var vA=rg.getValues();
var s="For the Lamb on the Throne will be their Shepard.";
for(var i=0;i<vA.length;i++){
for(var j=0;j<vA[i].length;j++){
vA[i][j]=s.charAt(Math.floor(Math.random() * s.length+1));
}
}
rg.setValues(vA);
}
Not sure if this is the most efficient, but I tried this amendment to my code and it works for my purposes with test data. Here's the final code I used.
function deleteDuplicates() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var source = "Master";
var sourceSheet = ss.getSheetByName(source);
for (var a=1; a<=19; a+=3){
var sourceRange = sourceSheet.getRange(3, a, sourceSheet.getLastRow(), 3)
var sourceData = sourceRange.getValues();
var keepData = new Array();
var deleteCount = 0;
for(i in sourceData) {
var row = sourceData[i];
var duplicate = false;
for(j in keepData) {
if(row[0] == keepData[j][0] &&
row[1] == keepData[j][1] &&
row[2] == keepData[j][2]) {
duplicate = true;
}
}
if(!duplicate) {
keepData.push(row);
}
}
sourceRange.clear();
sourceSheet.getRange(3, a, keepData.length,
keepData[0].length).setValues(keepData);
}
It basically starts at column a and adds 3 to it which changes the source range and loops until it reaches column 19.

Google Scripts delete duplicates from select range (not whole sheet)

Below is a Google Sheets script for deleting duplicate rows from a spreadsheet. While it does work in removing duplicate rows it also destroys in-cell formulas in the process. I currently have a series of sheets where all of the raw data is contained within Columns A:P and all of my formulas are relegated to Columns Q:T.
In my attempts to limit the following script to work only on Columns A:P I receive a missing argument error upon running the script.
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange("A2:P").getValues();
TL:DR, I love the script, but I want to limit the range upon which it is ran. Any help? Thank you.
https://developers.google.com/apps-script/articles/removing_duplicates
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var duplicate = false;
for(j in newData){
if(row.join() == newData[j].join()){
duplicate = true;
}
}
if(!duplicate){
newData.push(row);
}
}
sheet.clearContents();
sheet.getRange(1, 1, newData.length,
newData[0].length).setValues(newData);
}
EDIT: I believe the following is above a valid question, but I would like to add a new observation that may have some impact on what's happening(??). When I initiate a find and replace from within sheets to remove all instances of the word 'null' then sheets removes null and leaves every other cell alone. However if I run a script to remove 'null' it reformats all of my cells changing dates & times to decimals. etc. Is there a means by which to run a script and avoid overall unintended actions such as sheet wide reformatting?
You will need to use getRange() function to specify the range on which you want to work with.
var sheet = SpreadsheetApp.getActiveSheet();
var rng = sheet.getRange("A2:P")
var data = rng.getValues();
Also, you clear the entire sheet with this line sheet.clearContents(); instead use this
rng.clearContents()
That way you only clear contents in the range specified by rng
Your final code should look like this
function removeDuplicates() {
var sheet = SpreadsheetApp.getActiveSheet();
var rng = sheet.getRange("A2:P")
var data = rng.getValues();
var newData = new Array();
for(i in data){
var row = data[i];
var duplicate = false;
for(j in newData){
if(row.join() == newData[j].join()){
duplicate = true;
}
}
if(!duplicate){
newData.push(row);
}
}
rng.clearContents();
sheet.getRange(1, 1, newData.length,
newData[0].length).setValues(newData);
}
It's been a few years since the answer was provided. For some reason this wasn't working for me now.
I removed the "s," making rng.clearContents(); into rng.clearContent();
Also the range start from row 1 to row 2 making sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData); into sheet.getRange(2, 1, newData.length, newData[0].length).setValues(newData);
Worked for me. Thank you!

Deleting rows in google sheets using Google Apps Script

I encountered the weirdest error while trying to delete rows that match a specific value using Google Apps Script.
Here is my Code:
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("file.csv");
var values = sheet.getRange("N2:N").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'del'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = 0; i < row_del.length; i++)
{
sheet.deleteRow(row_del[i]);
}
}
The code that I have written picks up the row numbers that should be deleted but not all these rows are deleted in my first try. I should execute my script a number of times for these rows to be deleted.
If my code has an error, it should show up and if the logic is wrong, incorrect rows must be deleted. I encounter neither of these scenarios and I should just execute this function multiple times.
Is there something that I'm missing here?
When a row is deleted from a sheet, the rows below it get renumbered even as the script continues to run. If the script subsequently tries to also delete those rows, the result is unpredictable. For this reason, when deleting rows one should proceed from bottom to top. In your case, like so:
for (var i = row_del.length - 1; i>=0; i--) {
sheet.deleteRow(row_del[i]);
}
refer to lock function suggested by google team:
var lock = LockService.getScriptLock();
lock.waitLock(30000); // lock 30 seconds
//do whatever you want here
lock.releaseLock();
That way, you got your deleting job work once at a time! The system thread won't go on to other jobs until 30 seconds is up or releasing the lock.
google dev-document: https://developers.google.com/apps-script/reference/lock/lock
To delete blank rows from a single named sheet, assuming column 1 has data in valid rows.
Search and delete from highest row number to lowest row number.
// Deletes any row whose first column is blank
// WARNING: Assumes any valid row has data in column 1
function deleteBlankRows() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet3");
var lastRow = SpreadsheetApp.getActiveSheet().getLastRow();
for (var i = lastRow; i > 0; i--) {
var range = sheet.getRange(i,1);
var data = range.getValue();
if (data == '') {
sheet.deleteRow(i);
}
}
}
increment i only if you didn't delete a row
function del_F_rows(){
var i=1;
while(!sht_balanceHistory.getRange(i,1).isBlank()){
if(sht_balanceHistory.getRange(i,3).getValue()=="F")
sht_balanceHistory.deleteRow(i);
else
i=i+1;
}
}
You can just delete the rows alter the last row using the deleteRows function like this:
var maxRows = newsheet.getMaxRows();
var lastRow = newsheet.getLastRow();
if (maxRows-lastRow != 0)
{
newsheet.deleteRows(lastRow+1, maxRows-lastRow);
}
Update 2020
A faster and a more modern JavaScript approach would be to use forEach and reverse() to iterate backwards.
It makes more sense to flatten the values array since it concerns data of a single column.
Solution:
function myFunction() {
const doc = SpreadsheetApp.getActiveSpreadsheet();
const sheet = doc.getSheetByName("file.csv");
const values = sheet.getRange("N2:N").getValues().flat();
values.reverse().forEach((r,i)=>{
if (r=='del'){
sheet.deleteRow(values.length-i+1);
}
});
}
So, is this what it should look like?
function myFunction() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheet = doc.getSheetByName("Sheet1");
var values = sheet.getRange("A:A").getValues();
var row_del = new Array();
for(var i=0;i<values.length;i++)
{
if(values[i] == 'N'){
row_del.push(i+2); // This line was added for debugging purposes.
// sheet.deleteRow(i+2) was the line that was in this condition
// (i+2) is used because row 1 has headers and the range starts from 0.
}
}
// Logger.log(row_del);
// GmailApp.sendEmail("my_email_address", "subject", row_del)
for (var i = row_del.length - 1; i>=0; i--) { sheet.deleteRow(row_del[i]); }
}
Copy pasting from: https://gist.github.com/dDondero/285f8fd557c07e07af0e
Instead of looping through the rows twice, you can count how mwny rows have been deleted, to calculate the correct index for the row that you will delete next.
function deleteRows() {
var sheet = SpreadsheetApp.getActiveSheet();
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();
var rowsDeleted = 0;
for (var i = 0; i <= numRows - 1; i++) {
var row = values[i];
if (row[0] == 'delete' || row[0] == '') { // This searches all cells in columns A (change to row[1] for columns B and so on) and deletes row if cell is empty or has value 'delete'.
sheet.deleteRow((parseInt(i)+1) - rowsDeleted);
rowsDeleted++;
}
}
};
This is a script I used to delete everything but certain values, but can be easiliy modified to keep certain strings and runs a lot faster than looping through all the data filters out and deletes the values you want to remove and bulk deletes. my data had about 10000 rows so loop would have taken forever.
function DeleteCertainRows(){
columnCopy("D","Z","YourSheetName");//Copy from your target column in this case D to another column in this case Z
replaceInSheet("Z","YourSheetName","keep String 1","keep");//find and replace the value with what you want to keep
replaceInSheet("Z","YourSheetName","keep String 2","keep");//Can repeat for additional values
DeleteValueInColumn("Z","YourSheet","keep");//filters and deletes all other values Column is case sensitive and cant go past Z
};
function replaceInSheet(repColumn,sheetname, to_replace, replace_with) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var values = sheet.getRange(repColumn+'1:'+repColumn+sheet.getMaxRows());
var textFinder = values.createTextFinder(to_replace);
var replaceall = textFinder.replaceAllWith(replace_with);
};
function columnCopy(copyfrm,copyto,sheetname){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var copyrange = sheet.getRange(copyfrm+'1:'+copyfrm+sheet.getMaxRows());
var pasterange = sheet.getRange(copyto+'1:'+copyto+sheet.getMaxRows());
copyrange.copyTo(pasterange);
};
function DeleteValueInColumn(colStr, sheetname, deleteval){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var filterRange=sheet.getDataRange();
var myFilter = filterRange.createFilter();
var CriteriaBuild = SpreadsheetApp.newFilterCriteria();
var Criteria = CriteriaBuild.whenTextDoesNotContain(deleteval);//change to whenTextContains to delete your string value instead of everything else
var myCriteria = Criteria.build();
var str = colStr;
var myCol = parseInt(str.charCodeAt(0) - 64);
Logger.log(myCol);
myFilter.setColumnFilterCriteria(myCol, myCriteria);
var deleterange=sheet.getRange('2:'+sheet.getLastRow());
sheet.deleteRows(deleterange.getRow(), deleterange.getNumRows());
myFilter.remove();
};
Another method is to sort records and delete all at once.
My following code will sort the data with N/A value and will delete all rows at once.
function DeleteErrorLine2() {
var SS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("ORDER SHEET"); //Get Open Lines Sheet
var lastRow = SS.getLastRow();
var range = SS.getRange(2, 1, lastRow-1, 33); //get range
range.sort({column: 2, ascending: false}) // filter data descending
var cell = SS.getRange('B1'); // values stored in cell B
var ct = 1; // starting row
while(cell.offset(ct, 0).getValue() == "#N/A" ) {
ct++;
}
if(ct!=1){
ct = ct - 1 // minus 1 to get the last row
SS.deleteRows(2, ct)
}
range.sort({column: 2, ascending: true}) // filter data again ascending
}