How can I compare data in two spreadsheets with appscript? - google-apps-script

I have two spreadsheets named as source_data and target_data. I am comparing them both with the following code, however I want this code to return the exact cell address where the changes have been made. Also what exact change is there, the previous value of that cell and new value of that cell. Both sheets have same range as I am already copying the data of source_data with another function to the target_data sheet.
function updateSheet() {
const actualSheet = SpreadsheetApp.openById("1dtyM-Q3ej4q3NORbPAgTNlymcdLlBCXd4wbNbMMc8fE");
const copiedSheet = SpreadsheetApp.openById("14xvI7fUVxsMNMfAPWP-WUcoX2bo35S93CylC6RnjP7k");
let sourceValues = actualSheet.getSheetByName("admanager").getRange("A:D").getValues().filter(String)
let targetSheet = copiedSheet.getSheetByName("admanager")
let targetRange = targetSheet.getRange("A:D");
let targetValues = targetRange.getValues().filter(String)
let diff = targetValues.showDif(sourceValues)
Logger.log(diff);
// targetRange.clearContent();
// targetValues = (diff && diff.length) ? targetValues.concat(diff) : targetValues;
// targetSheet.getRange(15, 5, targetValues.length, targetValues[0].length).setValues(targetValues)
}
Array.prototype.showDif = function (array) {
let that = this;
return array.filter(function (r) {
return !that.some(function (x) {
return r.join() === x.join();
})
})
}
This code is giving the output in an array which returns the whole row where there is a difference.
output >>
1:54:04 PM Info [[a, bcd, lorem ipsumdh dchdbcbdj. bhjdbc.], [b, language, Lorem ipsum hbfhbfhdv]]
Kindly let me know how can I modify this code in such a way that it returns the exact Cell location and its value instead of an array with the whole row?

I believe your goal as follows.
You want to check the difference between the sheet of "admanager" in "actualSheet" sheet and the sheet of "admanager" in "copiedSheet" sheet using Google Apps Script.
You want to return the differences as the coordinates and each value of "actualSheet" sheet and "copiedSheet" sheet.
In this case, how about the following modified script?
Modified script:
function updateSheet() {
const a1Notation = "'admanager'!A:D"; // Please set the range.
const sourceSpreadsheetId = "###"; // Please set the Spreadsheet ID.
const targetSpreadsheetId = "###"; // Please set the Spreadsheet ID.
const [sourceValues, targetValues] = [sourceSpreadsheetId, targetSpreadsheetId].map(id => SpreadsheetApp.openById(id).getRange(a1Notation).getDisplayValues());
const diff = sourceValues.reduce((ar, r, i) => {
r.forEach((c, j) => {
if (c != targetValues[i][j]) ar.push({row: i + 1, col: j + 1, sourceValue: c, targetValue: targetValues[i][j]});
});
return ar;
}, []);
console.log(diff);
}
In this sample script, the cell values of 2 Spreadsheets of sourceSpreadsheetId and targetSpreadsheetId are compared, and when the cell values are different, the cell coordinate and each value are returned.
Result:
When above script is run, the following result is obtained.
[
{ row: 1, col: 1, sourceValue: 'a1', targetValue: 'updated' },
,
,
,
]
References:
map()
reduce()

Related

Google Sheets AppScript - Insert formula based on Column Value - Stop insert of formula when the same column value is blank

This link will lead you to the data
https://docs.google.com/spreadsheets/d/1kSo49Q1dH55cH9VFukCtyPSU_uIyeZ1QnzmgDDIIJNk/edit?usp=sharing
I am looking to accomplish the formulas in Column B & C with AppScript
I need the script to run only **when there is a value in Column G
**
Here is what has worked to insert the corret formula - How do I get it to stop until a new value is entered in Column G
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tabRFQ = ss.getSheetByName('RFQ DATA ENTRY TEST');
const lastRow = tabRFQ.getDataRange().getValues().length;
const dataB = tabRFQ.getRange(3, 2, lastRow - 2, 1).getValues();
const dataC = tabRFQ.getRange(3, 3, lastRow - 2, 1).getValues();
function insertFormulaB() {
dataB.forEach((row, i) => {
if (row[0] === '') {
let formula = `=IF(G${i + 2}=G${i + 3},B${i + 2},(B${i + 2}+1))`;
tabRFQ.getRange(i + 3, 2).setValue(formula);
}
}
)
};
function insertFormulaB() {
dataB.forEach((row, i) => {
if (row[0] === '') {
let formula = `=IF(G${i + 2}=G${i + 3},C${i + 2},1))`;
tabRFQ.getRange(i + 3, 3).setValue(formula);
}
}
)
}
Modification points:
In your script, setValue is used in the loop. In this case, the process cost becomes high. Ref (Author: me)
I thought that in your script, both the fomulas for columns "B" and "C" can be put by one function.
When these points are reflected in your script, how about the following modification?
Modified script:
function myFunction() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("RFQ DATA ENTRY");
const values = sheet.getRange("G4:G" + sheet.getLastRow()).getDisplayValues();
const { b, c } = values.reduce((o, [g], i) => {
if (g) {
o.b.push(`B${i + 4}`);
o.c.push(`C${i + 4}`);
}
return o;
}, { b: [], c: [] });
sheet.getRangeList(b).setFormulaR1C1("if(R[-1]C[5]=R[0]C[5],R[-1]C[0],R[-1]C[0]+1)");
sheet.getRangeList(c).setFormulaR1C1("if(R[-1]C[4]=R[0]C[4],R[-1]C[0]+1,1)");
}
When this script is run, the formulas are put to columns "B" and "C" when the value is existing in column "G".
And, the formulas are put using R1C1.
Note:
In this modified script, from your sample Spreadsheet, the formulas are put from row 4. Please be careful about this.
References:
reduce()
getRangeList(a1Notations)
setFormulaR1C1(formula)

fixing my #ERROR in google sheets with google apps script

I have a google app script program that looks throughout a spreadsheet and moves rows with empty values in column 5 into another sheet (block 3 of code).
However, I keep getting a #error upon transfer because in certain rows, some phone numbers have a + in front of them. I tried to turn the entire number into a string (block 2 of code with foreach loop), but the error remains.
function insertRecord() {
var sheet_2 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[1];
var sheet_1 = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var rows = sheet_1.getDataRange().getValues();
rows.forEach(function(element) {
iterator=element.length;
for (i=0;i<iterator+1;i++){
if (typeof element[i]=== Number){
element[i]=element[i].toString
}
}
});
rows
.filter(row => row[5] == '')
.map(row_w_empty5col => sheet_2.appendRow(row_w_empty5col));
}
When I saw your script, at for (i=0;i<iterator+1;i++){, I thought that i<iterator+1 will be i<iterator. And, toString of element[i]=element[i].toString will be toString(). And also, when appendRow is used in a loop, the process cost will be high. Ref
And, about moves rows with empty values in column 5 into another sheet, in your script, .filter(row => row[5] == '') is used. In this case, the column 6 is checked. In order to check the column 5 (column "E"), it is row[4].
From your current issue and your goal, in your situation, how about copying the number format? When this is reflected in your script, it becomes as follows.
Modified script:
function insertRecord() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet_2 = ss.getSheets()[1];
var sheet_1 = ss.getSheets()[0];
var range = sheet_1.getDataRange();
var rows = range.getValues();
var f = range.getNumberFormats();
var n = rows.reduce((ar, r, i) => {
if (r[4].toString() == "") ar.push(i);
return ar;
}, []);
var values = n.map(e => rows[e]);
var formats = n.map(e => f[e]);
sheet_2.getRange(sheet_2.getLastRow() + 1, 1, values.length, values[0].length).setValues(values).setNumberFormats(formats);
}
References:
getNumberFormats()
setNumberFormats(numberFormats)
Update: Changed my code a little bit, and it works now.
function emailEditor() {
var app=SpreadsheetApp;
var targetSht=app.getActiveSpreadsheet().getSheetByName('Linkedin Only');
var spreadsheet=app.getActiveSpreadsheet().getSheets().length;
for (i=0;i<spreadsheet;i++){
var shts=app.getActiveSpreadsheet().getSheets()[i];
var originalData=shts.getDataRange().getValues();
var newData=originalData.filter(function(item){
return item[5]===''; //user declared input here
});
var begin=targetSht.getDataRange().getValues().length;
targetSht.getRange(begin+1,1,newData.length,newData[0].length).setValues(newData);
}
}

App Script - Nested ForEach looping on first and second col data

I have a set of areas where multiple tests were ran at different locations. If a test doesn't pass, locations are retested on a different date.
Currently, below function that was posted as a solution by #TheWizEd to my question (Google App Script - Summarizes Result from Multiple Rows and Columns with Same Area Name) is not looping for dates and recording in the output, how can I add a second loop to achieve that or if there is a better way?
function Summary() {
try {
let sheet = SpreadsheetApp.openById(SpreadsheetID).getSheetByName(SheetName03);
let sheet04 = SpreadsheetApp.openById(SpreadsheetID).getSheetByName(SheetName04);
let values = sheet.getDataRange().getValues();
values.shift(); // remove the headers
// get unique sites
let areas = values.map( row => row[0] );
areas = [...new Set(areas)]
// find results for each area
let results = areas.map( area => [area,"Pass"]);
values.forEach( row => { let index = areas.findIndex( area => area === row[0] );
let tests = row.slice(2);
if( tests.indexOf("Fail") >= 0 ) {
results[index][1] = "Fail";
return;
}
else if( tests.indexOf( "" ) >= 0 ) {
results[index][1] = "Data Missing";
return;
}
}
);
sheet04.getRange(2,1,results.length,results[0].length).setValues(results);
}
catch(err) {
console.log(err);
}
}
This is the format of the data:
Present Output:
Intended Output:
Modification points:
In your script, the 1st column of the source sheet is checked. In order to achieve your goal, I thought that 1st and 2nd columns are required to be checked.
About the output values in your script, only 2 elements are included in each array of results values. In order to achieve your goal, 3 elements are required to be included in each array.
When these points are reflected in your script, how about the following modification?
Modified script:
function Summary() {
const SheetName03 = "Sheet1"; // Please set the sheet name of source sheet.
const SheetName04 = "Sheet2"; // Please set the sheet name of destination sheet.
const SpreadsheetID = "###"; // Please set your Spreadsheet ID.
const ss = SpreadsheetApp.openById(SpreadsheetID);
const sheet = ss.getSheetByName(SheetName03);
const sheet04 = ss.getSheetByName(SheetName04);
const [, ...values] = sheet.getDataRange().getValues();
const obj = values.reduce((m, [a, b, c, ...d]) => {
const k = `${a}###${b.toISOString()}`;
return m.set(k, m.has(k) ? [...m.get(k), ...d] : d);
}, new Map());
const results = [...obj].map(([k, v]) => {
const [a, b] = k.split("###");
return [a, new Date(b), v.includes("Fail") ? "Fail" : [...new Set(v)][0] == "Pass" ? "Pass" : "Data Missing"]
});
sheet04.getRange(2, 1, results.length, results[0].length).setValues(results);
}
When this script is run, the columns "A" and "B" of the source sheet are checked, and an array for putting to the destination sheet is created and the array is put to the destination sheet.
Note:
In this modified script, it supposes that the values of column "B" of the source sheet are the date object. Please be careful about this.
In this modified script, your showing sample Spreadsheet is used. So, when you change the sheet or your actual sheet is different from your showing sheet, this script might not be able to be used. Please be careful about this.
References:
reduce()
map()

Google Sheets / Apps Script - Add Duplicated Data Together - If Col A & Col B are the same

I am trying to combine rows of data where they match in Col A & Col B only.
In the example 1 - you can see 2 rows of Guitar D in Aisle 7 - Then the expected result of combining the duplicates together.
So combine the Qty`s together and list the latest date.
I am sorry I have no code to offer as an example as i cannot find something similar.
I do appreciate any help.
I believe your goal and situation as follows.
You want to achieve the image in your question.
Also, in your actual situation, the cells "A2:D" are used as the source range.
When the same values of "SKU" are existing and the values of "LOCATION" are different, the rows are not merged. Only when both values of "SKU" and "LOCATION" are the same, the rows are merged.
Pattern 1:
In this pattern, as your sample image, it supposes that the rows in the source sheet are sorted with "SKU", "LOCATION" and "DATE".
Sample script:
In this sample, it supposes that the source values are put in the sheet name of source and the output values are put to the sheet name of destination. So please modify them for your actual situation.
function myFnunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve values from the source sheet.
const srcSheet = ss.getSheetByName("source");
const srcValues = srcSheet.getRange("A2:D" + srcSheet.getLastRow()).getValues();
// 2. Create an array for putting to the destination sheet.
const dstValues = srcValues.reduce((o, r) => {
if (r[0] == o.temp[0] && r[1] == o.temp[1]) {
o.temp[2] += r[2];
o.temp[3] = r[3];
} else {
o.ar.push(o.temp.length == 0 ? r : o.temp);
o.temp = r;
}
return o;
}, {ar: [], temp: []});
// 3. Put the values to the destination sheet.
const dstSheet = ss.getSheetByName("destination");
dstSheet.getRange(dstSheet.getLastRow() + 1, 1, dstValues.ar.length, dstValues.ar[0].length).setValues(dstValues.ar);
}
Pattern 2:
In this pattern, as your sample image, it supposes that the rows in the source sheet are NOT sorted with "SKU", "LOCATION" and "DATE".
Sample script:
In this sample, it supposes that the source values are put in the sheet name of source and the output values are put to the sheet name of destination. So please modify them for your actual situation.
function myFnunction() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve values from the source sheet.
const srcSheet = ss.getSheetByName("source");
const srcValues = srcSheet.getRange("A2:D" + srcSheet.getLastRow()).getValues();
// 2. Create an array for putting to the destination sheet.
const dstObj = srcValues.reduce((o, r) => Object.assign(o, {[`${r[0]}_${r[1]}`]: (o[`${r[0]}_${r[1]}`] ? [o[`${r[0]}_${r[1]}`][0], o[`${r[0]}_${r[1]}`][1], o[`${r[0]}_${r[1]}`][2] + r[2], (o[`${r[0]}_${r[1]}`][3].getTime() < r[3].getTime() ? r[3] : o[`${r[0]}_${r[1]}`][3])] : r)}), {});
const dstValues = Object.values(dstObj);
// 3. Put the values to the destination sheet.
const dstSheet = ss.getSheetByName("destination");
dstSheet.getRange(dstSheet.getLastRow() + 1, 1, dstValues.length, dstValues[0].length).setValues(dstValues);
}
References:
getValues()
reduce()
Conditional (ternary) operator
setValues(values)

Is there a way to iterate down rows and across columns and compile that data into a cell?

I am trying to compile data from one sheet containing order information to another sheets cell based on each customers name. I'd like to take the title of the column as well as the count for the item(s) they have ordered. I tried to brute force it with a formula in Google Sheets but the formula messes up and stops giving the correct information so I figured using a scripts function would be better for what I am trying to do. I made a new sheet to test a script on but have little experience and can't seem to make any progress.
I'd like to get the title(top row) of each column and the count of the item(s) into the order column on sheet2 base on the matching names on both sheets. If anyone could help or provide some insight it would be greatly appreciated.
Here is the code I came up with:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var orders = ss.getSheetByName("Sheet2");
var data = ss.getSheetByName("Sheet1");
var names = orders.getRange("Sheet2!A2:A5");
var name = names.getValues();
var startRow = 1
var endRow = ss.getLastRow()
var getRange = ss.getDataRange();
var getRow = getRange.getRow();
var title = data.getRange("Sheet1!B1:J1");
var item = title.getValues();
var itemCell = data.getRange("Sheet1!B2").getValue();
var orderCell = orders.getRange(2,2).getValue()
Logger.log(itemCell)
if(itemCell>=0){
orders.getRange(2,2).setValue("item1 x " +itemCell)
}
currently this only has the desired effect on one cell and does not complete the row and repeat on next column.
Here is how I adjusted the code to try and fit a larger data set:
function myFunction() {
const srcSheetName = "Test Data";
const dstSheetName = "Order Changes";
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve source values.
const srcSheet = ss.getSheetByName(srcSheetName);
//I changed values 1,1 to change the values that were retreived
const [[, ...header], ...srcValues] = srcSheet.getRange(1, 1,
srcSheet.getLastRow(), srcSheet.getLastColumn()).getValues();
// 2. Create an object using the source values.
const srcObj = srcValues.reduce((o, [a, ...v]) => {
const temp = v.reduce((s, r, i) => {
if (r.toString() != "") s += `${header[i]} ${r}`;
return s;
}, "");
return Object.assign(o, {[a]: temp || ""});
}, {});
// 3. Retrieve the header column of destination values.
const dstSheet = ss.getSheetByName(dstSheetName);
//I changed values 2 or 1 to adjust the destination of values
const dstRange = dstSheet.getRange(2, 1, dstSheet.getLastRow() - 1);
const dstValues = dstRange.getValues();
// 4. Create the output values using the header column and the object.
const putValues = dstValues.map(([a]) => [srcObj[a] || ""]);
// 5. Put the values.
dstRange.offset(0, 1).setValues(putValues);
}
after making the changes and running the code the values would either not appear or appear in the wrong column with incorrect data.
Goal of function Update:
Match names in Sheet2!A with names in Sheet1!F
If a match is found combine header and value of cells in Sheet1!N1:BQ.
This should be from the same row of the matched name in Sheet1!F (example:
John Smith lm14 1, lm25 2, lm36 1)
Place combined data into Sheet2!C
Repeat for every name in Sheet2!A
header and cell value should not be combined if value < 0
I hope this helps to clarify any misunderstanding.
Here is are better example images:
I believe your goal as follows.
In your goal, the upper image in your question is the output you expect, and the cells "B2" should be item1 1 item3 1 item6 2 item9 3 when the lower input situation is used.
You want to achieve this using Google Apps Script.
In order to achieve above, I would like to propose the following flow.
Retrieve source values.
Create an object using the source values.
Retrieve the header column of destination values.
Create the output values using the header column and the object.
Put the values.
Sample script:
Please copy and paste the following script to the script editor of the Spreadsheet and set the source sheet name and destination sheet name, and run myFunction.
function myFunction() {
const srcSheetName = "Sheet1";
const dstSheetName = "Sheet2";
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve source values.
const srcSheet = ss.getSheetByName(srcSheetName);
const [[, ...header], ...srcValues] = srcSheet.getRange(1, 1, srcSheet.getLastRow(), srcSheet.getLastColumn()).getValues();
// 2. Create an object using the source values.
const srcObj = srcValues.reduce((o, [a, ...v]) => {
const temp = v.reduce((s, r, i) => {
if (r.toString() != "") s += `${header[i]} ${r}`;
return s;
}, "");
return Object.assign(o, {[a]: temp || ""});
}, {});
// 3. Retrieve the header column of destination values.
const dstSheet = ss.getSheetByName(dstSheetName);
const dstRange = dstSheet.getRange(2, 1, dstSheet.getLastRow() - 1);
const dstValues = dstRange.getValues();
// 4. Create the output values using the header column and the object.
const putValues = dstValues.map(([a]) => [srcObj[a] || ""]);
// 5. Put the values.
dstRange.offset(0, 1).setValues(putValues);
}
References:
getValues()
setValues(values)
reduce()
map()
Added 1:
About your current issue, the reason of your issue is that the ranges of source and destination are the different from the sample image in your question. My suggested answer is for the sample images in your initial question. When you changed the structure of the Spreadsheet, it is required to modify my suggested script. But from I changed values 1,1 to change the values that were retrieved and I changed values 2 or 1 to adjust the destination of values, I couldn't understand about your modified script. So as the additional script, I would like to modify my suggested answer for your updated question.
From your updated question and replyings, I understood that the source range and destination range are "N1:BQ" and "A52:C79", respectively. From this, please modify above sample script as follows.
From:
const [[, ...header], ...srcValues] = srcSheet.getRange(1, 1, srcSheet.getLastRow(), srcSheet.getLastColumn()).getValues();
To:
const [[, ...header], ...srcValues] = srcSheet.getRange("N1:BQ" + srcSheet.getLastRow()).getValues();
and
From:
dstRange.offset(0, 1).setValues(putValues);
To:
dstRange.offset(0, 2).setValues(putValues);
Added 2:
About your current issue, the reason of your issue is that the ranges of source and destination are the different from your 1st updated question in your question. My suggested answer is for the sample images in your 1st updated question. When you changed the structure of the Spreadsheet, it is required to modify my suggested script.
From your 2nd updated question, I understood that the source range and destination range are "F1:BQ" (the column "F" is the title and the columns "N1:BQ" are the values.) and "A2:C", respectively. From this, please modify above sample script as follows.
function myFunction() {
const srcSheetName = "Sheet1";
const dstSheetName = "Sheet2";
const ss = SpreadsheetApp.getActiveSpreadsheet();
// 1. Retrieve source values.
const srcSheet = ss.getSheetByName(srcSheetName);
const [[,,,,,,,, ...header], ...srcValues] = srcSheet.getRange("F1:BQ" + srcSheet.getLastRow()).getValues();
// 2. Create an object using the source values.
const srcObj = srcValues.reduce((o, [a,,,,,,,, ...v]) => {
const temp = v.reduce((s, r, i) => {
if (r.toString() != "") s += `${header[i]} ${r}`;
return s;
}, "");
return Object.assign(o, {[a]: temp || ""});
}, {});
// 3. Retrieve the header column of destination values.
const dstSheet = ss.getSheetByName(dstSheetName);
const dstRange = dstSheet.getRange(2, 1, dstSheet.getLastRow() - 1);
const dstValues = dstRange.getValues();
// 4. Create the output values using the header column and the object.
const putValues = dstValues.map(([a]) => [srcObj[a] || ""]);
console.log(srcObj)
// 5. Put the values.
dstRange.offset(0, 2).setValues(putValues);
}