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
Actions
Actions repeat until there are no populated rows in Sheet B from row 2 on (i.e. excluding the row 1 header).
Thanks
Edits
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:
I used this Sheet format as example:
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.
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:
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.