Vlookup with split text by Google Appscript - google-apps-script

I am new to Appscript hence any help on below will be really appreciated.
My query is similar to the one posted in the below link,however, in that question the job is done by custom function and it is working bit slow and runs on every edit. In place of custom function I want to design an Appscript for the same which runs on the change of dropdown.
Link to similar question:
Google Appscript partial vlookup
Link of sample spreadsheet.
https://docs.google.com/spreadsheets/d/1vI22QCmixKe3aoWMLODTFzt7pNXIKO3pjXS4mT6GHT0/edit#gid=0
Any help on above will really be appreciated.

I believe your goal is as follows.
You want to run the script when the dropdown list of cell "A1" of "Sheet3" is changed to "Refresh".
You want to obtain the same result with your following script.
function MYLOOKUP(data1, data2) {
return data1
.map(([rollNo_Name, value]) => {
return (rollNo_Name !== '' && value === '') ?
data2.find(([rollNo,]) => rollNo_Name.split('*')[0] == rollNo)[1] :
''
});
}
In this case, how about using the OnEdit trigger of the simple trigger? When this is reflected in your sample Spreadsheet, the sample script is as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. When you run the script, please change the dropdown list of cell "A1" of "Sheet3" to "Refresh". By this, the script is run.
function onEdit(e) {
const sheetName = "Sheet3"; // This sheet name is from your Spreadsheet.
const { range, value, source } = e;
const sheet = range.getSheet();
if (sheet.getSheetName() != sheetName || range.getA1Notation() != "A1" || value != "Refresh") return;
const sheet1 = source.getSheetByName("Sheet1"); // This sheet name is from your Spreadsheet.
const sheet2 = source.getSheetByName("Sheet2"); // This sheet name is from your Spreadsheet.
const range1 = sheet1.getRange("A2:B" + sheet1.getLastRow());
const obj = sheet2.getRange("A2:B" + sheet2.getLastRow()).getValues().reduce((o, [a, b]) => (o[a] = b, o), {});
const values = range1.getValues().map(([a, b]) => {
const temp = obj[a.split("*")[0]];
return [temp && !b.toString() ? temp : null];
});
range1.offset(0, 2, values.length, 1).setValues(values);
range.setValue(null);
}
In this script, when the dropdown list of cell "A1" of "Sheet3" is changed to "Refresh", the script is run. And, the same result with your script is obtained. And, the value of the dropdown list is changed to null.
The result values are put to column "C" of "Sheet1". If you want to change this, please modify the above script.
Note:
In this script, when you directly run the function onEdit with the script editor, an error occurs. Please be careful about this.
In this script, in order to search the values, I used an object. By this, the process cost might be able to be reduced a little.
Updated: I reflected value in to be pulled only when there is no value in Column B.
References:
Simple Triggers
reduce()
map()

Related

App Script in Google Sheets - problem with data auto complete

I have two sheets. In one, the data of the manufactured devices is already entered. In the second, new warranty claims are added daily. To shorten the working time, I automatically copy the production data from the sheet Production to sheet Complaints. Currently I am solving this with VLOOKUP or INDEX function. But it works very slow when number of rows >20k.
Is it possible to write a script (using AppScripts) where after entering the ID number in the complaint sheet and selecting this cell, script will independently fill in the appropriate columns with data from 'Production'? (the order of the columns is different in both sheet)
Link to my sheet with example
I've tried all option with built-in function, I'm expecting to know and understand a possible solution using AppScript
In your situation, how about the following sample script?
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and save the script. And please confirm the sheet names again.
function myFunction() {
// Retrieve sheets.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const productionSheet = ss.getSheetByName("Production");
const complaintsSheet = ss.getSheetByName("Complaints");
// Retrieve values from sheets.
const [h1, ...v1] = productionSheet.getDataRange().getValues();
const range = complaintsSheet.getRange("A2:I" + complaintsSheet.getLastRow());
const [h2, ...v2] = range.getValues();
// Create an array for putting to Complaints sheet by converting columns.
const hconv = h2.map(h => h1.indexOf(h));
const obj = v1.reduce((o, r) => (o[r[0]] = hconv.map(i => i > -1 ? r[i] : null), o), {});
const len = Object.values(obj)[0].length;
const res = v2.map(([a]) => obj[a] || Array(len).fill(null));
// Put the created array to Complaints sheet.
range.offset(1, 0, res.length, res[0].length).setValues(res);
}
When this script is run, the values are retrieved from both sheets. And, an array is created for putting to "Complaints" sheet by checking the ID of column "A" and converting the columns. And, the array is put to "Complaints" sheet.
Note:
In this sample, I tested it using your sample Spreadsheet. If you change the spreadsheet, this script might not be able to be used. Please be careful about this.
I thought that in this case, the custom function might be able to be used. But, from your question, if the data is large, the custom function cannot be used. So, I proposed the above script.
In this sample, in order to convert the columns, the header titles are used. So, if you change the header titles, please be careful about this.
As another approach, if you want to put the value to the row when you put a value to the column "A" of "Complaints", how about the following script? In this script, the script is automatically run by a simple trigger of OnEdit. So, in this case, when you put a value to the column "A" of "Complaints" sheet, this script is automatically run, and the value is put into the edited row. Please select one of 2 scripts for your actual situation. As an important point, when you use this, please don't directly run this function because of no event object. Please be careful about this.
function onEdit(e) {
const { range, source } = e;
const complaintsSheet = range.getSheet();
if (complaintsSheet.getSheetName() != "Complaints" || range.columnStart != 1 || range.rowStart <= 2) return;
const value = range.getValue();
const productionSheet = source.getSheetByName("Production");
const [h1, ...v1] = productionSheet.getDataRange().getValues();
const [h2] = complaintsSheet.getRange("A2:I2").getValues();
const hconv = h2.map(h => h1.indexOf(h));
const obj = v1.reduce((o, r) => (o[r[0]] = hconv.map(i => i > -1 ? r[i] : null), o), {});
const len = Object.values(obj)[0].length;
const res = [obj[value] || Array(len).fill(null)];
range.offset(0, 0, res.length, res[0].length).setValues(res);
}
References:
reduce()
map()

Google sheet script to update cell value in another sheet

I'm trying to use a code I found in another topic but so far I failed. I'm trying to find the corresponding row and update the last column in another google spreadsheet after updating the first column of another spreadsheet.
When the user selects "ready" in ColC of spreadsheet X, I need to look up the ID value in ColB on another sheet (Y). Then I need to access spreadsheet Y and find the row that contains that same ID. Access the last column or columnC (3) and change the cell value to "ready".
Here is what I have so far but I get the error:
TypeError: Cannot read properties of undefined (reading 'range')
Can anyone help me fix this?
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = ss.getActiveSheet();
if( s.getName() == "Studio"){
if ( (e.range.getColumn() == 2.0) && (e.range.getValue() == "ready") ){
var nextCell = e.range.offset(0, -1);
var buybackId = nextCell.getValue();
var ss2 = SpreadsheetApp.openById('1bbNuH_GXbmNwMHk7Kjo0zAhD-QGwQqiva8_HJc3mgFY');
var sheet = ss2.getSheetByName("Status");
var data = sheet.getDataRange().getValues();
for(var i = 0; i<data.length;i++){
if(data[i][1] == buybackId){
sheet.getRange((i+1), 3).setValue("ready");
}
}
}
}
}
The table structure looks like this:
In the 1st section of your question, you say I'm trying to find the corresponding row and update the last column in another google spreadsheet after updating the first column of another spreadsheet.. But, from your 2nd section, it seems that you wanted to run the script by editing column "B" like ready. By the way, about When the user selects "ready" in ColC of spreadsheet X,, your selects is to edit?
So, when column "B" in "Studio" sheet on the active Spreadsheet is edited to like ready, you want to update column "C" of "Status" sheet in another Spreadsheet by checking the ID of column "B".
If my understanding is correct, how about the following modification?
Modification points:
In order to use SpreadsheetApp.openById, it is required to use the installable OnEdit trigger.
About your current error of TypeError: Cannot read properties of undefined (reading 'range'), I'm worried that you might have directly run the function onEdit with the script editor. In that case, such an error occurs because of no event object.
When these points are reflected in your script, how about the following modification?
Modified script:
Please copy and paste the following script to the script editor of Spreadsheet. And, please install OnEdit trigger to installedOnEdit. When you use this script, please edit the column "B" of "Studio" sheet of the active Spreadsheet. By this, the script is run. And, please set your destination Spreadsheet ID to const ss = SpreadsheetApp.openById('###');.
function installedOnEdit(e) {
const { range } = e;
const sheet = range.getSheet();
if (sheet.getSheetName() != "Studio" || range.columnStart != 2) return;
const [id, status] = range.offset(0, -1, 1, 2).getDisplayValues()[0];
const ss = SpreadsheetApp.openById('###'); // Please set the destination Spreasheet ID.
const dstSheet = ss.getSheetByName("Status");
const r = dstSheet.getRange("B2:B" + dstSheet.getLastRow()).createTextFinder(id).matchEntireCell(true).findNext();
if (!r) return;
r.offset(0, 1).setValue(status);
}
When this script is run by the installable OnEdit trigger, the above goal can be achieved.
Note:
If you want to copy all values from the source sheet to the destination sheet, how about the following sample script? In this case, you can directly run the script with the script editor.
function sample() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Studio");
const obj = sheet.getRange("A2:B" + sheet.getLastRow()).getDisplayValues().reduce((o, [a, b]) => (o[a] = b, o), {});
const ss = SpreadsheetApp.openById('###'); // Please set the destination Spreasheet ID.
const dstSheet = ss.getSheetByName("Status");
const dstRange = dstSheet.getRange("B2:C" + dstSheet.getLastRow());
const dstValues = dstRange.getValues().map(([b, c]) => [obj[b] || c]);
dstRange.offset(0, 1, dstValues.length, 1).setValues(dstValues);
}
Note:
About the 1st script, when you directly run the script, an error like TypeError: Cannot destructure property 'range' of 'e' as it is undefined. occurs. Please be careful about this. The 1st script is automatically run with the installable OnEdit trigger.
By the way, when you use the 1st script, please rename your onEdit function name. Because when you didn't rename it, when the cell is edited, both functions installedOnEdit and onEdit are run. Please be careful about this.
Reference:
Installable Triggers

How can I get a different cell to activate depending on the contents of an edit?

In Google Apps Script for Google Sheets, I want to use a barcode scanner to input new data into a cell. If this new data matches what I am expecting, I want another cell to then be activated so I can continue to input data. This is being used to create an inventory, and the initial data being input (and what is being checked by the code) will be the name of the person using the scanner at that time. Here's what I have so far:
var ss = SpreadsheetApp.getActive()
var sheet1 = SpreadsheetApp.getActive().getSheetByName("Sheet1")
//Set active cell to A2 (cell below Name header) when file is opened.
function onOpen() {
sheet1.getRange('A2').activate();
}
function onEdit(e) {
if (e.range.getA1Notation() === 'A2') {
var nameval = sheet1.getActiveCell.getValue();
if (nameval == 'Alpha' || nameval == 'Beta') {
sheet1.getRange('D7').activate();
}
else {
sheet1.getRange('F1').activate();
}}}
Unfortunately this does not seem to do anything - The value in the cell is accepted but the new cell did not activate when either of the activation cases were input.
Modification points:
In your script, sheet1 is not declaread.
getActiveCell of sheet1.getActiveCell.getValue() is not run as the method.
I thought that nameval == 'Alpha' || nameval == 'Beta' might be written by includes.
When these points are reflected in your script, it becomes as follows.
Modified script:
function onEdit(e) {
var range = e.range;
var sheet1 = range.getSheet();
if (range.getA1Notation() === 'A2') {
var active = ['Alpha', 'Beta'].includes(range.getValue()) ? "D7" : "F1";
sheet1.getRange(active).activate();
}
}
In this modified script, when a value of 'Alpha' or 'Beta' is put to the cell "A2", the cell "D7" is activated. When a value except for 'Alpha' and 'Beta' is put to the cell "A2", the cell "F1" is activated.
Note:
This script is automatically run by the OnEdit simple trigger. So, when you directly run this script with the script editor, an error occurs. Please be careful about this.
References:
Simple Triggers
includes()

Protect row according to the data in cell (Yes/No)

I have a sheet with a table. When me or one of my editors changes the data of the cell in column F (so that it is not blank), I need the row to become protected for everybody except me (owner).
I have seen several similar questions here, but no one gives a working script... I will appreciate any help.
I believe your current situation and your goal as follows.
Your Spreadsheet is shared with some users.
When the user is edited the dropdown list of the column "F", when the value is not empty, you want to protect the row of columns "A" to "F".
After the row was protected, you want to edit by only the owner.
You want to achieve this using Google Apps Script.
In this case, I would like to propose to run the script with the installable OnEdit trigger. The sample script is as follows.
Sample script:
Please copy and paste the following script to the script editor of Spreadsheet and please install the OnEdit trigger to the function of myFunction.
function myFunction(e) {
const sheetName = "Sheet1"; // Please set your sheet name.
const range = e.range;
const sheet = range.getSheet();
const value = range.getValue();
const row = range.getRow();
if (sheet.getSheetName() != sheetName || range.getColumn() != 6 || row == 1 || value == "") return;
const p = sheet.getRange(`A${row}:F${row}`).protect();
const owner = Session.getActiveUser().getEmail();
p.getEditors().forEach(f => {
const email = f.getEmail();
if (email != owner) p.removeEditor(email);
});
}
In this script, it supposes that the dropdown list is put to the cells "F2:F" from your sample image. If you want to change the range, please modify above script.
When you use this script, by an user who is not owner, please edit the dropdown list of the column "F" of "Sheet1". By this, when the value of dropdown list is not empty, the script works. And, the row is protected. The editor is only the owner.
References:
Installable Triggers
protect() of Class Range
Class Protection
Added:
About your following 2nd question in comment,
thank you very much!! It works, and I know now something about triggers - a new thing for me. Is it possible to extend this function for 3 sheets with the same structure (within one spreadsheet)? Sheet1, Sheet2,Sheet 3...
When you want to use above script for the specific sheets like "Sheet1", "Sheet2", "Sheet3", how about the following sample script?
Sample script:
function myFunction(e) {
const sheetNames = ["Sheet1", "Sheet2", "Sheet3"]; // Please set the sheet names you want to run the script.
const range = e.range;
const sheet = range.getSheet();
const value = range.getValue();
const row = range.getRow();
if (!sheetNames.includes(sheet.getSheetName()) || range.getColumn() != 6 || row == 1 || value == "") return;
const p = sheet.getRange(`A${row}:F${row}`).protect();
const owner = Session.getActiveUser().getEmail();
p.getEditors().forEach(f => {
const email = f.getEmail();
if (email != owner) p.removeEditor(email);
});
}

Google Sheet Formatting

I have bill of materials report where the data is double stacked when exported from database.
Original data:
Sheet is very large, but I want to format the data like this
Still a noob with appscript, but how would i script this to do that?
Thanks in advance.
I believe your goal as follows.
You want to achieve the following conversion on Google Spreadsheet using Google Apps Script.
From: Source sheet
To: Destination sheet
Flow:
In this case, I would like to propose the following flow of the sample script.
Retrieve values from the source sheet.
Create values for putting to the destination sheet an 2 dimensional array.
In this case, in the following sample script, the value of column "A" is checked. When the value of column "A" is not empty, the values of columns "A" to "D" is set to temp array. And when the next row of it has no value of column "A", the value of row is added to temp. By this, 0,123-00000-00,Test Assembly,,ABC Inc,abc123 is created. When the next row has no value of column "A", the value is added to ar by adding 4 empty values. By this, ,,,,XYZ Inc,abc234 is created. By this flow, the destination values are created.
Put the created value to the destination sheet.
Sample script:
When you use this script, please copy and paste the following script to the container-bound script of the Spreadsheet. And please set the source sheet name and destination sheet name.
function myFunction() {
const srcSheetName = "Sheet1"; // Please set the source sheet name.
const dstSheetName = "Sheet2"; // Please set the destination sheet name.
// 1. Retrieve values from the source sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [header1, [,subHead1, subHead2], ...srcValues] = ss.getSheetByName(srcSheetName).getDataRange().getValues();
// 2. Create values for putting to the destination sheet an 2 dimensional array.
let temp = [];
const dstValues = srcValues.reduce((ar, [a,b,c,d]) => {
if (a.toString() != "") {
if (temp.length == 4) ar.push(temp.concat("",""));
temp = [].concat(a,b,c,d);
} else {
ar.push(temp.length == 4 ? temp = temp.concat(b,c) : Array(4).fill("").concat(b,c));
}
return ar;
}, [header1.concat(subHead1, subHead2)]);
// 3. Put the created value to the destination sheet.
ss.getSheetByName(dstSheetName).getRange(1, 1, dstValues.length, dstValues[0].length).setValues(dstValues);
}
When you run the function of myFunction(), the values are put to the destination sheet.
Note:
Please use this script with enabling V8.
If you want to use the script as the custom function, you can also use the following script. When you use this script, for example, when your sample input sheet is used, please put a custom function of =CUSTOMSAMPLE(A1:D12).
function CUSTOMSAMPLE(values) {
// 1. Retrieve values from the source sheet.
const [header1, [,subHead1, subHead2], ...srcValues] = values;
// 2. Create values for putting to the destination sheet an 2 dimensional array.
let temp = [];
const dstValues = srcValues.reduce((ar, [a,b,c,d]) => {
if (a.toString() != "") {
if (temp.length == 4) ar.push(temp.concat("",""));
temp = [].concat(a,b,c,d);
} else {
ar.push(temp.length == 4 ? temp = temp.concat(b,c) : Array(4).fill("").concat(b,c));
}
return ar;
}, [header1.concat(subHead1, subHead2)]);
// 3. Put the created value to the destination sheet.
return dstValues;
}
This sample script is for your sample input and output values in your question. When the cell structure in your actual sheet is different from them, the script might not be able to be used. Please be careful this.
References:
getValues()
reduce()
setValues(values)