VBA: transpose the table with dates - excel

I'm novice in VBA. I'm trying to transpose my data:
I would like to have this results:
I tried all day the methodes like: Resize(UBound(Table2, 1), UBound(Table2, 2)) = Table2, Application.transpose(Tbl1) but I don't have the diserid result. Could you help me please? Thank you very much!

A Power Query Solution (CVR)
Added corrections; credits to Ron Rosenfeld.
Click into your table.
Select Data > From Table/Range: The Power Query Editor opens containing your data.
The first columns is selected. If not, click the header of your first column (Date) to select it.
Select Transform > Pivot Column: The Pivot Column window opens.
In the Values Column combo box the second column (Values) is already selected. If not, select it.
Click Advanced Options where Sum is already selected which will sum multiple entries for the same ID/Date columns. If not, select it.
Press OK. The data is transformed.
Select Home > Close & Load: The Power Query Editor closes and the transformed data is presented in a table in a new worksheet.
Short Version
Click into your table.
Select Data > From Table/Range: The Power Query Editor opens containing your data.
Select Transform > Pivot Column: The Pivot Column window opens.
Press OK. The data is transformed.
Select Home > Close & Load: The Power Query Editor closes and the transformed data is presented in a table in a new worksheet.

With Office365 you can use below formulas (as per my screenshot).
F2=UNIQUE(C1:C11)
G1=TRANSPOSE(SORT(UNIQUE(A1:A10)))
G2=FILTER($B$1:$B$11,($C$1:$C$11=$F2)*($A$1:$A$11=G$1),"")
After putting FILTER() formula to G2 cell drag across right and down as needed.
You can also use XLOOKUP() instead of FILTER() formula to G2 like-
=XLOOKUP(1,($A$1:$A$11=G$1)*($C$1:$C$11=$F2),$B$1:$B$11,"")

Pivot CVR
CVR: Column Labels, Values, Row Labels.
It is assumed that the initial data, the Source Range, contains a row of headers, whose third cell value will be copied to the first cell of the resulting data, the Destination Range.
Adjust the values in the constants section.
Copy the complete code to a standard module, e.g. Module1.
Only run the first procedure, pivotDataCVR, the other two are being called by it, when necessary.
A similar solution, which I based this solution on, although RCV, can be found here.
The Code
Option Explicit
Sub pivotDataCVR()
' Define constants.
Const srcName As String = "Sheet1"
Const srcFirst As String = "A1"
Const dstName As String = "Sheet2"
Const dstFirst As String = "A1"
' Define workbook.
Dim wb As Workbook
Set wb = ThisWorkbook
' Define Source First Cell Range.
Dim cel As Range
Set cel = wb.Worksheets(srcName).Range(srcFirst)
' Define Source Range.
Dim rng As Range
With cel.CurrentRegion
Set rng = cel.Resize(.Rows.Count + .Row - cel.Row, _
.Columns.Count + .Column - cel.Column)
End With
' Get unique values.
Dim dts As Variant
dts = getUniqueColumn1D(rng.Columns(1).Resize(rng.Rows.Count - 1).Offset(1))
sort1D dts
Dim idx As Variant
idx = getUniqueColumn1D(rng.Columns(3).Resize(rng.Rows.Count - 1).Offset(1))
sort1D idx
' Write values from Source Range to Source Array.
Dim Source As Variant
Source = rng.Value
' Define Destination Array.
Dim Dest As Variant
ReDim Dest(1 To UBound(idx) - LBound(idx) + 2, _
1 To UBound(dts) - LBound(dts) + 2)
' Write values from arrays to Destination Array.
Dest(1, 1) = Source(1, 3)
Dim n As Long
Dim i As Long
i = 1
For n = LBound(idx) To UBound(idx)
i = i + 1
Dest(i, 1) = idx(n)
Next n
Dim j As Long
j = 1
For n = LBound(dts) To UBound(dts)
j = j + 1
Dest(1, j) = dts(n)
Next n
For n = 2 To UBound(Source, 1)
i = Application.Match(Source(n, 3), idx, 0) + 1
j = Application.Match(Source(n, 1), dts, 0) + 1
Dest(i, j) = Source(n, 2)
Next n
' Define Destination First Cell Range.
Set cel = wb.Worksheets(dstName).Range(dstFirst)
' Define Destination Range.
Set rng = cel.Resize(UBound(Dest, 1), UBound(Dest, 2))
' Write from Destination Array to Destination Range.
rng.Value = Dest
' Inform user.
MsgBox "Data transferred.", vbInformation, "Success"
End Sub
' Returns the unique values from a column range in a 1D array.
Function getUniqueColumn1D(ColumnRange As Range, _
Optional ByVal Sorted As Boolean = False) _
As Variant
Dim Data As Variant
Data = ColumnRange.Columns(1).Value
With CreateObject("Scripting.Dictionary")
.CompareMode = vbTextCompare
Dim Key As Variant
Dim i As Long
For i = 1 To UBound(Data, 1)
Key = Data(i, 1)
If Not IsError(Key) And Not IsEmpty(Key) Then
.Item(Key) = Empty
End If
Next i
If .Count > 0 Then
getUniqueColumn1D = .Keys
End If
End With
End Function
' Sorts a 1D array only if it contains values of the same data type.
Sub sort1D(ByRef OneD As Variant, _
Optional ByVal Descending As Boolean = False)
With CreateObject("System.Collections.ArrayList")
Dim i As Long
For i = LBound(OneD) To UBound(OneD)
.Add OneD(i)
Next i
.Sort
If Descending Then
.Reverse
End If
OneD = .ToArray
End With
End Sub

Related

How to get the cell address if I used a VBA function on such cell?

I want to use a VBA function ScopeSum() in an Excel table, such function is checking the "1" values on the same row & then sum relevant header's values.
"SopeH" is named header range.
I've to use this function on the same column (column "P" for the below example) for 100's of rows.
If I copy the function cell & fill all rows the result is as the first cell, but if I edit it, it works fine.
Function ScopeSum() As String
Dim i As Integer
Dim j As Long
Dim rng As Range
Dim cur_rng As Range
Dim ScopeText As String
Dim cell As Variant
Set rng = Range("ScopeH")
j = Range(ActiveCell.Address).Row
Set cur_rng = Range("ScopeH").Offset(j - 2, 0)
i = 0
ScopeText = ""
For Each cell In cur_rng.Cells
i = i + 1
If UCase(cell.Value) = 1 Then ScopeText = ScopeText & ", " & Application.WorksheetFunction.Index(rng, 1, i)
Next
ScopeSum = ScopeText
End Function
Excel Table
After refreshing the page
Make sure you submit the data and header ranges as parameters, so the UDF (user defined function) works for any data range and depends on the data range. Otherwise your formula would not update automatically if the data changes.
Option Explicit
Public Function ScopeSum(ByVal DataRange As Range, ByVal HeaderRange As Range) As String
Dim Data() As Variant ' read data into array
Data = DataRange.Value
Dim Header() As Variant ' read header into array
Header = HeaderRange.Value
Dim Result As String ' collect results for output here
Dim iCol As Long
For iCol = 1 To UBound(Data, 2) ' loop through data and concatenate headers
If Data(1, iCol) = 1 Then
Result = Result & IIf(Result <> vbNullString, ", ", vbNullString) & Header(1, iCol)
End If
Next iCol
ScopeSum = Result ' output results
End Function
Then use the following formula in cell P3:
=ScopeSum(Q3:Z3,$Q$2:$Z$2)
Make sure the header is fixed with the $ signs in the formula. And copy it down:
This has the advantage that you never need to change the code, even if the ranges change. Also you could easily add an Item 11 without changing the code by just adjusting the ranges in the formula.

Excel IF Statement Limited

I am using an IF statement in Excel to search for portions of text in the previous column in order to assign a supplier and category to the expense.
Supplier Column
=IF(ISNUMBER(SEARCH("tit",[#Description])),"TITAN",IF(ISNUMBER(SEARCH("Sol",[#Description])),"Soltrack",IF(ISNUMBER(SEARCH("coin",[#Description])),"Coin",IF(ISNUMBER(SEARCH("gree",[#Description])),"Green Dream Projects",IF(ISNUMBER(SEARCH("sars V",[#Description])),"SARS VAT",IF(ISNUMBER(SEARCH("sars p",[#Description])),"SARS PAYE",IF(ISNUMBER(SEARCH("acb",[#Description])),"Debit Order","")))))))
Category Column
the next column then has the following to get the category of the supplier
=IF(ISNUMBER(SEARCH("TITAN",[#Payee])),"Direct Operating Cost",IF(ISNUMBER(SEARCH("Soltrack",[#Payee])),"Direct Operating Cost",IF(ISNUMBER(SEARCH("Coin",[#Payee])),"Direct Operating Cost",IF(ISNUMBER(SEARCH("Green Dream Projects",[#Payee])),"Direct Operating Cost",IF(ISNUMBER(SEARCH("SARS VAT",[#Payee])),"VAT",IF(ISNUMBER(SEARCH("SARS PAYE",[#Payee])),"PAYE",IF(ISNUMBER(SEARCH("Debit Order",[#Payee])),"Debit Order","")))))))
this is working great, but seems i have reached the limit (7) of IF statements I can use in one formula?
I have created the below function to search for text "tit" and if it matches it updates the Payee column.
'excel if range of cells contains specific text vba
Sub MacroToCheckIfRangeOfCellsContainsSpecificText_vba()
Set Rng = Range("B2:B572") ' You can change this
specificText = "*tit*" ' You can change this
For Each Cell In Rng.Cells
If UCase(Cell.Value) Like "*" & UCase(specificText) & "*" Then
Cell.Offset(0, 1) = "Titan"
Else
Cell.Offset(0, 1) = ""
End If
Next
End Sub
Would I need to create a new specificText = "*tit*" for each of the keywords and also a whole section for each of the "For Each" functions?
Dictionary Solution
The first idea is to use a dictionary Replacements and add all the serach/replace pairs there. This has one huge disadvantage. It is against the good practice to not mix logic (code) and data. Good practice would be to put the data not into the code but into a worksheet (see next solution).
Option Explicit
Public Sub MacroToCheckIfRangeOfCellsContainsSpecificText_vba()
Dim RngToCheck As Range
Set RngToCheck = ThisWorkbook.Worksheets("Sheet1").Range("B2:B572") ' specify in which workbook and worksheet
Dim Replacements As Object
Set Replacements = CreateObject("Scripting.Dictionary")
With Replacements
.Add "tit", "Titan"
.Add "sol", "Soltrack"
'add more here
End With
Dim InputValues() As Variant
InputValues = RngToCheck.Value 'read input values into array
Dim OutputValues() As Variant 'create an output array (same size as RngToCheck)
ReDim OutputValues(1 To RngToCheck.Rows.Count, 1 To 1)
Dim iRow As Long
For iRow = 1 To UBound(OutputValues, 1)
Dim Key As Variant
For Each Key In Replacements.Keys
If UCase(InputValues(iRow, 1)) Like "*" & UCase(Key) & "*" Then
OutputValues(iRow, 1) = Replacements(Key)
Exit For 'we don't need to test for the others if we found a key
End If
Next Key
Next iRow
'write output values from array next to input values in the cells
RngToCheck.Offset(ColumnOffset:=1).Value = OutputValues
End Sub
Worksheet Solution
The better solution would be to create a new worksheet Replacements as below:
This can easily be edited by anyone and you don't need to fiddle with the code later if you want to delete or add pairs.
Public Sub ImprovedCheckUsingWorksheet()
Dim RngToCheck As Range
Set RngToCheck = ThisWorkbook.Worksheets("Sheet1").Range("B2:B572") ' specify in which workbook and worksheet
Dim Replacements() As Variant 'read replacements from worksheet
Replacements = ThisWorkbook.Worksheets("Replacements").Range("A2", ThisWorkbook.Worksheets("Replacements").Cells(Rows.Count, "B").End(xlUp)).Value 'read input values into array
Dim InputValues() As Variant
InputValues = RngToCheck.Value 'read input values into array
Dim OutputValues() As Variant 'create an output array (same size as RngToCheck)
ReDim OutputValues(1 To RngToCheck.Rows.Count, 1 To 1)
Dim iRow As Long
For iRow = 1 To UBound(OutputValues, 1)
Dim rRow As Long
For rRow = 1 To UBound(Replacements, 1)
If UCase(InputValues(iRow, 1)) Like "*" & UCase(Replacements(rRow, 1)) & "*" Then
OutputValues(iRow, 1) = Replacements(rRow, 2)
Exit For 'we don't need to test for the others if we found a key
End If
Next rRow
Next iRow
'write output values from array next to input values in the cells
RngToCheck.Offset(ColumnOffset:=1).Value = OutputValues
End Sub
For a 3ʳᵈ column in your replacements worksheet you would need to adjust the following line to be until column "C":
Replacements = ThisWorkbook.Worksheets("Replacements").Range("A2", ThisWorkbook.Worksheets("Replacements").Cells(Rows.Count, "C").End(xlUp)).Value 'read input values into array
and the output values need another column too (second parameter needs to go 1 To 2):
ReDim OutputValues(1 To RngToCheck.Rows.Count, 1 To UBound(Replacements, 2) - 1) 'this works for any amount of columns as it reads the column count from the `Replacements`
the data needs to be written
OutputValues(iRow, 1) = Replacements(rRow, 2) 'first output column
OutputValues(iRow, 2) = Replacements(rRow, 3) 'second output column
and writing the output values needs to be adjusted too:
RngToCheck.Offset(ColumnOffset:=1).Resize(ColumnSize:=UBound(OutputValues, 2)).Value = OutputValues 'this works for any amount of columns as it reads the column count from `OutputValues`
Formula Solution
But if you have your data in a worksheet Replacements like above, and you don't rely an a partial match. Then you don't need VBA and can easily use a formula instead to look it up:
=IFERROR(INDEX(Replacements!B:B,MATCH(B:B,Replacements!A:A,0)),"")

Excel VBA Simulating "Not In" SQL functionality

All -
I have a 2 sheet excel.
Sheet 1 is three columns (name, date, value)
Sheet 2 is name.
I want to write a VBA script that displays all of Sheet 1 data that does NOT have any of the name field listed in Sheet 2 anywhere in sheet 1 (name can appear in different columns so ideally it would search all cells in Sheet 1) to appear in sheet 3
See the sample image for a rough idea of what I"m hoping to accomplish. I have searched but have not had luck.
If you have Excel 365 you can use the Dynamic Array formulas
=LET(Names,FILTER(Sheet1!$C:$E,Sheet1!$C:$C<>""),FILTER(Names,ISERROR(MATCH(INDEX(Names,,1),Sheet2!$G:$G,0))))
Example:
Data (Sheet1)
Exclusion List (Sheet2)
Result
Note: this excludes the headers because the header label Name is present in both the Data column and the Exclusion column so be sure to maintain that
Without Excel 365. I'd recommend a UDF
Function FilterList(ByVal Data As Range, ByVal Exclusion As Range) As Variant
Dim Res As Variant
Dim Dat As Variant
Dim Excl As Variant
Dim rw As Long
Dim idx As Long
Dim cl As Long
Dim ExcludeIt As Variant
Dim Cols As Long
Dim TopRow As Long
ReDim Res(1 To Application.Caller.Rows.Count, 1 To Application.Caller.Columns.Count)
If IsEmpty(Data.Cells(1, 1)) Then
TopRow = Data.Cells(1, 1).End(xlDown).Row
Set Data = Data.Resize(Data.Rows.Count - TopRow).Offset(TopRow - 1)
End If
If IsEmpty(Data.Cells(Data.Rows.Count, 1)) Then
Set Data = Data.Resize(Data.Cells(Data.Rows.Count, 1).End(xlUp).Row - Data.Row + 1)
End If
Dat = Data.Value
Excl = Exclusion.Columns(1).Value
Cols = Application.Min(UBound(Dat, 2), UBound(Res, 2))
idx = 0
For rw = 1 To UBound(Dat, 1)
ExcludeIt = Application.Match(Dat(rw, 1), Excl, 0)
If IsError(ExcludeIt) Then
idx = idx + 1
For cl = 1 To Cols
Res(idx, cl) = Dat(rw, cl)
Next
End If
Next
For rw = 1 To UBound(Res, 1)
For cl = IIf(rw <= idx, UBound(Dat, 2) + 1, 1) To UBound(Res, 2)
Res(rw, cl) = vbNullString
Next
Next
FilterList = Res
End Function
Enter it as an Array Formula (complete it with Ctrl+Shift+Enter) in a range large enough to hold the returned data (can be larger), and pass it your input Data range and Exclusion range (both as whole columns)
=FilterList(Sheet1!$C:$E,Sheet2!$G:$G)
Welcome to Stack Overflow!
You did not say where the source table and criteria table begin, or where to place the result of the "anti-filter". I wrote this code on the assumption that they all start at the first cell of the worksheet, A1:
Sub AntiFilter()
Dim aSource As Range, aCriteria As Range, oCell As Range, oTarget As Range, countCells As Long
Set aSource = Worksheets("Sheet1").Range("A1").CurrentRegion
countCells = aSource.Columns.Count
Set aCriteria = Worksheets("Sheet2").Range("A1").CurrentRegion
Set oTarget = Worksheets("Sheet3").Range("A1")
aSource.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=aCriteria, Unique:=False
For Each oCell In Application.Intersect(aSource, aSource.Columns(1))
If oCell.RowHeight < 1 Then
oCell.Resize(1, countCells).Copy Destination:=oTarget
Set oTarget = oTarget.Offset(1, 0)
End If
Next oCell
On Error Resume Next
aSource.Worksheet.ShowAllData
On Error GOTO 0
End Sub
Workbook with macro, test data and examples of selection criteria on Sheet2
If the macro does not work as expected, make sure that you have sheets named Sheet1, Sheet2, and Sheet3 in your workbook, and that the source data range and criteria range start with cells A1. If this is not the case, make the necessary changes to the text of the macro:

Excel: Click cell to open the data to another sheet

Here's my draft data
Sheet_name: "FIRST"
The data represents the Pass and fail of X & Y fields. If the Area fails when it comes to X it will be mark as F and if it pass, the field X will mark as P. Same procedure to Field Y
And
Sheet_name: "SECOND"
Here's the summary of the Sheet: "FIRST"
It calculates the counts of passes and fails.
Using the idea of Countif Function.
=COUNTIF(FIRST!B2:B5,"P")
=COUNTIF(FIRST!C2:C5,"F")
What I'm trying to do is,
When you try to click the counts of passes and fails. It will redirect you to new sheet where the sheet gives the data who are the areas passed and failed.
Example:
If I click the "3" under the field of Passed
It will give me something like this,
| X |
Area1 | p |
Area2 | p |
Area4 | p |
Sorry, this one is not my project, homework, or exam.
I just need to understand the logic of opening the data when you click a cell.
Cell-Click to Another Sheet
Copy the code into the Sheets("SECOND") sheet code (in VBA
double-click on "SECOND") and rename a sheet as "THIRD".
In sheet THIRD there will be 2 columns with headers AREA and X. The
headers are excluded from ClearContents.
Below the results will be either for Pass or Fail depending on which
cell was 'clicked' (selected) at the moment.
The Code
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Const cStrPass As String = "A3" ' Pass Cell Range
Const cStrFail As String = "B3" ' Fail Cell Range
If Target = Range(cStrPass) Then
CellClick Range("A3")
End If
If Target = Range(cStrFail) Then
CellClick Range("B3")
End If
End Sub
Sub CellClick(CellRange As Range)
Const cVntName1 As Variant = "FIRST"
Const cVntName3 As Variant = "THIRD"
Dim vntSrc As Variant ' Source Array
Dim vntTgt As Variant ' Target Array
Dim lngLastRow As Long ' Source Last Row
Dim i As Long ' Source Row Counter
Dim k As Long ' Target Row Counter
Dim j As Integer ' Source/Target Column Counter
Dim strPF As String ' PassFail String
' Paste Source Range into Source Array.
With Worksheets(cVntName1)
lngLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
vntSrc = .Range("A2", .Cells(lngLastRow, "B"))
End With
' Determine PassFail String.
If CellRange.Column = 1 Then
strPF = "P"
Else
strPF = "F"
End If
' Count rows for Target Array
For i = 1 To UBound(vntSrc)
If vntSrc(i, 2) = strPF Then
k = k + 1
End If
Next
' Write data to Target Array
ReDim vntTgt(1 To k, 1 To 2)
k = 0
For i = 1 To UBound(vntSrc)
If vntSrc(i, 2) = strPF Then
k = k + 1
For j = 1 To UBound(vntSrc, 2)
vntTgt(k, j) = vntSrc(i, j)
Next
End If
Next
' Paste Target Array into Target Range.
With Worksheets(cVntName3)
.Range("A2", "B" & .Rows.Count).ClearContents
.Range("A2").Resize(UBound(vntTgt), UBound(vntTgt, 2)) = vntTgt
.Select
End With
End Sub

Loop through cells; Copy if not blank; Paste in sheet2 unformatted

I've been sitting with this problem for a few hours and would be very thankful if anyone here could assist me.
What I want to do:
For all cells A10:A180 in sheet1
If cell contains a date on the form YYYY-MM-DD
Copy cell and the two next cells to the right (e.g. A11:A13)
Remove all formatting, so as to only copy the value/string of the cell.
Paste at end of column in sheet2
When finished, sort the entries (whole row) by date
Any thoughts?
Best regards
Dean
Edit: copy&pasted code from comments:
Private Sub Worksheet_Activate()
Sheet2.Cells.Clear
Dim R1 As Range, R2 As Range
Dim wsFrom As Worksheet, wsTo As Worksheet
Set wsFrom = ThisWorkbook.Sheets("Blad1")
Set wsTo = ThisWorkbook.Sheets("Blad2")
Set R1 = wsFrom.Range("A:B")
Set R2 = wsTo.Range("A:B")
R1.Copy R2
End Sub
There are some unclear points in your question, but following code should get you started how to properly load, manipulate and paste values between ranges using VBA arrays:
Option Explicit
Sub copy_nonblank()
Dim data() As Variant ' () creates an array instead of a simple variable
Dim row_count, col_count, r, c, shift As Integer
'load data from specified range into an array
data = ThisWorkbook.Sheets("Blad1").Range("A10:C180").Value2
' iterate through rows and shift data up to fill-in empty rows
row_count = UBound(data, 1)
col_count = UBound(data, 2)
shift = 0
For r = 1 To row_count
If IsEmpty(data(r, 1)) Then
shift = shift + 1
ElseIf shift > 0 Then
For c = 1 To col_count
data(r - shift, c) = data(r, c)
Next c
End If
Next r
' delete values, but not formatting
ThisWorkbook.Sheets("Blad2").Cells.ClearContents
' paste special as values, but only the shifted non-empty rows
ThisWorkbook.Sheets("Blad2").Range("A10") _
.Resize(r - shift - 1, col_count) _
.Value2 = data
End Sub
You will need to specify the formatting on the output sheet manually, but only once.

Resources