transpose same single column range from multiple worksheets onto single worksheet - transpose

I have an indefinite number of multiple worksheets from which I am trying to transpose a single column range (i.e., "C4" on down) from each onto a single summary worksheet. The column range is the same for all worksheets. The code I've adapted from other posts have gotten me close but when I transpose the column from each worksheet it seems it only transposes them onto one row ("F4"). Could someone please try and see what I'm missing? Greatly appreciated!! This is the code I've been working with:
Sub UpdateSummary()
Dim rng3 As Range, sh As Worksheet, lastRow As Long
'the "sample" work sheet has the total # of rows; used to count # rows
'which will be needed from each of the other worksheets
lastRow = Worksheet("sample").Range("J3").End(xlDown).Row
'the workbook has multiple worksheets numbered from 1 to n
Sheets("summary").Activate
For Each sh In Worksheets
If sh.Name <> "summary" _
And sh.Name <> "sample" Then
'if i don't add the +1 it's short by 1
Set rng3 = sh.Range("C4:C" & lastRow + 1)
rng3.Copy
'using Transpose so for each sh its range goes into the summary as
'rows (starts from F4 because the top 3 rows are headers)
Worksheets("summary").Range("F4").PasteSpecial Transpose:=True
End If
Next sh
End Sub
In thinking this through... I think part of what it needs is to count the number of worksheets from which ranges will be copied/transposed - and use that number of total worksheets as the number of rows on which the data will be transposed?
I got it... I needed to reset the target range by Offsetting it. This is the code that worked:
Sub CalcSummary()
'vba to calculate summary
'for all worksheets except sample and summary
'select range to copy values
'transpose values onto summary sheet
Dim rng3 As Range
Dim sh As Worksheet
Dim cases As Long
Dim items As Long
Dim trng As Range
cases = Worksheets("sample").Range("A3").End(xlUp).Row
items = Worksheets("sample").Range("J3").End(xlDown).Row
Set trng = Worksheets("summary").Range("F4")
Sheets("summary").Activate
For Each sh In Worksheets
If sh.Name <> "summary" _
And sh.Name <> "sample" Then
Set rng3 = sh.Range("C4:C" & items + 1)
rng3.Copy
With trng
.PasteSpecial Transpose:=True
End With
Set trng = trng.Offset(1, 0)
End If
Next sh
MsgBox "Summary sheet updated successfully."
End Sub
Hope this helps for anyone else who needs to do something similar ;) And thanks for all those who maybe were trying to rewrite the code for me.

Related

manipulate data from multiple rows in a sheet, combine the new totals together, and post to a different sheet

I have two google sheets. One is more of a summary and the other one tracks all of the data. I need to get the price from the row on the 2nd page that is marked as entry, with a corresponding trade id to the first page, and then, if it was a long trade(Determined by the Long/Short(L/S) column on page 1, subtract the entry price from each exit price and then add those values together. If short trade it would be each entry price minus the exit entry price. Most trades will have 1 entry and 2 exits.
Basic example: Buy 3 contracts long at $10. Exit at $15 for 2 contracts and $20 for 1 contract.
So it will be (exit1 - entry) + (exit1 - entry) + (exit2 - entry). This
will give me the total points for each section of the trade. That data
then needs to get displayed on the overview tab
Example google sheet: https://docs.google.com/spreadsheets/d/1Y8QHrARyYjJKfOwq0g3waDfkVyWbWc2uSIzBiqsIal0/edit?usp=sharing
Edit: adjusted match to properly represent the outcome.
Try this one.
Formula:
=sum(query('Trades Taken'!A:D, "select D where A = "&A2&" and B = 'Exit'")) - (rows(filter('Trades Taken'!A:D, 'Trades Taken'!A:A = A2, 'Trades Taken'!B:B = "Exit")) * filter('Trades Taken'!D:D, 'Trades Taken'!A:A = A2, 'Trades Taken'!B:B = "Entry"))
I have simplified the formula so it would be easier to interpret.
Output:

In Sheet 1, search Row 1 for all occurrences of a string, and each time its found, copy the cell below it (from Row 2) onto separate rows in Sheet 2

I have went through some trial and error working on the wrong formulas for what I thought I needed and I've now narrowed it down to the exact formula/concept that I need for my use case.
I need a formula that searches the header row (Row 1 of Sheet 1) for occurrences of a specific string, for instance HouseNumber, and when it finds an occurrence, it grabs the data from the cell below it (from Row 2 of Sheet 1) and copies that over to a different sheet (starting at Row 2 of Sheet 2, leaving Row 1 as a header row), then continues looking for the second occurrence, then once it's found, it grabs the data from the cell below it (from Row 2 of Sheet 1), and copies that to the next row down on the different sheet (Row 3 of Sheet 2), then continues until no more occurrences are found, etc...
So for example, in Sheet 1 below, search Row 1 for all occurrences of HouseNumber and copy the values in the cells below them, from Row 2, over to separate rows in Sheet 2 (so a formula should go in Sheet 2 at A2).
Sheet 1:
HouseNumber.1
HouseStreet.1
HouseNumber.2
HouseStreet.2
123
1st Ave
456
2nd Ave
Sheet 2:
HN
123
456
Then do the same as above, except search for occurrences of HouseStreet (so a separate formula should go in Sheet 2 at B2).
Sheet 2:
HN
HS
123
1st Ave
456
2nd Ave
I have been trying nested functions with INDEX and MATCH, but it is not fully working and also it is only structured to search for / return one occurrence / value, not all occurrences/values.
If you have Windows Excel 2021+ (which have the functions used), you can use the following formulas:
For the address table 1, I used a Table with structured addressing, but you could use regular addressing if you prefer
HN: =FILTERXML("<t><s>" & TEXTJOIN("</s><s>",TRUE,FILTER(adrTbl,ISNUMBER(FIND("Number",adrTbl[#Headers])))) & "</s></t>","//s")
HS: =FILTERXML("<t><s>" & TEXTJOIN("</s><s>",TRUE,FILTER(adrTbl,ISNUMBER(FIND("Street",adrTbl[#Headers])))) & "</s></t>","//s")
results should spill down as far as needed
adrTbl
Results
Note that you can expand the table rows and/or columns and the formula should still work.
If you have an earlier version of Excel, I'd suggest using Power Query
Custom Function
Pivot with no aggregation
//credit: Cam Wallace https://www.dingbatdata.com/2018/03/08/non-aggregate-pivot-with-multiple-rows-in-powerquery/
//rename this query fnPivotAll
(Source as table,
ColToPivot as text,
ColForValues as text)=>
let
PivotColNames = List.Buffer(List.Distinct(Table.Column(Source,ColToPivot))),
#"Pivoted Column" = Table.Pivot(Source, PivotColNames, ColToPivot, ColForValues, each _),
TableFromRecordOfLists = (rec as record, fieldnames as list) =>
let
PartialRecord = Record.SelectFields(rec,fieldnames),
RecordToList = Record.ToList(PartialRecord),
Table = Table.FromColumns(RecordToList,fieldnames)
in
Table,
#"Added Custom" = Table.AddColumn(#"Pivoted Column", "Values", each TableFromRecordOfLists(_,PivotColNames)),
#"Removed Other Columns" = Table.RemoveColumns(#"Added Custom",PivotColNames),
#"Expanded Values" = Table.ExpandTableColumn(#"Removed Other Columns", "Values", PivotColNames)
in
#"Expanded Values"
Main Query
let
Source = Excel.CurrentWorkbook(){[Name="adrTbl"]}[Content],
//Unpivot all the columns to => data in just two columns
unPivot = Table.UnpivotOtherColumns(Source,{},"Attribute","Value"),
//split off the number from the column header to normalize them
//If you have other columns => unwanted attributes, may need to filter after the Split
#"Split Column by Delimiter" = Table.SplitColumn(unPivot, "Attribute", Splitter.SplitTextByDelimiter(".", QuoteStyle.Csv), {"Attribute.1", "Attribute.2"}),
#"Removed Columns" = Table.RemoveColumns(#"Split Column by Delimiter",{"Attribute.2"}),
//Pivot on the Attribute with no aggregation
pivot=fnPivotAll(#"Removed Columns","Attribute.1","Value")
in
pivot
Solved it with a formula:
=arrayformula(query(transpose(to_text(Sheet1!1:2)),"Select Col2 where Col1 contains 'HouseNumber'"))

Generate a list of all unique values of a multi-column range and give the values a rating according to how many times they appear in the last X cols

As the title says.
I have a range like this:
A B C
------ ------ ------
duck fish dog
rat duck cat
dog bear bear
What I want is to get a single-column list of all the unique values in the range, and assign them a rating (or tier) according to the number of times they have appeared in the last X columns (more columns are constantly added to the right side).
For example, let's say:
Tier 0: hasn't appeared in the last 2 columns.
Tier 1: has appeared once in the last 2 columns.
Tier 2: has appeared twice in the last 2 columns.
So the results should be:
Name Tier
------ ------
duck 1
rat 0
dog 1
fish 1
bear 2
cat 1
I was able to generate a list of unique values by using:
=ArrayFormula(UNIQUE(TRANSPOSE(SPLIT(CONCATENATE(B2:ZZ9&CHAR(9)),CHAR(9)))))
But it's the second part that I am not sure exactly how to achieve. Can this be done through Google Sheets commands or will I have to resort to scripting?
Sorry, my knowledge is not enough to build an array-formula but I can explain how I get it per cell and then expanded a range from it.
Part 1: count the number of nonempty columns (assuming that if column has something on the second row, then it's filled.
COUNTA( FILTER( Sheet1!$B$2:$Z$2 , NOT( ISBLANK( Sheet1!$B$2:$Z$2 ) ) ) )
Part 2: build a range for the last two filled columns:
OFFSET(Sheet1!$A$2, 0, COUNTA( ... )-1, 99, 2)
Part 3: use COUNTIF to count how many values of "bear" we meet there (here we can pass a cell-reference instead) :
COUNTIF(OFFSET( ... ), "bear")
I built a sample spreadsheet that gets the results, here's the link (I know external links are bad, but there's no other choice to show the reproducible example).
Sheet1 contains the data, Sheet2 contains the counts.
I suggest using both script and the formula.
Normalize the data
Script is the easiest way to normalize data. It will convert your columns into single column data:
/**
* converts columns into one column.
*
* #param {data} input the range.
* #return Column number, Row number, Value.
* #customfunction
*/
function normalizeData(data) {
var normalData = [];
var line = [];
var dataLine = [];
// headers
dataLine.push('Row');
dataLine.push('Column');
dataLine.push('Data');
normalData.push(dataLine);
// write data
for (var i = 0; i < data.length; i++) {
line = data[i];
for (var j = 0; j < line.length; j++) {
dataLine = [];
dataLine.push(i + 1);
dataLine.push(j + 1);
dataLine.push(line[j]);
normalData.push(dataLine);
}
}
return normalData;
}
Test it:
Go to the script editor: Tools → Editor (or in Chrome browser: [Alt → T → E])
After pasting this code into the script editor, use it as simple formula: =normalizeData(data!A2:C4)
You will get the resulting table:
Row Column Data
1 1 duck
1 2 fish
1 3 dog
2 1 rat
2 2 duck
2 3 cat
3 1 dog
3 2 bear
3 3 bear
Then use it to make further calculations. There are a couple of ways to do it. One way is to use extra column with criteria, in column D paste this formula:
=ARRAYFORMULA((B2:B>1)*1)
it will check if column number is bigger then 1 and return ones and zeros.
Then make simple query formula:
=QUERY({A:D},"select Col3, sum(Col4) where Col1 > 0 group by Col3")
and get the desired output.

Formulate GSheets formula involving nested IFs

I have created a Google spreadsheet for our small business which lists all the invoices. I have uploaded a simplified format in
https://docs.google.com/spreadsheets/d/1zYrRxDm0ahsjWE8aNquz-shHuNY_Eifl3lXLhIBUeTE/edit?usp=sharing.
1.There can be 1-5 products per invoice.
2.The column G is the total of all the products in that invoice. I want to create a formula for this column.
Presently, my formula is very long and inefficient.
The column (G) calculates number of products with this formula:
=IF(B3<>"",IF(OFFSET(B3,1,0)="",IF(OFFSET(B3,2,0)="",if(OFFSET(B3,3,0)="",if(OFFSET(B3,4,0)="",if(OFFSET(B3,5,0)="",5,5),4),3),2),1),0)
Another column (H) sums up the product values with this: =IF(G3>0,SUM(OFFSET(D3,0,0,G3,1)),"")
Help me rework the G column formula which calculates the number of products. If there's any way I can consolidate G and H that would be great too.
Note: the (I) column is just an alternative to (H) column.
P.S. Please don't flag this as an opinion based question. This is purely a problem solving question.
Since you are ok with the option of a helper column off to the side or hidden, we can do the following.
In column K starting in row 3 I placed this formula:
=IF(A3<>"",A3,K2)
You can actually use whatever column suits you just remember to update the column references in subsequent formulas. It generates a column of invoice numbers with no spaces which allows some other formulas to work much easier for us.
In column L startin in row 3 I placed this formula:
IF(COUNTIF($K$3:K3,K3)=1,COUNTIF(K:K,K3),0)
This gives the same results as column G. The first part of the IF statement is checking to see if the invoice number is the first occurrence of the invoice number. If it is count how many times the invoice number occurs, otherwise display 0.
Now if you want to skip counting how many items there are in an invoice you can use the sumproduct formula as follows:
=IF(A3<>"",SUMPRODUCT(($K$3:$K$12=A3)*$D$3:$D$12),"")
now to account for a variable sized list of invoices we will count the number of invoices and adjust our formula with an offset to return the appropriate ranges as follows:
=IF(A3<>"",SUMPRODUCT((OFFSET($K$3,0,0,COUNT(K:K),1)=A3)*OFFSET($D$3,0,0,COUNT(K:K),1)),"")
Since we are using COUNT(K:K) it is imperative that no numbers be entered in this column other than those generated by our formula.
This treats items inside the brackets as an array, without the formula itself being an array. The whole thing is placed inside an IF statement so that empty cells are displayed instead of zeros in the rows that do not correspond to an invoice number in column A.
now if you want to understand how sumproduct works in this case, its basically generating a an array filled with 1 or 0 representing true or false and then multiplying it by an array of the same size that is filled with all your amounts. So anything multiplied by 0 is 0 and anything multiplied by 1 is amount. The final step of sumproduct is to add up all the values. So you will only get the sum of what ever is true or 1.
If you are able to utilise VBA, you could use a User Defined Function. Insert this code into a new module and call it like you would a normal excel function:
Public Function InvoiceDetail(Invoice As Range, ReturnType As Integer)
Dim varCount As Long
Dim varSheet As Worksheet
Dim varInvoiceID As String
Dim varPartyName As String
Dim varInvoiceTotal As Double
Dim varInvoiceCount As Integer
Set varSheet = ThisWorkbook.Sheets(Invoice.Parent.Name)
If varSheet.Range("A" & Invoice.Row).Value <> "" Then
varInvoiceID = varSheet.Range("A" & Invoice.Row).Value
varPartyName = varSheet.Range("B" & Invoice.Row).Value
For varCount = Invoice.Row To 1000000
If varSheet.Range("D" & varCount).Value = "" Then
Exit For
End If
If varInvoiceID = varSheet.Range("A" & varCount).Value And varPartyName = varSheet.Range("B" & varCount).Value Then
varInvoiceTotal = varInvoiceTotal + varSheet.Range("D" & varCount).Value
varInvoiceCount = varInvoiceCount + 1
ElseIf varSheet.Range("A" & varCount).Value = "" And varSheet.Range("B" & varCount).Value = "" Then
varInvoiceTotal = varInvoiceTotal + varSheet.Range("D" & varCount).Value
varInvoiceCount = varInvoiceCount + 1
Else
Exit For
End If
Next
End If
Set varSheet = Nothing
Select Case ReturnType
Case 1 '// Count Only
InvoiceDetail = varInvoiceCount
Case 2 '// Total Only
InvoiceDetail = varInvoiceTotal
Case 3 '// Total [Count]
InvoiceDetail = varInvoiceTotal & " [" & varInvoiceCount & "]"
Case Else
InvoiceDetail = "Error"
End Select
End Function
This code obviously assumes that your Invoice ID is in Column A, your Party Name is in Column B, and your Amount is in Column D. I've implemented a few options for you too:
=InvoiceDetail(A3,1) returns the number of items on the invoice (as integer)
=InvoiceDetail(A3,2) returns the sum of items on the invoice (as double)
=InvoiceDetail(A3,3) returns both sum and [count] (as string)
I was able to solve this with 2 arrayformulas.
Paste this formula in any corresponding cell, let it be O3:
=TRANSPOSE(SPLIT(JOIN("",ArrayFormula(REPT(FILTER(A3:A12,A3:A12>0)&"-",
len(TRANSPOSE(SPLIT(REGEXREPLACE(JOIN(",",A3:A12)&",","\d+","-"),"-")))))),"-"))
And this formula in cell P3:
=ArrayFormula(if(A3:A>0,SUMIF(O3:O,O3:O,D3:D),""))
My sample file.
To make the first formula work with different ranges:
replace A3:A12 to offset(A3,,,counta(C3:C))

Merging data in google sheets from multiple sheets with varying number of rows

I have multiple google sheets with each containing variable number of rows but fixed set of columns. The number of rows can change in any sheet with time. How can i create summary sheet with data from all sheets placed one after the other? eg. If sheet 1 has 5 rows and sheet 2 has 6, i want the summary sheet to have 11 rows with 1-5 containing data from sheet 1 and 6-11 data from sheet 2.
= {
FILTER(NamedRange1, NamedRange1Col1 <> '');
Filter(NamedRange2, NamedRange1Col1 <> '')
}
Please create a named range for each sheet. This ranges will comntain your data.
Within each named range, also create a named range column. This can be the first column of every named range, or any column.
We are using the filter function FILTER so that you do not import empty rows. You dont have to use it though. The curly braces are semicolon are the crucial parts. In general, you "stack" multiple arrays as follows:
= {Array1; Array2;...;Array n}
the correct syntax is:
={FILTER(Sheet1!A:C; Sheet1!B:B<>"");
FILTER(Sheet2!A:C; Sheet2!B:B<>"")}
and another solution would be:
=QUERY({Sheet1!A:C; Sheet2!A:C}; "where Col2 is not null", 0)

Resources