-1

I am having trouble with using Google Apps Script and the two functions the deleteRow() and deleteRows() methods.

The code below uses a string matches testing if a string exists in column. If the string exists then I want to do something, in this case delete a couple of rows in my active sheet.

The problem is that it deletes the first row or one row, then does not seem to delete the next row in my code below.

I have been able to verify that even if I do sheet from the end of the sheet starting at last row and going to first row or like below from first row to last row the same problem exist.

It deletes one row but never deletes more then one row. In my case no error is produced just row does not get deleted using either method.

    var data = ws.getRange("A2:U" + ws.getLastRow()).getValues();  

    var i = 0;
    var W = 23;
    var deleted = "DELETED"; 

    var sheetDataAsArr = SpreadsheetApp.getActiveSheet().getDataRange().getValues(); // 2020Members 

    sheetDataAsArr.forEach(function(row,i)     {   

    // The i is a index 
       if ( row[W] = 'DELETED' ) {
          // DO SOMETHING HERE
            Logger.log('EMAIL DELETED ROW row is :' + row[B]);  // The email 
            ws.deleteRow(i+1);  // It deleted the record
            i--; // Decrement i because record was deleted 
       }
     i++; 
    });

The problem is very strange, I have been able to duplicate using both functions. It does not delete the next row but I get to see the next row because I show the correct email address but does not do the delete the second time through the loop but also does not produce any error message just does not do the delete the second one but deletes the first row just fine.

It only allows me to delete one row and no more then one row

   var ws = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("2020Members");

         The following above works fine now 

     var myValue = ws.getRange(i+1, W).getValue(); 

This is the value in the sheet in the W column and the if works now also !

Whether I go from the last row to the first row but not the header and it still only allows me to delete one row and stop on the next row so if i traverse the sheet from first to last or last to first it only will not let me delete only one row and stops.

Do I need to sleep or flush something before doing the second delete?

Second example of code above I even tried to use ws.deleteRows(i+1,1); and same problem happens.

     The following above works fine now 

     var myValue = ws.getRange(i+1, W).getValue(); 

      This is the value in the sheet in the W column and the if works now also !
  • Do I need to sleep or flush something before doing the second delete ? – whitejeep1953 Jun 05 '20 at 12:36
  • You do know that JS does not have a `=` comparison operator, right? What you did, is written a `if(true)` statement that *always* passes. Next, `i--` does nothing as this is not how `forEach` [works](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach). More, `row[U]` will be either always `true` or always `false` (either not keep it in loop or do something with `U`). Once you deal with the above, the question should go away – Oleg Valter is with Ukraine Jun 05 '20 at 12:48
  • I am sorry i am new to apps scripts and kind of rusty ! Please explain ? I do know row[U] will be true or false in my situation iit is a check box checked in that column ! – whitejeep1953 Jun 05 '20 at 13:18
  • GAS is a covenience layer on top of JavaScript, so underneath it is plain old JS. Please, read up on `foEach` and [comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators). And remove the outer scope `i` - it is [pointless](https://stackoverflow.com/questions/11901427/an-example-of-variable-shadowing-in-javascript). Well, you can disregard the statement about `row[U]` - it is not always true / false. – Oleg Valter is with Ukraine Jun 05 '20 at 13:20
  • Yes, it works, but it also works *all the time* (as assignment returns the value that is assinged, that is `DELETED`, which is truthy, which always evaluates to true). As for your question - judging from the code you provided, it should delete all rows starting from 1 to `sheetDataAsArr.length` in `ws` sheet. Since you probably have a correlation between the checkbox being ticked and `DELETED` set in col `W`, you see the correct result (if that's what you mean - but your nested `if` does not do any comparison for sure) – Oleg Valter is with Ukraine Jun 05 '20 at 13:24
  • Btw, another thing is that once you delete a row, it shifts the positions up. E.g. if you had 2 rows, at fitst one `i === 0`, and first row gets deleted [if it matches the condition - but in your code the condition only depends on `row[U] == true`]. You now have 1 row, but `i` will go to `i === 1` the next iteration, therefore you will try to delete row at position 2, but it is actually at position 1 – Oleg Valter is with Ukraine Jun 05 '20 at 13:31
  • But what is the reason that it deletes one row and not delete the next row yet the email shows that i am on the correct row i see it in the log - is because I am dealing with a copy of the data and allows for one change after the script ends ? – whitejeep1953 Jun 05 '20 at 13:37
  • Well, as I mentioned, you are not on the correct row (well, you are, but not the `i+1`) - except for the first one. Re:i+1 Of course nothing would happen as rows are 1-based, and indices 0-based, hence the `i+1`. The issue is that the more rows you process, the worse the offset from the actual row you want to delete is ( see above as to what happens to row positions ). Please, provide the code where the `ws` is defined and a sample data structure / copy of the spreadsheet for us to test. But yes, the operation may be batched - it only affects when you will see the change, though – Oleg Valter is with Ukraine Jun 05 '20 at 13:48
  • Looks ok, but please, post in the question, comments are not suited for that. Please, also add the sample data structure and expected output (not a description of it). As it stands now, correcting what is discussed above should result in what you expect the script to do. That is, fixing comparison and indexing [only the first row to be removed complies with `i+1` position] – Oleg Valter is with Ukraine Jun 05 '20 at 14:14
  • I made the change to the second piece of code above if i am following you correctly please see the change === fixing comparison but what about indexing also need to stop at row 1 because do not want to delete the headers – whitejeep1953 Jun 05 '20 at 14:35
  • Will the return false cause the break out of the foreach loop which is a callback function ? – whitejeep1953 Jun 05 '20 at 15:08
  • Please add your sheetDataAsArr function for clarity – Aerials Jun 05 '20 at 15:54

2 Answers2

0

One "big problem" with the script is

i--; // Must auto decrement i

inside of

sheetDataAsArr.forEach(function(row,i)     {

}

The above because Array.prototype.forEach() increment i on each iteration. If you want to do reverse iteration, use for

Related

Rubén
  • 34,714
  • 9
  • 70
  • 166
  • So instead of using the forEach use a for loop ? – whitejeep1953 Jun 05 '20 at 15:43
  • Ruben - will using a for also support the callback function can you please give me a example or is the callback not needed here at all ? I do want to use reverse iteration from last row to first row ! – whitejeep1953 Jun 05 '20 at 15:59
  • I think i am closer to getting correct synthetics for the modify the second piece of code according to my understanding but still confused about the For instead of forEach ? – whitejeep1953 Jun 05 '20 at 16:12
  • @whitejeep1953 I added a link to a related question that includes a sample code that uses `for` to do a reverse iteration. – Rubén Jun 05 '20 at 16:35
  • I was able to solve the Delete Row problem by not using a forEach but use a for loop and i do not know what was going on using the forEach only could delete one row so i asome a exception and script would end using a callback function instead traversed the rows from bottom to top using for loop. – whitejeep1953 Jun 06 '20 at 02:38
0

Offset issue

Let's image a hypothetical situation where you have 3 rows with 2 cols each. Second column contains the checked "DELETE" value. Your loop will do three iterations, since values length equals 3:

//first iteration:

pos 1, idx 0 -->  row1 | col1 | DELETE | -- match
                  row2 | col1 | DELETE |
                  row3 | col1 | col2   |

//second iteration:

                  row2 | col1 | DELETE |
pos 2, idx 1 -->  row3 | col1 | col2   | -- no match

//third iteration:

                  row2 | col1 | DELETE |
                  row3 | col1 | col2   |
pos 3, idx 2 -->  ---- | ------------- | -- out of bounds

As you can see, each time forEach moves to next iteration, if a row is deleted, the result using your algorithm becomes "off" by 1. That is if we use the fixed version that actually does the comparison, otherwise the second iteration will delete a row as well.

Keeping track of deleted rows

More importantly, as can be seen from the above, on non-sequential matches the resulting offset will become more and more as number of rows increases. You need to track rows that were deleted and compensate. This sample should give you a good idea of what to do:

function testDeleteRow() {

    const deletedRowNumbers = [];

    const ss = SpreadsheetApp.getActiveSpreadsheet();

    const testName = "test_deletion";

    const sh = ss.insertSheet();

    sh.setName(testName);

    const dimensions = [1, 1, 5, 2];

    const matrix3x2 = sh.getRange(...dimensions);

    matrix3x2.setValues([
        ["column1", "DELETE"],
        ["column1", "KEEP"],
        ["column1", "DELETE"],
        ["column1", "KEEP"],
        ["column1", "DELETE"]
    ]);

    const testRng = sh.getRange(...dimensions);

    const testVals = testRng.getValues();

    let offset = 0;

    testVals.forEach((rowOfValues, i) => {

        const [, toDelete] = rowOfValues;

        if (toDelete === "DELETE") {
            const offsetted = (i + 1 - offset) || 1;

            sh.deleteRow(offsetted);

            deletedRowNumbers.push( i + 1 );

            offset++;
        }

    });

    const ui = SpreadsheetApp.getUi();
    ui.prompt(`Deleted rows: ${deletedRowNumbers.join(", ")}`);

    ss.deleteSheet(sh);
}

Notes

  1. As mentioned times and times again, get rid of the i declaration and increment - it is shadowed by forEach method's callback function i argument, thus increment literally does nothing.
  2. Will the return false cause the break out - no, it won't. Please, read the documentation on forEach. There is a huge disclaimer explaining that you can't stop the execution unless an exception is thrown (forEach literally means "for each element do", it does not make sense to use it unless you want to create a side-effect for each element in the array).
  3. If you need to go from last row to first, just start not from 0, but from the last index of the array of values.
  4. if(row[W] = 'DELETED') is the same as writing if(true). The condition will evaluate to true due to how assignment works - it returns the value that is assigned, thus if statement actually evaluates if("DELETED").
  5. Do I need to sleep or flush something - no, you don't need to. The only reaosn to flush changes is to let users know that something is happening while your script performs heavy lifting or if it depends on non-atomic operation to finish, which is not your case.