0
votes

I have found a few options that come close to what I want to do, but nothing matching it exactly.


The request is simple enough.

"Sheet A" - Master Sheet (has 1 header row)

"Sheet B" - Input Sheet (has 1 header row)

"Column C" - Unique ID (same column on both sheets)


Trigger

  • Sheet B is edited

Actions

  • Script finds the Unique ID from Sheet B in Column C and looks for it in Sheet A in Column C.
  • If it finds it, the entire respective row on Sheet A is replaced with the entire respective row from Sheet B.
  • If it does not find it, a new row is added at the bottom of Sheet A and the entire respective row from Sheet B is added as a new record into the new row at the bottom of Sheet A.
  • The entire respective row on Sheet B is Deleted.

Actions repeat until there are no populated rows in Sheet B from row 2 on (i.e. excluding the row 1 header).

Thanks

Edits

  1. For clarification on why I am looking to do this. I have a Form that is being submitted and sending the data through to Google Sheets (Cognito -> Zapier -> Google Sheets). Part of this form involves repeating sections (line items). The current method that is importing the responses has no issue with adding new responses correctly, however when a response is updated, it cannot find/update the existing row(s) correctly for the repeating sections. So I had the intention of using Sheet A as my master sheet and then using Sheet B to simply be a receiving sheet. This way I can just submit every entry (including updated ones) as a "new" entry on Sheet B, and then have my script do the updating.
  2. Sheet B will be edited automatically every time a new form entry is submitted or updated. The "edit" is basically a new row being added and data being populated into that row. It may be a good idea to add a 1 minute timer to the trigger so that if there is lots of data being added that it gives time for that to happen.
  3. I am not even remotely close to a script expert. I just browse around different scripts other people have made and try to combine them to get them to work for what I need. I have found scripts that will move a row over and then delete it, but it does not check for matching values to update. I have found other scripts that check for unique values and copy over, but they do not delete the original row on the other sheet. I have tried to combine them, but since I don't have the base knowledge, I can't seem to get it to work.
1
It's possible with onChange or onEdit. Probably onEdit is easier because there is more info about your current location in the event object.Cooper
I cannot understand about your goal. I apologize for this. So can I ask you about your goal? 1. About Sheet B is edited, how will you edit it? For example, the values including several rows are copy and pasted? 2. About The entire respective row on Sheet B is Deleted., in this case, the Sheet B always has only one header row? Under this situation, the values are put to the Sheet B? 3. Can I ask you about the current issue of your script?Tanaike
When you mentioned that Sheet B and Sheet A have column C in common my first thought was "why don't you just insert the new record" but it turns out you are treating Sheet B - Column C as a queue-like. can you confirm this? What have you tried so far?Jose Vasquez
Thanks for the questions. I'll answer them with an edit in my question since 500 characters in this reply is not going to be enough.Rylan Grose
It'd be helpful for the community if we had the type of data you have in your sheet B and Sheet A. Can you share a similar Spreadsheet without sensitive data?Jose Vasquez

1 Answers

1
votes

As a workaround I'd use the onEdit simple trigger and a O(n) search

Here's my approach:

function onEdit(e) {
  // If it's not the Sheet B it won't make changes
  if (e.range.getSheet().getName() !== "Sheet B") {
    return;
  }

  var range = e.range;
  var numberRow = range.getA1Notation().slice(1);
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheetA = ss.getSheetByName("Sheet A");
  var sheetB = ss.getSheetByName("Sheet B");

  var currentRowB = sheetB.getRange(`A${numberRow}:D${numberRow}`);
  var id = currentRowB.getValues()[0][2];
  // There's to be 4 values in the row (no empty values)
  if(currentRowB.getValues()[0].filter(value => value !== '').length === 4) {

    // Get all the values in Sheet A
    var rows = sheetA.getDataRange().getValues();
    for (row=1; row < rows.length; row++) {
      // If column C matches the ID replace the row
      if(rows[row][2] === id) {
        var currentRowA = sheetA.getRange(`A${row+1}:D${row+1}`);
        currentRowA.setValues(currentRowB.getValues());
        currentRowB.deleteCells(SpreadsheetApp.Dimension.COLUMNS);
        return;
      }
    }

    // If the ID doesn't match then insert a new row
    var newRow = sheetA.getRange(`A${rows.length+1}:D${rows.length+1}`);
    newRow.setValues(currentRowB.getValues());
    currentRowB.deleteCells(SpreadsheetApp.Dimension.COLUMNS);
  }
}

Which meets the requirements you asked:

  • Script finds the Unique ID from Sheet B in Column C and looks for it in Sheet A in Column C. (line 19-28)
  • If it finds it, the entire respective row on Sheet A is replaced with the entire respective row from Sheet B. (line 22-24)
  • If it does not find it, a new row is added at the bottom of Sheet A and the entire respective row from Sheet B is added as a new record into the new row at the bottom of Sheet A. (line 31-33)
  • The entire respective row on Sheet B is Deleted. (line 22 and 33)

I used this Sheet format as example:

enter image description here

Both Sheets have the same format. Keep in mind that this script checks if there's a valid row (in this specific case 4 columns which compose a row) before replacing it.

As a different approach (handling blank data)

In a summary this script should run every X minutes or the time you want it doesn't matter if there's new data incoming because this code will handle all the data given a certain time.

I edited the code in order to use the Z1 cell as a blocker cell and a time based trigger:

Trigger:

enter image description here

Code

function processCells() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheetA = ss.getSheetByName("Sheet A");
  var sheetB = ss.getSheetByName("Sheet B");

  // If it's not the Sheet B or if there's a process running it won't make changes
  if (sheetB.getName() !== "Sheet B" || sheetB.getRange("Z1") === "Running") {
    return;
  }

  // Use the Z1 cell in order to block or unblock this sheet
  sheetB.getRange("Z1").setValue('Running');

  // Process all the rows
  var numCells = sheetB.getDataRange().getValues().length + 1;
  for (numberRow = 2; numberRow <= numCells; numberRow++) {

    var currentRowB = sheetB.getRange(`A${numberRow}:D${numberRow}`);
    var id = currentRowB.getValues()[0][2];

    // Get all the values in Sheet A
    var rows = sheetA.getDataRange().getValues();
    var match = false;
    for (row=1; row < rows.length; row++) {
      // If column C matches the ID replace the row
      if(rows[row][2] === id) {
        var currentRowA = sheetA.getRange(`A${row+1}:D${row+1}`);
        currentRowA.setValues(currentRowB.getValues());
        currentRowB.deleteCells(SpreadsheetApp.Dimension.COLUMNS);
        match = true;
        break;
      }
    }

    if(!match) {
      // If the ID doesn't match then insert a new row
      var newRow = sheetA.getRange(`A${rows.length+1}:D${rows.length+1}`);
      newRow.setValues(currentRowB.getValues());
      currentRowB.deleteCells(SpreadsheetApp.Dimension.COLUMNS);
    }
  }

  sheetB.getRange("Z1").setValue('');
}

Note that every time the script runs it'll check if there's another one processing the rows by using Z1.

enter image description here

References