I have the code below which allows me to update the existing spreadsheet (much like an matrix lookup). And what I am doing right now is to include a error log which will be updated in another sheet, this includes Missing data in Column A in Destination Spreadsheet and Missing Column in Destination Spreadsheet. I have worked with Missing data in Column A, my problem was the Missing Column, cause instead of the column name (found in the row 1) the result shows the column index:
code:
function updateShadowSKU() {
var source = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var dest = SpreadsheetApp.openById('179PCrIWe1mvbbzOi9ySEHxzBlFaXpCB2i0wHlYVE2vg').getSheetByName('Sheet1');
var destRange = dest.getDataRange();
var destValues = destRange.getValues();
var destHeaders = destValues[0];
var destIds = destValues.map(e => e[0]);
var values = source.getDataRange().getValues().map(e => e.filter((f, i) => !i || i > 10));
var colMap = values[0].map(e => destHeaders.indexOf(e));
Logger.log(colMap);
values = values.map((e, i, arr) => e.map((f, j) => [e[0], colMap[j], f, values[0][j], i, j])).flat().filter(e => e[0] && e[1] && e[2] && e[3] && e[4] && e[5]);
Logger.log(values);
// Initialize an array to store log entries
var logEntries = [];
// Check for missing SKUs
values.forEach(function(e) {
if (!destIds.includes(e[0])) {
logEntries.push(["Missing SKU", e[0], e[3]]);
}
});
// Check for missing column headers
colMap.forEach(function(e, i) {
if (e == -1) {
var index = destHeaders.indexOf(values[0][i]);
colMap[i] = index;
if (index == -1) {
logEntries.push(["Missing column header", values[0][e], ""]);
}
}
});
if (!values.length) {
logEntries.push(["No changes to make", "", ""]);
} else {
values = values.map(e => [destIds.indexOf(e[0]), e[1], e[2]]);
Logger.log(values.length + ' changes to make');
Logger.log(values);
values.forEach(function(e) {
try {
destValues[e[0]][e[1]] = e[2];
} catch (err) {}
});
destRange.setValues(destValues);
}
// Write log entries to the 'Shadow Log' sheet in the destination spreadsheet
if (logEntries.length > 0) {
var logSheet = SpreadsheetApp.openById('179PCrIWe1mvbbzOi9ySEHxzBlFaXpCB2i0wHlYVE2vg').getSheetByName("Shadow Log");
if (logSheet == null) {
logSheet = SpreadsheetApp.openById('179PCrIWe1mvbbzOi9ySEHxzBlFaXpCB2i0wHlYVE2vg').insertSheet("Shadow Log");
}
logSheet.clear();
logSheet.getRange(1, 1, logEntries.length, 3).setValues(logEntries);
}
}
the code block for getting the missing data in Column A destination spreadsheet works fine, but what I am having a hard time is getting the column name. The code block below shows only the column index instead of the comlumn title/header:
// Check for missing column headers
colMap.forEach(function(e, i) {
if (e == -1) {
var index = destHeaders.indexOf(values[0][i]);
colMap[i] = index;
if (index == -1) {
logEntries.push(["Missing column header", values[0][e], ""]);
}
}
});
sample sheets:
source: https://docs.google.com/spreadsheets/d/1zGqiYocUmSBRDPKRqI3iI3nIf7w3b9C7ykT-QffBROA/edit#gid=0
destination: https://docs.google.com/spreadsheets/d/1PjOvhscblzPxaBnJi1Q5oB6iCAt9_emljUR6vlAfIA0/edit#gid=0
example:
source data:
| SKU | walmart1 | amazon 2 | ebay1 |
|============|==============|==============|===========|
| SKUitem1 | SKUwm1 | | SKUebay1 |
| SKUitem2 | | | SKUitem5 |
| SKUitem3 | SKUwmi1 | | |
destination:
| items | **walmart2** | **amazon 1** | ebay1 |
|============|==============|==============|===========|
| SKUitem1 | SKUwm1 | | SKUebay1 |
|**SKUitem5**| | | |
| SKUitem3 | | | |
desired result:
in this scenario the 'Shadow Log' sheet should result into
| Missing SKU | SKUitem2 |
| Missing Column | walmart1 |
missing SKU - cause there were an update from the source but unable to find it in the destination
missing column - cause there were an update on that column but unable to find that column
you will also see that there is a mismatch column amazon 2 --> amazon 1, but you will see nothing in the desired results, it is because there's no new data to be updated.
I hope this clears things out, please don't hesitate to ask me
Check and Fix the Index when Accessing Desired Data
I noticed that when you tried to access the data from values, you used [e] as the index which will lead to an undefined value since [e] will always be -1 in your case and there is no -1 index in an arrays.
Also, when using the forEach method, the first variable (e in your case) will be the value to be processed while the second variable will be the index value (i in your case). Hence, you should use [i] when accessing data using loops.
With all that, I added a sourceCol variable to store all wanted column headers from the source.
You may change the range if you should add more columns. I changed values[0][e] to sourceCol[0][i] to access the wanted column header.
The new code should look like this: (I added comments for further guidance)
function updateShadowSKU() {
var source = SpreadsheetApp.getActive().getSheetByName('Sheet1');
var dest = SpreadsheetApp.openById('Destination Sheet ID').getSheetByName('Sheet1');
var destRange = dest.getDataRange();
var destValues = destRange.getValues();
var destHeaders = destValues[0];
var destIds = destValues.map(e => e[0]);
var values = source.getDataRange().getValues().map(e => e.filter((f, i) => !i || i > 10));
//-----------------------------------------------------------------------------------------------
//added sourceCol variable to specifically get the array of all Column Headers from the Source
var sourceCol = source.getDataRange().getValues().map(e => e.filter((f, i) => i >= 3 && i <= 17));
var colMap = sourceCol[0].map(e => destHeaders.indexOf(e));
//-----------------------------------------------------------------------------------------------
values = values.map((e, i, arr) => e.map((f, j) => [e[0], colMap[j], f, values[0][j], i, j])).flat().filter(e => e[0] && e[1] && e[2] && e[3] && e[4] && e[5]);
// Initialize an array to store log entries
var logEntries = [];
// Check for missing SKUs
values.forEach(function (e) {
if (!destIds.includes(e[0])) {
logEntries.push(["Missing SKU", e[0], e[3]]);
}
});
// Check for missing column headers
colMap.forEach(function (e, i) {
if (e == -1) {
//--------------------------------------------------------------
//change values[0][e] to sourceCol[0][i]
logEntries.push(["Missing column header", sourceCol[0][i], ""]);
//--------------------------------------------------------------
}
});
if (!values.length) {
logEntries.push(["No changes to make", "", ""]);
} else {
values = values.map(e => [destIds.indexOf(e[0]), e[1], e[2]]);
Logger.log(values.length + ' changes to make');
Logger.log(values);
values.forEach(function (e) {
try {
destValues[e[0]][e[1]] = e[2];
} catch (err) { }
});
destRange.setValues(destValues);
}
// Write log entries to the 'Shadow Log' sheet in the destination spreadsheet
// Added an else statement to clear the 'Shadow Log` when the log is empty.
var logSheet = SpreadsheetApp.openById('Destination Sheet ID').getSheetByName("Shadow Log");
if (logEntries.length > 0) {
if (logSheet == null) {
logSheet = SpreadsheetApp.openById('Destination Sheet ID').insertSheet("Shadow Log");
}
logSheet.clear();
logSheet.getRange(1, 1, logEntries.length, 3).setValues(logEntries);
}
else {
logSheet.clear();
}
}
Output
In testing the code, I deleted 5 column headers (as seen below):
When I ran the code with the modification, I got the following output:
References
forEach
Accessing Arrays
Related
I want to get the value of all the cells of rows. But not from all the rows, only rows having specific name in a specific column.
I attached a screenshot. I want to get all row values which "userID" is "user3". It means row 4 row 6 data.
I used following code.
function getByName(colName, row) {
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var col = data[0].indexOf(colName);
if (col != -1) {
return data[row-1][col];
}
}
Above I getting only specific cell value in the user ID column. Not all data in the ROW.
function getAllrowData() {
var user3 = getByName("userID",2);
var values = user3.getDisplayValues()
Logger.log(values)
return values
}
getByname giving only cell value.
I want following result so I can display data in html, belongs to only "user 3" in the image. Help me to correct my code.
C 25 30 0 16 user3
E 28 36 6 19 user3
Get Data by Row Number and Column Name:
function getByName(user, colName = 'userID') {
const sheet = SpreadsheetApp.getActiveSheet();
const data = sheet.getDataRange().getValues();
const hA = data.shift();
const idx = hA.reduce((a, h, i) => (a[h] = i, a), {});
let o = data.map(r => {
if (r[idx[colName]] == user) {
return r;
}
}).filter(r => r );
if (o && o.length > 0) {
return o;
}
}
Remove [col] from return data[row-1][col];
The above because sheet.getDataRange().getValues(); return an Array of Arrays of values. Using two indexes returns a cell value, using only the first index will return an Array having all the row values.
The employee sheet contains the id of the employee in Column B. image may help to understand how this code should be work.
How can I get the rows matches the employee id?
I tried the following script and more but it doesn't seem to work.
Sample Image
function getTwoVal() {
var idUrl = "idi";
var sheet = SpreadsheetApp.openById(idUrl).getSheetByName("Sheet1");
var data = sheet.getDataRange().getValues()
var filteredRows = data.filter(function (row) {
if (row[5] === '102' || row[5] === '106') {
return row;
}
});
console.log(filteredRows )
}
Try
var filteredRows = data.filter(function (row) {
if (row[1] == '102' || row[1] == '106') {
return row;
}
});
if id is in column B, use row[1] instead of row[5]
if id may be numeric, use == instead of ===
or, if you want between as the title suggests
var filteredRows = data.filter(function (row) {
if (row[1] >= 102 && row[1] <= 106) {
return row;
}
});
I am trying to set the value of a cell in a column when two other columns at an index match values. How can I set the value using an index? (<Edited)
for (let i = 0; i < assetId.length; i++) {
for (let p = 0; p < oldId.length; p++) {
if (assetId[i] !="" && oldId[p] !="") {
if (assetId[i] == oldId[p]) {
Logger.log('Old Match: ' + assetId[i])
//if match modify 4th column at row [i] to 'null'
d.getRange(i,3).setValue('null')
}
}
}
}
Based on if assetId[i] == oldId[p], I am trying to change column F of row [i] to 'null'
Edit (examples requested)
Column J is oldId and K is newId
EXPECTED OUTPUT: F4 should be null
Full code:
function replaceIds() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const r = ss.getSheetByName("Form Responses 1")
const d = ss.getSheetByName("Devices")
const oldId = r.getRange("J2:J").getValues().flat()
const newId = r.getRange("K2:K").getValues().flat()
const studentName = r.getRange("C2:C").getValues().flat()
const assetId = d.getRange("G3:G").getValues().flat()
const annotatedUser = d.getRange("E3:E").getValues().flat()
for (let i = 0; i < assetId.length; i++) {
for (let p = 0; p < oldId.length; p++) {
if (assetId[i] !="" && oldId[p] !="") {
if (assetId[i] == oldId[p]) {
Logger.log('Old Match: ' + assetId[i])
//if match modify 4th column at row [i] to 'null'
d.getRange(i,3).setValue('null')
}
}
}
//new asset ID loop
for (let r = 0; r < newId.length; r++) {
//Logger.log(oldId[p])
if (assetId[i] !="") {
if (newId[r] !="") {
//Logger.log('## not null ##')
if (assetId[i] == newId[r]) {
Logger.log('New Match: ' + assetId[i])
}
}
}
}
}
}
Issue:
Issue is that, using a nested for loop is not a good idea as you can't properly follow where the proper index is, and it will also needlessly reiterate on items that were already visited.
Solution:
Looping only on the assetId should suffice, then using indexOf as it will help you identify if a certain element (current assetId) belongs in an array (list of oldIds).
If assetId is found, indexOf will return a non-negative number (which is what index the element is found in the array).
Exclude empty assetIds due to how you get your data
Then you can remove the column of that same row, but since index starts at 0 and your data starts at 3rd row, we need to offset the getRange row so it would match the cell we want to delete properly.
Modifying your current solution, this is what the solution says above, and should work.
Script:
function replaceIds() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const r = ss.getSheetByName("Form Responses 1")
const d = ss.getSheetByName("Devices")
const oldId = r.getRange("J2:J").getValues().flat()
const newId = r.getRange("K2:K").getValues().flat()
const studentName = r.getRange("C2:C").getValues().flat()
const assetId = d.getRange("G3:G").getValues().flat()
const annotatedUser = d.getRange("E3:E").getValues().flat()
// loop your assetId
assetId.forEach(function(cell, index){
// if assetId is listed under oldId, remove annotated location of that row
// also, skip any rows where assetIds are blank
if(oldId.indexOf(cell) > -1 && cell != "")
// offset here is 3 since assetId starts at G3 and index starts at 0
// 3 - 0 = 3, which is the offset, and 6 is column F
d.getRange(index + 3, 6).setValue('');
});
}
Output:
This function will change the value in column1 if the value of col2 at that index is in column 10 on any line. you can change the indices as you desire.
function findDataBasedOnMatch() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
const sr = 2;//data start row
const vs = sh.getRange(sr, 1, sh.getLastRow() - sr + 1, sh.getLastColumn()).getValues();
const col10 =vs.map(r => r[9]);//you pick the indices
vs.forEach((r,i) => {
if(~col10.indexOf(r[1])) {//you pick the indices
sh.getRange(i + sr, 1).setValue('');
}
});
}
In Google Sheets, I have a spreadsheet called Events/Incidents which staff from various branches populate. I want Column B to automatically generate a unique ID based on the year in column A and the previously populated event. Given that there could be several events on a particular day, rows in column A could have duplicate dates.
The following is an example of what I am looking for in column B:
There can be no duplicates. Would really appreciate some help with either code or formula.
There are my thoughts https://github.com/contributorpw/google-apps-script-snippets/blob/master/snippets/spreadsheet_autoid/autoid.js
The main function gets a sheet and makes the magic
/**
*
* #param {GoogleAppsScript.Spreadsheet.Sheet} sheet
*/
function autoid_(sheet) {
var data = sheet.getDataRange().getValues();
if (data.length < 2) return;
var indexId = data[0].indexOf('ID');
var indexDate = data[0].indexOf('DATE');
if (indexId < 0 || indexDate < 0) return;
var id = data.reduce(
function(p, row) {
var year =
row[indexDate] && row[indexDate].getTime
? row[indexDate].getFullYear() % 100
: '-';
if (!Object.prototype.hasOwnProperty.call(p.indexByGroup, year)) {
p.indexByGroup[year] = [];
}
var match = ('' + row[indexId]).match(/(\d+)-(\d+)/);
var idVal = row[indexId];
if (match && match.length > 1) {
idVal = match[2];
p.indexByGroup[year].push(+idVal);
}
p.ids.push(idVal);
p.years.push(year);
return p;
},
{ indexByGroup: {}, ids: [], years: [] }
);
// Logger.log(JSON.stringify(id, null, ' '));
var newId = data
.map(function(row, i) {
if (row[indexId] !== '') return [row[indexId]];
if (isNumeric(id.years[i])) {
var lastId = Math.max.apply(
null,
id.indexByGroup[id.years[i]].filter(function(e) {
return isNumeric(e);
})
);
lastId = lastId === -Infinity ? 1 : lastId + 1;
id.indexByGroup[id.years[i]].push(lastId);
return [
Utilities.formatString(
'%s-%s',
id.years[i],
('000000000' + lastId).slice(-3)
)
];
}
return [''];
})
.slice(1);
sheet.getRange(2, indexId + 1, newId.length).setValues(newId);
}
I think it can be simplified in the feature.
There is an easier way to generate unique values that works for me, pick a #, then do +1. Ctrl C, then Ctrl shift V to paste back and remove the formula. Now you are left with thousands of unique IDs.
This is a manual solution but you can do an entire database in a matter of seconds every once in a while.
Description: I have a sheet (Ppto) with a list of IDs (Id), Credits(Cedent) and Debits(Recept). I would like to move these transactions to MP05, If Id are equal. If Ids are not equal move to MP04. Criterion: If Id = use MP05. If Id ≠ use MP04.
I'm a novice in google script, I need some support.
Thanks for your attention
function mp() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ppto = ss.getSheetByName('Ppto.');
var Id = ppto.getRange('B5:B12').getValues();
var cedent = ppto.getRange('D5:D12').getValues();
var recept = ppto.getRange('E5:E12').getValues();
for (var i = 0; i < cedent.length; i++) {
for (var j = 0; j < recept.length; j++) {
if (cedent[i] != '' ) {
if (recept[j] != '' ) {
//if (Id === Id) // MP-05
//if (Id != Id) // MP-04
{
ppto.getRange('H5:H12').setValues(cedent);
ppto.getRange('I5:I12').setValues(recept);
Logger.log(cedent[i]);
ppto.getRange('j5:j12').setValues(cedent);
ppto.getRange('k5:k12').setValues(recept);
}
}
}
}
}
}
Strategy:
FIFO: First-In First Out
Loop through all rows using forEach
If credit is present, Loop again through all rows using some to look for receipts
If credit e[2] in first loop equals receipts f[3] in second loop, Check for id [0]
If ID is equal, splice two empty columns at the end, else at the 2nd position to create a uniform 6-column array
Set that array back to the sheet.
Sample Script:
function transactionSegregator() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ppto = ss.getSheetByName('Ppto.');
var data = ppto.getRange('B5:E12').getValues();
data.forEach(function(e) {
//e: Each row
if (e[2] && e.length == 4) {
//e[2]:credits; If this row is not spliced
data.some(function(f) {
//f:Each row; Second loop
if (e[2] == f[3]) {
//if credits = debit
if (e[0] == f[0]) {
//if id = id, splice two empty columns after Col4, else after Col2
e.splice(4, 0, '', '');
f.splice(4, 0, '', '');
} else {
e.splice(2, 0, '', '');
f.splice(2, 0, '', '');
}
return true;
}
});
}
});
Logger.log(data);
ppto.getRange(5, 6, data.length, data[0].length).setValues(data);// F5
}
References:
Javascript tutorial
Array#forEach
Array#some
Array#splice