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)
Related
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()
Let's say I am getting the range A2:B2:
sheet.getRange("A2:B2").getValues();
But I added a row under A1:B1, so now my values are in A3:B3
Is it possible for Apps Script to dynamically catch that my values are now on another range ?
If not, any alternative ideas on how I can dynamically get the range of moving rows ?
I believe your goal is as follows.
You want to know whether the values of a range of "A2:B2" is moved.
In this case, how about using the named range, OnChange trigger, and PropertiesService? I thought that when those are used, your goal might be able to be achieved. When the sample script is prepared, it becomes as follows.
Usage:
1. Create a named range.
As a sample, please create a named range to the cells "A2:B2" as "sampleNamedRange1". Ref
2. Prepare sample script.
Please copy and paste the following script to the script editor of Spreadsheet. And, please install OnChange trigger to the function installedOnChange.
function installedOnChange(e) {
var originalRange = "A2:B2";
var nameOfNamedRange = "sampleNamedRange1";
if (!["INSERT_ROW", "REMOVE_ROW"].includes(e.changeType)) return;
var p = PropertiesService.getScriptProperties();
var pv = p.getProperty("range");
if (!pv) pv = originalRange;
var range = e.source.getRangeByName(nameOfNamedRange);
var a1Notation = range.getA1Notation();
if (a1Notation != pv) {
p.setProperty("range", a1Notation);
Browser.msgBox(`Range was changed from ${pv} to "${a1Notation}". Original range is "${originalRange}".`);
}
// var values = range.getValues(); // The values are not changed because of the named range.
}
3. Testing.
In this case, please do I added a row under A1:B1. By this, the script is automatically run by the OnChange trigger. And, you can see the dialog. You can see the demonstration as shown in the following image.
Note:
This is a simple sample script. So, please modify this for your actual situation.
References:
Installable Triggers
getRangeByName(name)
Properties Service
Added:
From the discussions, I understood that you wanted to retrieve the named range of the specific sheet using the name of the named range. In this case, the sample script is as follows.
When the sheet name is used
const range = SpreadsheetApp.getActive().getSheetByName("sheetName").getRange("nameOfNamedRange");
or
const range = SpreadsheetApp.getActive().getSheetByName("sheetName").getNamedRanges().find(n => n.getName() == "nameOfNamedRange").getRange();
When the active sheet is used
const range = SpreadsheetApp.getActiveSheet().getRange("nameOfNamedRange");
or
const range = SpreadsheetApp.getActiveSheet().getNamedRanges().find(n => n.getName() == "nameOfNamedRange").getRange();
This is possible through DeveloperMetadata. Metadata can be set to ranges or sheets and whenever such data(ranges or sheets) are moved, the associated metadata moves along with it as well. Unfortunately, this metadata cannot be set to arbitrary ranges, but only to single column or single row. For eg, with A2:B2, We have to set the metadata to the entirety of column A, column B and Row 2. However, once set, apps script is no more needed. Google sheets automatically keeps track of the movements of such data.
Sample script:
const setDevMetadata_ = (sheetName = 'Sheet1', rng = '2:2', key = 'a2b2') => {
SpreadsheetApp.getActive()
.getSheetByName(sheetName)
.getRange(rng)
.addDeveloperMetadata(key);
};
/**
* #description Set metadata to a specific range
* Unfortunately, this metadata cannot be set to arbitrary ranges, but only to single column or single row.
* For eg, with `A2:B2`, We have to set the metadata to the entirety of column A, column B and Row 2.
* #see https://stackoverflow.com/a/73376887
*/
const setDevMetadataToA2B2 = () => {
['2:2', 'A:A', 'B:B'].forEach((rng) => setDevMetadata_(undefined, rng));
};
/**
* #description Get a range with specific developer metadata key
*/
const getRangeWithKey = (sheetName = 'Sheet1', key = 'a2b2') => {
const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName),
devFinder = sheet.createDeveloperMetadataFinder(),
[rows, columns] = ['Row', 'Column'].map((rc) =>
devFinder
.withKey(key)
.withLocationType(
SpreadsheetApp.DeveloperMetadataLocationType[rc.toUpperCase()]
)
.find()
.map((devmetadata) =>
devmetadata.getLocation()[`get${rc}`]()[`get${rc}`]()
)
);
console.log({ rows, columns });
const rng = sheet.getRange(
rows[0],
columns[0],
rows[1] ? rows[1] - rows[0] + 1 : 1,
columns[1] ? columns[1] - columns[0] + 1 : 1
);
console.log(rng.getA1Notation());
return rng;
};
I have 25 tabs in a Google Spreadsheet, but this number slowly grows as more data is added. The importrange function that we are provided by default requires the specification of which sheets to import data from using something like what was outlined here: Combining multiple spreadsheets in one using IMPORTRANGE. However, is there a way (using a default function or the Apps Script) to do this for all tabs in the Google Spreadsheet, without specifying each one individually?
I believe your goal is as follows.
You want to retrieve the values from all sheets in a Google Spreadsheet and want to put the values to a sheet of the destination Spreadsheet. In this case, you want to set a range you want to retrieve.
You want to achieve this using Google Apps Script.
Sample script:
Please copy and paste the following script to the script editor of Google Apps Script, and set the variables. And, run the function.
function myFunction() {
const range = "A1:E3"; // Please set the range you want to retrieve.
const srcSpreadsheetId = "###"; // Please set source Spreadsheet ID.
const dstSpreadsheetId = "###"; // Please set destination Spreadsheet ID.
const dstSheetName = "Sheet1"; // Please set destination sheet name.
const values = SpreadsheetApp.openById(srcSpreadsheetId).getSheets().flatMap(s => s.getRange(range).getValues());
const dstSheet = SpreadsheetApp.openById(dstSpreadsheetId).getSheetByName(dstSheetName);
dstSheet.clearContents().getRange(1, 1, values.length, values[0].length).setValues(values);
}
When this script is run, the values are retrieved from the specific range of all sheets from the source Spreadsheet, and the values are put to the destination sheet.
In the above script, the retrieved values to "A1" of the destination sheet. If you want to append the values, please modify as follows.
From
dstSheet.clearContents().getRange(1, 1, values.length, values[0].length).setValues(values);
To
dstSheet.getRange(dstSheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
Unfortunately, I'm not sure whether I could correctly understand your question. So, for example, if you want to retrieve the values from all sheets except for multiple sheets, and you want to put the retrieved values to the specification sheet in the same Spreadsheet, how about the following sample script? In this case, you can use this script as a custom function. So, when you use this script, please put a custom function of =SAMPLE("A1:E3", "Sheet2,Sheet3") to a cell. In this sample arguments, the values are retrieved from the range of "A1:E3" of all sheets except for "Sheet2" and "Sheet3".
function SAMPLE(range, exclude) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const excludeSheets = [ss.getActiveSheet().getSheetName(), ...exclude.split(",").map(e => e.trim())];
return ss.getSheets().flatMap(s => {
if (!excludeSheets.includes(s.getSheetName())) {
return s.getRange(range).getValues();
}
return [];
});
}
As another pattern, if you want to retrieve the values from all sheets in a source Spreadsheet to a sheet of destination Spreadsheet, how about the following sample script? In this sample, please copy and paste the following script to the script editor of the destination Spreadsheet. And, please set the variables of source Spreadsheet ID and range you want. When this script is run, a formula is put to the cell "A1" of the destination sheet. The formula is from this thread. By this, you can see the values from all sheets in the source Spreadsheet at the destination sheet.
function myFunction2() {
const range = "A1:E3"; // Please set the range you want to retrieve.
const srcSpreadsheetId = "###"; // Please set source Spreadsheet ID.
const srcSs = SpreadsheetApp.openById(srcSpreadsheetId);
const url = srcSs.getUrl();
const values = srcSs.getSheets().map(s => `IMPORTRANGE("${url}","'${s.getSheetName()}'!${range}")`);
const formula = `={${values.join(";")}}`;
const dstSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1"); // Please set the destination sheet name.
dstSheet.clearContents().getRange(1, 1).setFormula(formula);
}
References:
getSheets()
getValues()
setValues(values)
Added:
About your following reply,
I'm using the first block of code that you sent, and it works. However, I want to copy the entire cell so that it also transfers the cell color, in addition to the text content of the cell. How would I modify the first block of code you sent to do that?
Unfortunately, from your question, I couldn't notice that you wanted to copy both the values and the background color of the cells. To achieve this request, the modified script of my 1st script is as follows.
Sample script:
function myFunction() {
const range = "A1:E3"; // Please set the range you want to retrieve.
const srcSpreadsheetId = "###"; // Please set source Spreadsheet ID.
const dstSpreadsheetId = "###"; // Please set destination Spreadsheet ID.
const dstSheetName = "Sheet1"; // Please set destination sheet name.
const { values, backgroundColors } = SpreadsheetApp.openById(srcSpreadsheetId).getSheets().reduce((o, s) => {
const r = s.getRange(range);
o.values = [...o.values, ...r.getValues()];
o.backgroundColors = [...o.backgroundColors, ...r.getBackgrounds()];
return o;
}, { values: [], backgroundColors: [] });
const dstSheet = SpreadsheetApp.openById(dstSpreadsheetId).getSheetByName(dstSheetName);
dstSheet.clearContents().clearFormats().getRange(1, 1, values.length, values[0].length).setValues(values).setBackgrounds(backgroundColors);
}
I need to get & combine values of some specific columns (Name, Age, Phone) from a raw sheet to another sheet but I don't know how to do it with the google app script.
the workflow looks like as below:
First, I need to get values of only Name, Age, Phone columns from the raw sheet, I don't want to collect the region column. The raw sheet looks like as an image here:
Then I think I have to push it to an array and paste it to the result sheet which contains 3 columns: Name, Age, Phone
In query function I can do it with this statement:
=query("raw sheet","select Col1, Col3, Col4")
but I don't know how to do it with google app script.
If you use to try it with google app script, please share with me the reference code to do it, thank you so much!
I believe your goal is as follows.
You want to retrieve the specific columns from a source sheet, and want to put the retrieved columns to the destination sheet using Google Apps Script.
In this case, how about the following sample script?
Sample script:
Please copy and paste the following script and set the variables of sourceAndDestinationSheet and dstHeader and run the function with the script editor.
function myFunction() {
const sourceAndDestinationSheet = ["source sheet name", "destination sheet name"]; // Please set the source sheet name and destination sheet name to the 1st and 2nd element.
const dstHeader = ["Name", "age", "phone"]; // This is the header of the destination values.
// 1. Retrieve values from the source sheet.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const [srcSheet, dstSheet] = sourceAndDestinationSheet.map(s => ss.getSheetByName(s));
const [header, ...values] = srcSheet.getDataRange().getValues();
// 2. Create an array of destination values.
const colIndexes = dstHeader.map(h => header.indexOf(h));
const dstValues = [dstHeader, ...values.map(r => colIndexes.map(i => r[i]))];
// 3. Put the destination values to the destination sheet.
dstSheet.getRange(1, 1, dstValues.length, dstValues[0].length).setValues(dstValues);
}
Note:
If you want to use this script as a custom function, how about the following script? In this case, please put a custom formula of =SAMPLE(Sheet1!A1:D, "Name,age,phone") (It supposes that the source sheet and range are "Sheet1" and "A1:D") to the destination sheet.
function SAMPLE(v, h) {
const dstHeader = h.split(",").map(e => e.trim());
const [header, ...values] = v;
const colIndexes = dstHeader.map(h => header.indexOf(h));
return [dstHeader, ...values.map(r => colIndexes.map(i => r[i]))];
}
In this sample script, it is required to be the same between dstHeader and the actual header name of the source sheet. Please be careful this.
References:
map()
Custom Functions in Google Sheets
Try
=query('raw_2'!A1:D,"select A,B,D where A is not null",1)
I want to fill down Col D starting at Item Number Row and fill down that number to that row and the rows of data under it. And repeat for next items until done. Pictures below of desired results.
How would I do this in appscript?
Before:
Desired Result:
I believe your script as follows.
You want to achieve the situation from the top image to the bottom image in your question using Google Apps Script.
Flow:
In this case, I would like to propose the following flow.
Retrieve values from the sheet.
Create an array for putting values to the column "D".
In this case, I thought that the values for putting to the column "D" can be created using the retrieve values.
Put the values to the column "D".
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 sheetName.
function myFunction() {
const sheetName = "Sheet1"; // Please set this.
// 1. Retrieve values from the sheet.
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
const values = sheet.getDataRange().getValues();
// 2. Create an array for putting values to the column "D".
let temp = 0;
const outputValues = values.map(([a, b]) => {
if (a.toUpperCase().includes("ITEM NUMBER:")) temp = b;
return [temp];
});
// 3. Put the values to the column "D".
sheet.getRange("D1:D" + outputValues.length).setValues(outputValues);
}
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. In this case, please put a custom function of =myFunction2(A1:B12) to the cell "D1" for your sample Spreadsheet.
function myFunction2(values) {
let temp = 0;
return values.map(([a, b]) => {
if (a.toUpperCase().includes("ITEM NUMBER:")) temp = b;
return [temp];
});
}
References:
getValues()
map()
setValues(values)