I have a Google Sheet (with header row) and I need to create a uniqueID for each new row as it's added.
I can create the ID like this (an AppsScript)
function generate() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const row = sheet.getLastRow();
const randomUnique = `${Math.random().toString(36).slice(2)}-${Date.now()}`
}
and I call the function like this
=ArrayFormula(IF(ISBLANK(A2:A),,generate()))
Simply if there is a value in Column A create the ID
BUT when I add the final line to the generate() function I always get an error
This is the final line
sheet.getRange(row,2 ).setValue(randomUnique);
This is the error
You do not have permission to call setValue()
To summarise, when row X column A has a value I need to have the ID created and inserted into row X Column B
Once set that ID must not change
How do I correct the function / formulas - thanks in advance
You need to return a value.
function generate() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const activeRange = sheet.getActiveRange();
//stop recalc, if needed:
//if(activeRange.getValue() !== '') return activeRange.getValue();
const activeRow = activeRange().getRow();
const lrow = sheet.getLastRow();
const randomUnique = () => `${Math.random().toString(36).slice(2)}-${Date.now()}`
return randomUnique();
//or return Array(lrow-activeRow).fill("").map(_ => [randomUnique()])
}
Unfortunately, in the current stage, setValue cannot be used with the custom function. And also, when the values are put with the custom function, the values are changed by the recalculation. So, in this case, in order to achieve your goal of To summarise, when row X column A has a value I need to have the ID created and inserted into row X Column B and Once set that ID must not change, how about using a Google Apps Script as not the custom function? The sample script is as follows.
Sample script:
function mYFunction() {
const sheetName = "Sheet1"; // Please set your sheet name.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const lastRow = sheet.getLastRow();
const values = sheet.getRange("A2:B" + lastRow).getDisplayValues().map(([a, b]) => [a && b ? b : a && !b ? `${Math.random().toString(36).slice(2)}-${Date.now()}` : ""]);
sheet.getRange("B2:B" + lastRow).setValues(values);
}
When this script is run, the columns "A" and "B" are checked. When there are the value of column "A" and no value of column "B", ${Math.random().toString(36).slice(2)}-${Date.now()} is used. When there are the value of columne "A" and "B", the value of column "B" is not changed.
References:
Custom Functions in Google Sheets
map()
Related
I am trying to compare values on 2 different columns values to ensure that they exist(on Sheet 1)
Once the script is aware that they exist I need to access sheet1's column for quantity and add that value to sheet 2's quantity column. The problem is I am unsure of how to just get the location/index of the foreach loop and offset a setValue to another column without setting the value to the entire column(I dont want to do that if the product name of column A does not exist in Sheet1)
Here is the code example of how i am trying to do it
I have included it in a pastebin because I could not figure out how to format the code to paste ( sorry i'm super new at this!)
<https://pastebin.com/EKB2n9kA>
Sheet1 incoming data https://drive.google.com/file/d/1eLNeOZZbdeCDfMMImksVRnBXwKxpHIO_/view?usp=sharing
Sheet2 'base' data to add quantity values to https://drive.google.com/file/d/1h26H9eQgZapd2Y0LVamhRPYme-8LmVF0/view?usp=sharing
example of expected/wanted results https://drive.google.com/file/d/1-0ozD5PrbIq-otG4j7kAyLufQFjDR5Hi/view?usp=sharing
I have also attached 3 different reference photos
Sheet 1 is 'incoming' data to read
Sheet 2 is our 'base' data and where Sheet1's quantity column needs to be added to
the third screenshot is the expected result(having the script skip over rows that do not contain matching data but still being able to get the quantity value based on the index/location the value was found)
Any insight on how to achieve this would be sincerely appreciated
I have tried pushing the results into an empty array but it does not seem to give much useful info the way I am doing it.
I have also tried just getting an offset range (getRange("G2:G").offset(0,3).setValues() to set the results but it sets the value of the entire column instead of only where the values match for each column being compared.
From your <https://pastebin.com/EKB2n9kA>, it seems that your current script is as follows.
function compareCol() {
var ss = SpreadsheetApp.getActiveSpreadSheet();
var s1 = ss.getSheetByName("Sheet1");
var s2 = ss.getSheetByName("Sheet2");
var datA = s1.getRange("A2:A").getValues();
var datB = s2.getRange("A2:A").getValues();
var quantityA = s1.getRange("C2:C").getValues();
var quantityB = s2.getRange("D2:D");
let matchingCol = [];
for (var i in datA) {
for (var x in datB[i]) {
if (datA[i][0] === datB[x][0]) {
}
}
}
}
Modification points:
In your script, the if statement in the loop has no script. And, quantityA, quantityB, and matchingCol are not used.
When your script is modified for achieving your goal, how about the following modification?
Modified script:
function compareCol() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s1 = ss.getSheetByName("Sheet1");
var s2 = ss.getSheetByName("Sheet2");
var datA = s1.getRange("A2:C" + s1.getLastRow()).getValues().reduce((o, [a, , c]) => (o[a] = c, o), {});
var datB = s2.getRange("A2:D" + s2.getLastRow()).getValues();
var matchingCol = datB.map(([a, , , d]) => [datA[a] || d]);
s2.getRange(2, 4, matchingCol.length).setValues(matchingCol);
}
In this modification, when the value of column "A" in "Sheet2" is not found from column "A" of "Sheet1", the value of column "D" of "Sheet2" is used. If you want to remove this, please modify it as follows.
From
var matchingCol = datB.map(([a, , , d]) => [datA[a] || d]);
To
var matchingCol = datB.map(([a]) => [datA[a] || null]);
References:
reduce()
map()
Hopefully this will help
function comparingColumns() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const osh = ss.getSheetByName("Sheet1");
const [h,...vs] = sh.getDataRange().getValues();
vs.forEach((r, i) => {
//comparing column A to Column D
if (r[0] == r[3]) {
//getting column G of same row in sheet1
osh.getRange(i + 2, 7).setValue(r[6]);
}
})
}
I'm trying to make a script that adds the value of one cell to another one before I clear the first cell. Unfortunately, all my attempts to add the second one have given errors. This is my latest one: I get a NUM error here. what do I do?1
You can use onEdit simple trigger, which will give you access to oldValue of cell which was edited and you can use that value to add it with column B.
Based on your this problem statement, try this sample script:-
function onEdit(e)
{
const range = e.range;
const sheet = range.getSheet();
const row = range.getRow();
const column = range.getColumn();
var oldValue = e.oldValue;
if(column === 1)
{
const colB = sheet.getRange(row,column+1); // Getting Column B value
var colB_Value = colB.getValue();
oldValue = isNaN(Number(oldValue)) ? 0 : Number(oldValue)
colB.setValue(oldValue + colB_Value) // adding old value of column A with B and setting it on Column B
}
}
Reference:
OnEdit()
Event Objects
isNaN
I'm using Google Forms to collect a data. So, there was one question in the Google Form that I considered as a primary key when using the VLOOKUP function. However, there is a possibility that some data will collect zero (0) for the answer since they do not have an official ID number yet. I'm wondering if there is a way for me to automatically add or update a value when the value is zero (0). I'm thinking of using an temporary ID which letters and numbers. Ex: ID000001
I believe your goal is as follows.
You want to convert 0 to ID000001. And, when the multiple 0 values are existing, you want to convert to ID000001, ID000002,,,.
In this case, how about the following sample script?
Sample script:
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const range = sheet.getRange("A2:A" + sheet.getLastRow());
let c = 1;
const values = range.getValues().map(([a]) => [a === 0 ? `ID${(c++).toString().padStart(6, '0')}` : a]);
range.setValues(values);
}
In this script, it supposes that the column of "STUDENT ID" is the column "A" because I cannot know the column information from your sample image.
References:
map()
padStart()
Added:
From your following additional question,
Wait. If I run again the script, the output is starting again with ID000001. Is there a way for me to just continue the number?
In this case, how about using PropertiesServices as follows?
Sample script:
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const range = sheet.getRange("A2:A" + sheet.getLastRow());
const p = PropertiesService.getScriptProperties();
const count = p.getProperty("count");
let c = count ? Number(count) : 1; // If you want to set the initial count, please modify 1 to other.
const values = range.getValues().map(([a]) => [a === 0 ? `ID${(c++).toString().padStart(6, '0')}` : a]);
range.setValues(values);
p.setProperty("count", c);
}
// When you want to reset the count, please run this function.
function reset() {
PropertiesService.getScriptProperties().deleteProperty("count");
}
For example, when all ID###### values are updated every run, you can also use the following sample script.
function myFunction() {
const sheetName = "Sheet1"; // Please set the sheet name.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const range = sheet.getRange("A2:A" + sheet.getLastRow());
range.createTextFinder("^ID[\\d]+$").matchEntireCell(true).useRegularExpression(true).replaceAllWith(0);
let c = 1;
const values = range.getValues().map(([a]) => [a === 0 ? `ID${(c++).toString().padStart(6, '0')}` : a]);
range.setValues(values);
}
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);
}
I am kinda new in GAS programming and I need help in making a script to rename multiple name ranges according to cell values
I am currently using a simple script in which I am creating a name range for a selected column, and naming the range according to a cell value.
function Group_A() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var first = ss.getSheetByName("DATA VALIDATION");
var range = first.getRange("A1");
var cell = range.getCell(1,1);
var sheet = cell.getValue();
ss.setNamedRange(sheet, ss.getRange('A2:A'));
}
The name of the range will be from the first cell of the given column. The range of the name range will be from the 2nd row to the end row for the given columns.
I need help in running this code on a loop for nNum of columns as there are more than 20 name ranges to make.
Thanks in advance
I believe your goal as follows.
The 1st row is the name for using the named range.
You want to rename the named range with the new name. The range is after the row 2 in the column.
You want to select the columns on the sheet DATA VALIDATION.
You want to rename the named ranges to each column of the selected columns by giving the name retrieved from the 1st row.
For this, how about this answer?
Flow:
The flow of this sample script is as follows.
Retrieve sheet.
Retrieve the 1st row values.
Retrieve the named ranges in the sheet and create an object.
Retrieve the selection.
Retrieve each range and rename the existing named range using the name.
Sample script 1:
In this sample script, the existing named range is renamed for the selected columns. Before you run the script, please select columns in the sheet DATA VALIDATION. And then, please run the script. By this, the named range is set for each column using the name retrieved by the 1st row.
function Group_A() {
// 1. Retrueve sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("DATA VALIDATION");
// 2. Retrieve the 1st row values.
const headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
// 3. Retrieve the named ranges in the sheet and create an object.
const namedRangesObj = sheet.getNamedRanges().reduce((o, e) => Object.assign(o, {[e.getRange().getColumn()]: e}), {});
// 4. Retrieve the selection.
const selection = sheet.getSelection();
// 5. Retrieve each range and rename the existing named range using the name.
selection
.getActiveRangeList()
.getRanges()
.forEach(r => {
const col = r.getColumn();
const name = headerRow[col - 1];
if (!name) throw new Error("No headef value.");
if (col in namedRangesObj) {
namedRangesObj[col].setName(name);
}
});
}
Sample script 2:
In this sample script, the existing named range is renamed for the selected columns. And also, when the selected column is not the named range, it is set as new named range using the name retrieved from the 1st row. Before you run the script, please select columns in the sheet DATA VALIDATION. And then, please run the script. By this, the named range is set for each column using the name retrieved by the 1st row.
function Group_A() {
// Ref: https://stackoverflow.com/a/21231012/7108653
const columnToLetter = column => {
let temp,
letter = "";
while (column > 0) {
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
};
// 1. Retrueve sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("DATA VALIDATION");
// 2. Retrieve the 1st row values.
const headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
// 3. Retrieve the named ranges in the sheet and create an object.
const namedRangesObj = sheet.getNamedRanges().reduce((o, e) => Object.assign(o, {[e.getRange().getColumn()]: e}), {});
// 4. Retrieve the selection.
const selection = sheet.getSelection();
// 5. Retrieve each range and rename and set the named range using the name.
selection
.getActiveRangeList()
.getRanges()
.forEach(r => {
const col = r.getColumn();
const name = headerRow[col - 1];
if (!name) throw new Error("No headef value.");
if (col in namedRangesObj) {
namedRangesObj[col].setName(name);
} else {
const colLetter = columnToLetter(col);
ss.setNamedRange(name, sheet.getRange(`${colLetter}2:${colLetter}`));
}
});
}
Note:
In these sample scripts, it supposes that each named range is one column. Please be careful this.
Please use this script with enabling V8.
References:
getSelection()
Class Selection