How to transpose some rows to columns? - google-sheets

There were a lot of questions with transpose but I have some specifics and can't create the right formula.
So I have near 100k rows in the following format
https://docs.google.com/spreadsheets/d/146-6YHv69DDOnPKheKeRFZIQeZYIgO6UwCMd7X9VeKU/edit?usp=sharing
I need ARRAYFORMULA or something like that to make this 100k rows in the format that you can see on the "Expected Outcome" tab.

=ARRAYFORMULA({UNIQUE(INDIRECT("source!A2:A"&COUNTA(source!A2:A))),
QUERY(SPLIT(TRANSPOSE(SPLIT(QUERY("♦"&INDEX(SPLIT(TRANSPOSE(SPLIT(
TRIM(QUERY(TRANSPOSE(QUERY(TRANSPOSE(
IF(source!B2:J<>"", "♦"&source!A2:A&"♠"&{"♥"&source!B2:B, source!C2:J}, ))
,,999^99)),,999^99)), "♦")), "♠"),,2),,999^99), "♥")), "♦"),
"where Col2 is not null", 0)})

You have a vast number of records but there are 19 data fields to each record spread over 10 columns & 12 rows. You suggested an arrayformula (and much more clever people than I can probably do that), but I suggest a script which takes the data in its current form on one sheet (say the 'source'), and which outputs the data to a new sheet (say, the 'target').
There are a couple of issues to be addressed in "mapping" the data:
how many records are represented on the input sheet - required to enable looping. I used the Javascript Math.floor method to calculate the number of products.
identify the correct row/column combination for each field. The data is in three segments
8 fields in contiguous columns in the first row,
2 fields in 2 rows in the ninth column, and
9 fields in nine contiguous rows in the tenth column.
Total = 12 rows/10 columns. To do this, in order to navigate the rows, I took the counter (i) multiplied by the number of rows, plus 1; the columns are more intuitive.
for efficiency,
get data once only at the beginning of the script;
use arrays to progressively build the output; and
update the output (setValues) once at the end of the script.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sourcesheetname = "source";
var targetsheetname = "target";
var source = ss.getSheetByName(sourcesheetname);
var target = ss.getSheetByName(targetsheetname);
var sourcerange = source.getDataRange();
var sourcedata = sourcerange.getValues();
var sourceheaders = 1;
var rowsperproduct = 12;
var sourcelr = source.getLastRow();
var integerPart = Math.floor((sourcelr-sourceheaders)/rowsperproduct);
//Logger.log("DEBUG: number of products = "+integerPart);
var rowdata = [];
// look thought the number of products
for (var i = 0; i<integerPart; i++){
// use a temporary array to build the data for the row.
var temp = [];
// row 1, copy first eight columns (0-7)
temp.push(sourcedata[(i*12)+1][0]);//ID
temp.push(sourcedata[(i*12)+1][1]);//GID
temp.push(sourcedata[(i*12)+1][2]);//NAME
temp.push(sourcedata[(i*12)+1][3]);//PRICE
temp.push(sourcedata[(i*12)+1][4]);//BRAND
temp.push(sourcedata[(i*12)+1][5]);//URL
temp.push(sourcedata[(i*12)+1][6]);//country
temp.push(sourcedata[(i*12)+1][7]);//instock
// row 2 & 3 in Column I(8)
temp.push(sourcedata[(i*12)+2][8]);//url1
temp.push(sourcedata[(i*12)+3][8]);//url2
// rows 4 - 12 in Column J(9)
temp.push(sourcedata[(i*12)+4][9]);// tech name
temp.push(sourcedata[(i*12)+5][9]);// size
temp.push(sourcedata[(i*12)+6][9]);// inches
temp.push(sourcedata[(i*12)+7][9]);// mm
temp.push(sourcedata[(i*12)+8][9]);// depth
temp.push(sourcedata[(i*12)+9][9]);// BB
temp.push(sourcedata[(i*12)+10][9]);// ref1
temp.push(sourcedata[(i*12)+11][9]);// ref2
temp.push(sourcedata[(i*12)+12][9]);// ref3
// Update the rowdata for this row
rowdata.push(temp);
}
// update data to target
target.getRange(2,1,integerPart,19).setValues(rowdata);
}

Related

Split cell values into new rows with common values

CONTEXT
I have a source table with multiple columns (Source B table in example spreadsheet).
On column E we have max one name and on column F we can have from 0 to multiple names separated by a comma.
AIM
When there are values in F, add one row per each name (E,F - the last one can have more than one separated by a comma) and duplicate the common values.
It should keep the rows where there are no values in F.
The final result will have one less column than Source table.
WHAT I'VE TRIED
=ARRAYFORMULA(QUERY(IFERROR(SPLIT(FLATTEN(IF(ISBLANK(E3:F6);;
A3:A6&"♦"&B3:B6&"♦"&C3:C6&"♦"&D3:E6&"♦"&E3:E6&"♦"&G3:G6&"♦"&H3:H6&"♦"&I3:I6&"♦"&J3:J6&"♦"&K3:K6&"♦"&L3:L6)); "♦"));
"where Col3 <> 0"; 0))
Problem
This formula was applied to Source A table (in the example spreadsheet and that's = Source B table but without comma separated values in F), which didn't have more than one value in F at the time, and:
duplicates the common values as expected but it's not showing the values from F. Just duplicates the ones from E.
ads blank row if the source row doesn't have values in F
because in column K only one row has a value, it messes up the final data
doesn't do anything different with or without comma separated values in F
Example spreadsheet
--- EDIT ---
I've found this other post with a script that I also tested on the example sheet. I've reduced the number of columns to ease the test and because I think it's aiming for the last column being the one that is supposed to be splitted, if applicable.
It does split but it repeats the "Main" client on the left column and the splited one on the right, where I would like the outcome to have all clients on the same column.
To give you an idea, you can try the following script, I used the "Source for Script" Sheet and created another sheet called "Result". As you can see in the script, I used arrays to collect the data, then added it to the "Result" Sheet.
function splitNewRow() {
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Source for Script");
var data = source.getRange(2, 1, source.getLastRow(), source.getLastColumn()).getValues();
var outcomeSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Result");
newArray = [];
newRow = [];
for (i=0; i < data.length; i++){
var claim = source.getRange(i+2,1).getValue();
var mainClient = source.getRange(i+2,2).getValue();
var others = source.getRange(i+2, 3).getValue();
hasComma = others.indexOf(",") != -1;
if (others != "" && hasComma == true){
newArray = [];
newArray = others.split(",");
newArray.push(mainClient);
for (j=0; j < newArray.length; j++){
newRow = [claim, newArray[j]];
outcomeSheet.appendRow(newRow);
}
}
else if(others != "" && hasComma == false){
newRow = [claim, mainClient];
outcomeSheet.appendRow(newRow);
newRow = [claim, others];
outcomeSheet.appendRow(newRow);
}
else{
newRow = [claim, mainClient];
outcomeSheet.appendRow(newRow);
}
}
}
After running the script, you get the following:
If you have any questions, let me know.

Google Sheets: Group rows and columns into cells by an ID

I have a table of tasks where rows are grouped using ID's. I want to represent these groupings in another table where there is one row for each ID and all data rows and columns are grouped for each ID in a single cell. I need a formula that can group certain rows into a cell using newlines, and certain columns into cells using delimiters. My data source is something like this:
The formula needs to:
Filter rows by Condition.
Put Task ID's in their own column, one row for each individual ID.
Put all data in Hours, Mins, Customer and Description columns into a cell next to ID so that:
Hours and Mins are joined with a ":" as a delimiter to represent time values
Customer and Description values are then concatenated to time values using space as a delimiter.
The result should look like:
A Google Sheet containing example of the source table and desired result:
https://docs.google.com/spreadsheets/d/14p_HYpfb7XlWhRRgo3JN7SPRerwEaB2A7Dz1KWuBgL8/edit#gid=299935838
SUGGESTION:
You can try this custom formula GROUPDATA using the script below. Just add it as a bound script on your spreadsheet file:
Script:
function GROUPDATA(data) {
var temp = "";
var filtered = [];
var container = [];
var col2 =[];
var final = [];
data.forEach(function(data) {
if(data[0] == true){
filtered.push(data[1])
container.push([data[1],data[2]+":"+data[3]+" "+data[4]+" "+data[5]])
}
});
const unique = (value,index,self) =>
{return self.indexOf(value) ===index;}
var unique_list=filtered.filter(unique);
for(i=0; i<unique_list.length; i++){
container.forEach(function(x) {
if(x[0]== unique_list[i]){
temp = temp+ x[1]+"\n";
}
});
col2.push(temp);
temp = "";
}
for(y=0; y<unique_list.length; y++){
final.push([unique_list[y],col2[y]]);
}
return final;
}
DEMONSTRATION:
Used the custom formula =GROUPDATA(A:F) on cell G14

Formula to import multiple columns to 1 column if a name from a different list matches any name in those columns

Looking for a way to import a long range of columns in to 1 list if a name from a different list matches any name in that particular column. I've tried a couple variations of query, filter, vlookup... Can't seem to find the right combination. Example..
List A on sheet 1 contains the names Jim, John and James. On a separate sheet, there are 5 columns containing names. Column 1 contains Jim, Alex and Ben. Column 2 contains Harold, Bob and Jimmy. Column 3 contains James, Jeremy and Felix. Column 4 contains James, Eric and Evan. Column 5 contains Sara, Jamie and Xavier. The end result should display the list in 1 column to contain the names - Jim, John, James, Alex, Ben, Jeremy, Felix, Eric and Evan. Columns 1, 3 and 4 would be imported to a single list because at least 1 name within those columns matched a name in the original list.
Example sheet.
UPDATED
You can try this sample implementation below using Apps Script custom function. Just copy and paste it to your spreadsheet file as a bound script:
NOTE: This script will check each name listed on the "Main List" & if each of them has matches on multiple columns on the second sheet Sheet2, then it will place all of the names on columns that contains any matched name on the "Imported List". The only catch using this implementation is that it'll run a bit slow when there's a huge amount of data to be processed.
SCRIPT
function onOpen(){ //Runs every time you open the spreadsheet
FINDMATCH();
}
function onEdit(){//Runs every time you make an edit to the sheet
FINDMATCH();
}
function FINDMATCH() { //Function to check each columns on "Sheet2" to see if it has names that match any names on the "Main List"
var main = SpreadsheetApp.getActive().getSheetByName("Sheet1");
var mainList = [].concat.apply([], main.getRange("A4:A").getValues()).filter(String); //Main list data starts at A4
var sheet2 = SpreadsheetApp.getActive().getSheetByName("Sheet2"); //Name of the Sheet2
var result = mainList;
for(col=1; col <= sheet2.getDataRange().getLastColumn(); col++){
// E.G. if you only want to set the maximum of 2 rows as seen below, the "currentCol" code will only scan range A1:F3 on Sheet2 because the number 3 on the third parameter of the getRange() method will be the maximum row to be scanned.
var currentCol = [].concat.apply([], sheet2.getRange(1,col,2,1).getValues()).filter(String);
mainList.forEach(function(list){
currentCol.forEach(function(data){
if(data == list){
result.push(currentCol.toString());
return;
}
});
});
}
var data = FILTER(result.toString().trim().split(","));
main.getRange("AG4:AG").clearContent(); //Clears the old "Imported List" data before pasting updated list of names
main.getRange(4,33,data.length,1).setValues(data); //Updates the "Imported List"
}
function FILTER(array) { //Function that filters duplicate names
var data = array;
var newData = [];
var formattedData = [];
for (var i in data) {
var row = data[i];
var duplicate = false;
for (var j in newData) {
if (row === newData[j] && row === newData[j]) {
duplicate = true;
}
}
if (!duplicate) {
newData.push(row);
}
}
newData.forEach(function(i){
formattedData.push([i]);
});
return formattedData;
}
Sample Result:
Sample Sheet
When spreadsheet gets updated with new data

Should this be a SUMIF formula?

I'm trying to make a formula that can recognize in Column A the name Brooke B for instance here, from there I'd like to SUM the values listed in Column I Cash Discounts for that specific user.
(Yes this user has no Cash Discounts, thus column I states "Non-Cash Payment").
There's about 80 users total here, so I'd prefer to automate the name recognition in Column A.
Sheet: https://docs.google.com/spreadsheets/d/1xzzHT7VjG24UJ4ZXaiZWsfzroTpn7jCJLexuTOf6SQs/edit?usp=sharing
Desired Results listed in Cash Discounts sheet, listed per user in column C.
You are trying to calculate the total amount of the Cash Discount per person given to people in a list. You have data that has been exported from a POS system to which that you have added a formula to calculate the amout of the discount on a line by line basis. You have speculated whether the discount totals could be calculated using SUMIFS formulae.
In my view, the layout of the spreadsheet and the format of the POS report do not lend themselves to isolating discrete data elements though Google sheets functions (though, no doubt, someone with greater skills than I will disprove this theory). Column A, containing names, also includes sub-groupings (and their sub-totals) as well as transaction dates. There are 83 unique persons and over 31,900 transaction lines.
This answer is a script-based solution which updates a sheet with the names and values of the discount totals. The elapsed execution time is #11 seconds.
function so5882893202() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// get the Discounts sheet
var discsheetname = "Discounts";
var disc = ss.getSheetByName(discsheetname);
//get the Discounts data
var discStartrow = 3;
var discLR = disc.getLastRow();
var discRange = disc.getRange(discStartrow, 1, discLR-discStartrow+1, 9);
var discValues = discRange.getValues();
// isolate Column A
var discnameCol = discValues.map(function(e){return e[0];});//[[e],[e],[e]]=>[e,e,e]
//Logger.log(discnameCol); // DEBUG
// isolate Column I
var discDiscounts = discValues.map(function(e){return e[8];});//[[e],[e],[e]]=>[e,e,e]
//Logger.log(discDiscounts); // DEBUG
// create an array to build a names list
var names =[]
// get the number of rows on the Discounts sheet
var discNumrows = discLR-discStartrow+1;
// Logger.log("DEBUG: number of rows = "+discNumrows);
// identify search terms
var searchPercent = "%";
var searchTotal = "Total";
// loop through Column A
for (var i=0; i<discNumrows; i++){
//Logger.log("DEBUG: i="+i+", content = "+discnameCol[i]);
// test if value is a date
if (Object.prototype.toString.call(discnameCol[i]) != "[object Date]") {
//Logger.log("it isn't a date")
// test whether the value contains a % sign
if ( discnameCol[i].indexOf(searchPercent) === -1){
//Logger.log("it doesn't have a % character in the content");
// test whether the value contains the word Total
if ( discnameCol[i].indexOf(searchTotal) === -1){
//Logger.log("it doesn't have the word total in the content");
// test whether the value is a blank
if (discnameCol[i] != ""){
//Logger.log("it isn't empty");
// this is a name; add it to the list
names.push(discnameCol[i])
}// end test for empty
}// end test for Total
} // end for percentage
} // end test for date
}// end for
//Logger.log(names);
// get the number of names
var numnames = names.length;
//Logger.log("DEBUG: number of names = "+numnames)
// create an array for the discount details
var discounts=[];
// loop through the names
for (var i=0;i<numnames;i++){
// Logger.log("DEBUG: name = "+names[i]);
// get the first row and last rows for this name
var startrow = discnameCol.indexOf(names[i]);
var endrow = discnameCol.lastIndexOf(names[i]+" Total:");
var x = 0;
var value = 0;
// Logger.log("name = "+names[i]+", start row ="+ startrow+", end row = "+endrow);
// loop through the Cash Discounts Column (Column I) for this name
// from the start row to the end row
for (var r = startrow; r<endrow;r++){
// get the vaue of the cell
value = discDiscounts[r];
// test that it is a value
if (!isNaN(value)){
// increment x by the value
x = +x+value;
// Logger.log("DEBUG: r = "+r+", value = "+value+", x = "+x);
}
}
// push the name and the total discount onto the array
discounts.push([names[i],x]);
}
//Logger.log(discounts)
// get the reporting sheet
var reportsheet = "Sheet10";
var report = ss.getSheetByName(reportsheet);
// define the range (allow row 1 for headers)
var reportRange = report.getRange(2,1,numnames,2);
// clear any existing content
reportRange.clearContent();
//update the values
reportRange.setValues(discounts);
}
Report Sheet - extract
Not everyone wants a script solution to their problem. This answer seeks to supply a repeatable solution using common garden-variety formula/functions.
As noted elsewhere, the layout of the spreadsheet does not lend itself to a quick/simple solution, but it IS possible to break down the data to compile a non-script answer. Though it may "seem" as though the following formula are less than "simple, when taken one-at-a-time they are logical, very easy to create, and very easy to verify successful outcomes.
Note: It is important to know at the outset that the first row of data = row#3, and the last row of data = row#31916.
Step#1 - get Text values from ColumnA
Enter this formula in Cell J3, and copy to row 31916
=if(isdate(A3),"",A3):
evaluates Column A, if the content is a date, returns blank, otherwise, returns the context
Taking Customer "AJ" as an example, the content at this point includes:
AJ
10% BuildingDiscount
10% BuildingDiscount Total:
Northwestern 10%
Northwestern 10% Total:
AJ Total:
Step#2 - ignore the values that contain "10%" (this removes both headings and sub-subtotals
Enter this formula in Cell K3 and copy to row 31916
=iferror(if(search("10%",J3)>0,"",J3),J3): searches for "10%" in Column J. Returns all values except those that containing "10%".
Taking Customer "AJ" as an example, the content at this point includes:
AJ
AJ Total:
**Step#3 - ignore the values that contain the word "Total"
Enter this formula in Cell L3 and copy to row 31916.
=iferror(if(search("total",K3)>0,"",K3),K3)
Taking Customer "AJ" as an example, the content at this point includes:
AJ
Results after Step#3
You might wonder, "couldn't this be done in a single formula?" and/or "an array formula would be more efficent". Both those thoughts are true, but we're looking at simple and easy, and a single formula is NOT simple (as shown below); and given that, an array formula is out-of-the-question unless/until an expert can wave a magic wand over the data.
FWIW - Combining Steps#1, 2 & 3
each of the Steps#1, 2 and 3 build on each other. So it is possible to create a single formula that combines these steps.
enter this formula in Cell J3, and copy dow to row #31916.
=iferror(if(search("total",iferror(if(search("10%",if(isdate(A3),"",A3))>0,"",if(isdate(A3),"",A3)),if(isdate(A3),"",A3)))>0,"",iferror(if(search("10%",if(isdate(A3),"",A3))>0,"",if(isdate(A3),"",A3)),if(isdate(A3),"",A3))),iferror(if(search("10%",if(isdate(A3),"",A3))>0,"",if(isdate(A3),"",A3)),if(isdate(A3),"",A3)))
As the image showed, step#3 concludes with mainly empty cells in Column L; the only populated cell is the first instance of the customer name at the start of their transactions - such as "Alec" in this example. However (props to #Rubén) it is possible to populate the blank transaction Cells in Column L. An arrayformula to find the previous non-empty cell in another column on Webapps explains how.
Step#4 - Create a customer name for each transaction row.
Enter this formula in Cell M3, it will automatically populate the cells to row#31916
=ArrayFormula(vlookup(ROW(3:31916),{IF(LEN(L3:L31916)>0,ROW(3:31916),""),L3:L31916},2))
Step#5 - Get the discount amount for each transaction value
The discount values are already displayed in Column I. They are interspersed with text values, so the formula for tests if this is a total line by testing the value in Column D; only if there is a vale (Product item) does the formula then test of there is a value in column I.
Enter this formula in Cell N3, it will automatically populate the cells to row#31916
=ArrayFormula(if(len(D3:D31914)>0,if(ISNUMBER(I3:I31916),I3:I31916,0),""))
Screenshot after step#5
Reporting by Query
Reporting is done via queries. These can go anywhere, but it is probably more convenient to put it on a separate sheet.
Step#6.1 - query the results to create report showing total by ALL customers
=query(Discounts_analysis!$M$2:$N$31916,"select M, sum(N) where N is not null group by M label M 'Customer', sum(N) 'Total Discount' ",1)
Step#6.2 - query the results to create report showing total by customer where the customer received a discount
=query(Discounts_analysis!$M$2:$N$31916,"select M, sum(N) where N >0 group by M label M 'Customer', sum(N) 'Total Discount' ",1)
Step#6.3 - query the results to create report showing customers with no discount
- `=query(query(Discounts_analysis!$M$2:$N$31916,"select M, sum(N) where N is not null group by M label M 'Customer', sum(N) 'Total Discount' ",1),"select Col1 where Col2=0")`
Queries screenshot

Trying to make a script using a macro - code too long

First of all I must say that my level of knowledge of Google App Scripts is almost nil. I tried to create a script using the macro utility, but the resulting code is too long. I will be very grateful if someone can help me simplify this code. I indicate below the parameters of the subject:
I have approximately 50 tabs (sheets) in a spreadsheet, although they can be more. (For short, the spreadsheet that is attached as an example has 5 tabs, whose content, also for short, is the same.)
Each tab contains data in cells B8:L17 (11 columns and 10 rows of data).
On a tab called "Index" are the names of the 50 tabs. The names of the tabs must begin in B2. Between one tab name and the next there are 9 empty rows. (All the above I have been able to solve.)
What I want to do --using the "Indirect" formula- is the following:
- That the data of sheet 1 be copied in Index in C2: M11.
- That the data on sheet 2 be copied to Index at C12: M21.
- That the data on sheet 3 be copied to Index in C22: M31.
- That the data of sheet 4 be copied in Index in C32: M41.
- That the data of sheet 5 be copied in Index in C42: M51.
--Etc.
Thank you for your attention.
function INDIRECT() {
var s = SpreadsheetApp.getActive();
s.getRange("c2").setFormula('=INDIRECT("\'"&Index!b2&"\'!b8:l17")');
s.getRange("c2").offset(10, 0).activate();
s.getCurrentCell().offset(-10, 0).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
s.getCurrentCell().offset(10, 0).activate();
s.getCurrentCell().offset(-20, 0).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
s.getCurrentCell().offset(10, 0).activate();
s.getCurrentCell().offset(-30, 0).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
s.getCurrentCell().offset(10, 0).activate();
s.getCurrentCell().offset(-40, 0).copyTo(s.getActiveRange(), SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
}
The OP needs to populate a master sheet with the contents of other sheets.
The following code assumes that the names of the sheets are already populated in Column B (as per the OP spreadsheet).
The equivalent query formula (in Cell C3, for example) looks like this:
=query(Funda!$B$8:$L$17;" Select * ").
This code does not produce a custom formula. It is a routine that might be run only once, looping through Column B of the Master sheet, and inserting the appropriate query into Column C, based on the sheet names listed in Column B. The code might be run only once. Unless there is a change in the spreadsheet layout, or more sheets are added, the query formulas inserted by the code will update the master sheet for all changes on sub-sheets.
function so54463600() {
// setup spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mastername = "Master"; // Change this sheet name to whatever you want
var sheet = ss.getSheetByName(mastername);
// The sheet data range
var datarange = "$B$8:$L$17";
// set the data and number of sheets in Column B
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
var lastRow = sheet.getLastRow();
//Logger.log("DEBUG: the last row = "+lastRow);//DEBUG
// loop through the rows of column B
for (var x = 0; x < lastRow; x++) {
// action if the cell has contents
if (Bvals[x][0].length != 0) {
// Logger.log("DEBUG: x = "+x+", Value = "+Bvals[x][0]+", length: "+Bvals[x][0].length);//DEBUG
// sample query: =query(Funda!$B$8:$L$17;" Select * ")
var formula = "=query(" + Bvals[x][0] + "!" + datarange + ";" + '"' + " Select * " + '")';
// Logger.log("DEBUG: the formula is "+formula);//DEBUG
// Location to set query formula
Logger.log("target range:" + sheet.getRange(x + 1, 3).getA1Notation());
// set the formula
sheet.getRange(x + 1, 3).setFormula(formula);
}
}
}
REVISION 4 Feb 2019
Update Sheet Names Programmatically
The number of sheets in a spreadsheet can be 50 or more. Sheet names can change, sheets can be added or deleted. Therefore, in order to ensure that the data shown on the master sheet is complete and accurate, it is desirable that sheet names be inserted programmatically, rather than manually.
This revision includes a new subroutine buildsheetnames AND some changes to the original code. Regard this is a complete substitution rather than a simple add-on.
Sourcing sheet names
The sheet names are obtained first with getSheets(), and then getName().
The user organises the sheets in the screen order that suits them; only sheets listed to the right of the "Master" sheet will be included in the names displayed by the routine.
A user defined variable startingsheetNumber is included to define the first sheet to be displayed on the "Master" list. This is obtained by counting ALL the sheets from left to right, beginning the count at zero. The count value of the first sheet to the right of "Master" should be assigned to this variable. The rest of the sheets to the right of "Master" will be automatically included.
function so54463600mk2() {
// setup spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mastername = "Master";
var sheet = ss.getSheetByName(mastername);
// The sheet data range
var datarange = "$B$8:$L$17";
// select the existing sheet data and clear the contents
// do this in case a sheet is added or deleted; minimize chance of not detecting an error.
var lastRow = sheet.getLastRow();
var lastColumn = sheet.getLastColumn();
//Logger.log("last row = "+lastRow+", last Column: "+lastColumn);
var masterrange = sheet.getRange(2, 2, lastRow - 1, lastColumn - 1);
masterrange.clear();
// update the sheet names in column B
// run the sub-routine buildsheetnames
buildsheetnames(mastername);
// set the data and number of sheets in Column B
var Bvals = sheet.getRange("B1:B").getValues();
var Blast = Bvals.filter(String).length;
var lastRow = sheet.getLastRow();
//Logger.log("DEBUG: the last row = "+lastRow);//DEBUG
// loop through the rows of column B
for (var x = 0; x < lastRow; x++) {
// action if the cell has contents
if (Bvals[x][0].length != 0) {
// Logger.log("DEBUG: x = "+x+", Value = "+Bvals[x][0]+", length: "+Bvals[x][0].length);//DEBUG
// sample query: =query(Funda!$B$8:$L$17;" Select * ")
//var formula = "=query(" + Bvals[x][0] + "!"+datarange+";"+'"'+" Select * "+'")';
var formula = "=query(" + "'" + Bvals[x][0] + "'!" + datarange + ";" + '"' + " Select * " + '")';
// Logger.log("DEBUG: the formula is "+formula);//DEBUG
// Location to set query formula
Logger.log("target range:" + sheet.getRange(x + 1, 3).getA1Notation());
// set the formula
sheet.getRange(x + 1, 3).setFormula(formula);
}
}
}
function buildsheetnames(mastername) {
// setup spreadsheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(mastername);
// move the curcor to cell B2
var row = 2;
var column = 2;
sheet.getRange(2, 2).activate();
// get the list of sheets
var sheetlist = ss.getSheets();
// Logger.log("DEBUG: Number of sheets: "+sheetlist.length);//DEBUG
// Displaying Sheet names on the master sheet
// Instructions:
// 1 - View the spreadsheet with sheets laid out at the bottom of the screen
// 2 - Move the sheets into the order that you want;
// 2a - Sheets to the left of "Master" (or whatever sheet you define as "mastername") wouldn't be included in the sheet names written onto Master
// 2b - Only the names of those sheets to the right of "Master" will be displayed.
// 3 - Count ALL the sheets, from left to right of screen. The first sheet will be zero, the second sheet will be one, and so on.
// 4 - Stop when you reach the first sheet that you want the name displayed on Master
// 5 - Update the count value for that sheet to the variable 'startingsheetNumber'.
// 6 - Note: the variable "sheetrowseparation" is the number of rows between each name;
// 6a - This is the same as the number of data rows on each data sheet.
// 6b - If the number of data rows ever changes, then you can change this variable.
var startingsheetNumber = 6;
var sheetrowseparation = 10;
// loop through the sheets
if (sheetlist.length > 1) {
for (var i = startingsheetNumber; i < sheetlist.length; i++) {
//Logger.log("DEBUG: Sheet#: "+i+", Sheet name: "+sheetlist[i].getName());//DEBUG
sheet.getRange(row, column).setValue(sheetlist[i].getName());
row = row + sheetrowseparation;
}
}
return;
}

Resources