Mapping table with multiple items - vba

I have a mapping table which I use for matching headers of two separate sheets (Sheet1 and Sheet2).
But what if I have something like this (3 columns in the left part, 2 columns in the right):
Basically I want POS1 2019 EMP1 to be equal to HR DEPARTMAENT Employee1 and so on.
Sheet1,
Sheet2,
Mapping
Any ideas how can I do it?
Thank you in advance! :)
Public Sub test()
Application.ScreenUpdating = False
stack "Sheet1", "Sheet2", "Mapping"
Application.ScreenUpdating = True
End Sub
Public Sub stack(ByVal Sheet1 As String, ByVal Sheet2 As String, ByVal
Mapping As String)
Dim rng As Range, trgtCell As Range, src As Worksheet, trgt As
Worksheet, helper As Worksheet
Dim rngSrc As Range, rngDest As Range
Dim sht As Worksheet
Set src = Worksheets("Sheet1")
Set trgt = Worksheets("Sheet2")
Set helper = Worksheets("Mapping")
With src
For Each rng In Intersect(.Rows(3), .UsedRange).SpecialCells(xlCellTypeConstants)
Dim lkup As Variant
With helper
lkup = Application.VLookup(rng.Value, .Range("D13:E" & .Cells(.Rows.Count, "D").End(xlUp).Row), 2, False)
End With
If Not IsError(lkup) Then
Set trgtCell = trgt.Range("$B$2:$F$7").Find(lkup, LookIn:=xlValues, lookat:=xlWhole)
If Not trgtCell Is Nothing Then
.Range(rng.Offset(1), .Cells(.Rows.Count, rng.Column).End(xlUp)).Copy
With trgt
.Range(Split(trgtCell.Address, "$")(1) & 3).PasteSpecial
End With
End If
End If
Next rng
End With
End Sub

I think dictionaries are the data structure most suited to this type of problem.
Be aware, to use dictionaries in VBA you need to set a reference to the Scripting Runtime library.
Tools->Reference-> Microsoft Scripting Runtime
Here is some code that works on the example you provided:
Public Sub test()
Application.ScreenUpdating = False
stack2 "Sheet1", "Sheet2", "Mapping"
Application.ScreenUpdating = True
End Sub
Public Sub stack(ByVal Sheet1 As String, ByVal Sheet2 As String, ByVal Mapping As String)
Dim rng As Range, src As Worksheet, trgt As Worksheet, helper As Worksheet
Dim sht As Worksheet
Dim dctCol As Dictionary, dctHeader As Dictionary
Dim strKey1 As String, strKey2 As String
Dim strItem As String, col As Integer
Set src = Worksheets(Sheet1)
Set trgt = Worksheets(Sheet2)
Set helper = Worksheets(Mapping)
'build a dictionary to lookup column based on 3 rows of headers
Set dctCol = New Dictionary
arr1 = src.Range("A1:F7") 'arrays are way faster than ranges
For j = 2 To UBound(arr1, 2) 'loop over data from columns B-F
strKey1 = Trim(arr1(1, j)) & "," & Trim(arr1(2, j)) & "," & Trim(arr1(3, j)) 'comma delimit string
dctCol(strKey1) = j 'j is the column number
Next
'build a dictionary to translate 2 headers to 3 headers
Set dctHeader = New Dictionary
arrHelp = helper.Range("A2:E6")
For i = 1 To UBound(arrHelp)
strKey2 = Trim(arrHelp(i, 4)) & "," & Trim(arrHelp(i, 5)) '2 header key
strItem = Trim(arrHelp(i, 1)) & "," & Trim(arrHelp(i, 2)) & "," & Trim(arrHelp(i, 3))
dctHeader(strKey2) = strItem
Next
'update sheet2 with numbers from sheet1
arr2 = trgt.Range("A1:F6")
For j = 2 To 5
'work backwards to find the column
strKey2 = Trim(arr2(1, 2)) & "," & Trim(arr2(2, j)) '2 headers
strKey1 = dctHeader(strKey2)
col = dctCol(strKey1)
'update the data for arr2
For i = 3 To 6
arr2(i, j) = arr1(i + 1, col)
Next
Next
'write it back to spreadsheet
trgt.Range("M10").Resize(UBound(arr2), UBound(arr2, 2)) = arr2
End Sub

Please, try the next adapted code.
Public Sub test()
Application.ScreenUpdating = False
stack "Sheet1", "Sheet2", "Mapping"
Application.ScreenUpdating = True
End Sub
Public Sub stack(ByVal Sheet1 As String, ByVal Sheet2 As String, ByVal Mapping As String)
Dim rng As Range, trgtCell As Range, src As Worksheet, trgt As Worksheet, helper As Worksheet
Dim rngSrc As Range, rngDest As Range
Dim sht As Worksheet
Set src = Worksheets(Sheet1)
Set trgt = Worksheets(Sheet2)
Set helper = Worksheets(Mapping)
With src
For Each rng In Intersect(.Rows(3), .UsedRange).SpecialCells(xlCellTypeConstants)
Dim lkup As Variant
With helper
lkup = Application.VLookup(rng.Value, .Range("C2:E" & .Cells(.Rows.Count, "C").End(xlUp).Row), 3, False)
End With
If Not IsError(lkup) Then
Set trgtCell = trgt.Range("$B$2:$F$7").Find(lkup, LookIn:=xlValues, lookat:=xlWhole)
Debug.Print trgtCell.Address
If Not trgtCell Is Nothing Then
With trgt
.Range(trgtCell.Offset(1), .Cells(.Rows.Count, trgtCell.Column).End(xlUp)).Copy
End With
.Range(Split(trgtCell.Address, "$")(1) & 4).PasteSpecial
End If
End If
Next rng
End With
End Sub
Please, also correct the sub calling the second one. You mixed "Shee2" with "Sheet1" there...
Please, test it and send some feedback.

Related

VBA Add formula to specific columns and fill down to last row

I'm in the process of trying to automate my monthly reporting and I'm finally dipping my toe into VBA (by copying a bunch of stuff I see online and trying to make it work with my project).
I currently have a macro that inserts colums in Column A,H,O etc. and now I want another macro to insert a =CONCATENATE formula into each of them and fill down to last row with data (I'll then string these two macros together).
I currently have the following
Sub FillDown()
Dim strFormulas(1 To 5) As Variant
With ThisWorkbook.Worksheets("CommentsData")
strFormulas(1) = "=CONCATENATE(B1,C1)"
strFormulas(2) = "=CONCATENATE(I1,J1)"
strFormulas(3) = "=CONCATENATE(P1,Q1)"
strFormulas(4) = "=CONCATENATE(W1,X1)"
strFormulas(5) = "=CONCATENATE(AD1,AE1)"
.Range("A1,H1,O1,V1,AC1").Formula = strFormulas
.Range("A1,H1,O1,V1,AC1").FillDown
.Range("A:AG").NumberFormat = "General"
End With
End Sub
I'm getting a Runtime 1004 "Filldown method of Range class failed error with the Range Line being highlighted. I assume there is an issue with the way I'm trying to refer to multiple columns that aren't side by side (haven't been able to find examples of this online).
Any help is appreciated.
Follow up question; Once I have this working, I'll want to do it with other worksheets within the workbook as well, but it will be different columns. Do I need to create a new module or can I just paste the code again in the same module and alter the ranges/cell references? If so, which part do I copy/paste
In this specific example you could simplify to:
Option Explicit
Public Sub FillDown1()
Dim myColumns(), lastRow As Long, i As Long
myColumns = Array("A", "H", "O", "V", "AC")
With ThisWorkbook.Worksheets("CommentsData")
lastRow = .Cells(.Rows.Count, "B").End(xlUp).row 'Change this to a column which you can use to determine how far to add formulas to
For i = LBound(myColumns) To UBound(myColumns)
.Range(.Cells(1, myColumns(i)), .Cells(lastRow, myColumns(i))).FormulaR1C1 = "=CONCATENATE(RC[1],RC[2])"
Next i
.Range("A:AG").NumberFormat = "General"
End With
End Sub
Something closer to yours, but boy is it ugly looking:
Public Sub FillDown2()
Dim myColumns(), lastRow As Long, i As Long, myFormulas(1 To 5) As Variant
myColumns = Array("A", "H", "O", "V", "AC")
myFormulas(1) = ("B,C")
myFormulas(2) = ("I,J")
myFormulas(3) = ("P,Q")
myFormulas(4) = ("W,X")
myFormulas(5) = ("AD,AE")
If UBound(myColumns) + 1 <> UBound(myFormulas) Then MsgBox "Array length for myColumns doesn't match myFormulas": Exit Sub
With ThisWorkbook.Worksheets("CommentsData")
lastRow = .Cells(.Rows.Count, "B").End(xlUp).row 'Change this to a column which you can use to determine how far to add formulas to
For i = LBound(myColumns) To UBound(myColumns)
.Cells(1, myColumns(i)).Formula = "=CONCATENATE(" & Split(myFormulas(i + 1), ",")(0) & 1 & "," & Split(myFormulas(i + 1), ",")(1) & 1 & ")"
.Range(.Cells(1, myColumns(i)), .Cells(lastRow, myColumns(i))).FillDown
Next i
.Range("A:AG").NumberFormat = "General"
End With
End Sub
You could even shift the row (1) back up into the myFormulas array
Public Sub FillDown2()
Dim myColumns(), lastRow As Long, i As Long, myFormulas(1 To 5) As Variant
myColumns = Array("A", "H", "O", "V", "AC")
myFormulas(1) = ("B1,C1") '<==========================shifted row back up into array
myFormulas(2) = ("I1,J1")
myFormulas(3) = ("P1,Q1")
myFormulas(4) = ("W1,X1")
myFormulas(5) = ("AD1,AE1")
If UBound(myColumns) + 1 <> UBound(myFormulas) Then MsgBox "Array length for myColumns doesn't match myFormulas": Exit Sub
With ThisWorkbook.Worksheets("CommentsData")
lastRow = .Cells(.Rows.Count, "B").End(xlUp).row 'Change this to a column which you can use to determine how far to add formulas to
For i = LBound(myColumns) To UBound(myColumns)
.Cells(1, myColumns(i)).Formula = "=CONCATENATE(" & Split(myFormulas(i + 1), ",")(0) & "," & Split(myFormulas(i + 1), ",")(1) & ")"
.Range(.Cells(1, myColumns(i)), .Cells(lastRow, myColumns(i))).FillDown
Next i
.Range("A:AG").NumberFormat = "General"
End With
End Sub
you could try this:
Sub FillDown()
With ThisWorkbook.Worksheets("CommentsData")
.Range("A:A,H:H,O:O,V:V,AC:AC").Offset(, 1).SpecialCells(xlCellTypeConstants).Offset(, -1).FormulaR1C1 = "=CONCATENATE(RC[1],RC[2])"
.Range("A:AG").NumberFormat = "General"
End With
End Sub
to extend it to more worksheets:
Sub FillDownMoreSheets()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets(Array("CommentsData", "CommentsData2", "CommentsData3"))
With ws
.Range("A:A,H:H,O:O,V:V,AC:AC").Offset(, 1).SpecialCells(xlCellTypeConstants).Offset(, -1).FormulaR1C1 = "=CONCATENATE(RC[1],RC[2])"
.Range("A:AG").NumberFormat = "General"
End With
Next
End Sub
You should avoid naming your Subs, Functions and variables with reserved words
FillDown will hide the built-in Range.FillDown Method
This will work on all sheets defined in the constant at the top
The list in WS_RANGES is separated by a space and contains a sub list of
SheetName-Range-ColumnOffset (CommentsData-A1:AG-7)
ColumnOffset must be 3 or greater (for the formulas)
Option Explicit
Public Sub JoinColumns()
Const WS_RANGES = "CommentsData-A1:AG-7 CommentsData2-C2:AX-3" 'WSNames-Range-Offset
Dim wsSet As Variant, ws As Worksheet, ur As Range, cls As Range, i As Variant, c As Long
wsSet = Split(WS_RANGES)
For Each ws In ThisWorkbook.Worksheets
For Each i In wsSet
i = Split(i, "-")
If ws.Name = i(0) Then
Set ur = ws.Range(i(1) & ws.Cells(ws.Rows.Count, Split(i(1),":")(1)).End(xlUp).Row)
Set cls = ur.Columns(1)
For c = i(2) + 1 To ur.Columns.Count Step i(2)
Set cls = Union(cls, ur.Columns(c))
Next
cls.Formula = "=RC[1] & RC[2]"
ur.NumberFormat = "General"
Exit For
End If
Next
Next
End Sub

VBA TreeView_NodeCheck: Search for Match in Column and post in row beneath if empty

I am trying to work around the tough task to save selected nodes from a TreeView (tough due to my limited VBA knowledge) by first saving a UserID with a TextBox_AfterUpdate Event and subsequently saving the checked node's full path in the rows below when a match is found. Multiple checks are possible, that's why I approached it the way I did below.
I am working on this Problem for 2 working-days now and pray that one of you can help me out of my misery here haha
No Error is produced and a look at the Debugger Shows me that each column in found correctly. Also, the saving user ID via TextBox_AfterUpdate Event works like a treat and should stay this way. It simply does not copy it - please Help.
Thank you in advance!
Private Sub SuppNo_AfterUpdate()
'########Save SuppNo for CG-entry-saving########
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.Worksheets("Supplier Skills")
Dim lastcol As Long
With ws
lastcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
End With
'Copy Supplier No into Row 1 and next empty column
ws.Cells(1, lastcol).Offset(0, 1).Value = Me.SuppNo.Value
End Sub
'------------------------------------------------------------------------------------------
Private Sub CGTreeView_NodeCheck(ByVal Node As MSComctlLib.Node)
Dim wb As Workbook
Set wb = ActiveWorkbook
Dim ws As Worksheet
Set ws = wb.Worksheets("Supplier Skills")
Dim myNode As Node
'1. - - Copy Supplier No into Row 1 and next empty column
'Done in SuppNo_Change event
'2.1. - - Find matching entry
Dim aCell As Range
Dim col As Long, lRow As Long, i As Long
Dim colName, NodePath As String
strFind = Me.SuppNo
NodePath = Me.CGTreeView.SelectedItem.FullPath
With ws
Set aCell = .Range("A1:ZZ1").Find(What:=Me.SuppNo, LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, MatchCase:=False, SearchFormat:=False)
'~~> If Found
If Not aCell Is Nothing Then
col = aCell.Column
colName = Split(.Cells(, col).Address, "$")(1)
lRow = .Range(colName & .Rows.Count).End(xlUp).Row
'2.2. - - Find the last empty row and copy each new FullPath when checked
For i = 2 To 50
If Cells(i, col) Is Nothing Then
ws.Cells(i, col) = NodePath
i = i + 1
End If
Next i
'~~> If not found
Else
Exit Sub
End If
End With
End Sub
I added a few features to eliminate duplicates.
Private Sub SuppNo_AfterUpdate()
'########Save SuppNo for CG-entry-saving########
Dim IDColumn As Long
Dim dItems As Object
Dim c As Range, ItemsRange As Range
Dim n As Node
Set dItems = CreateObject("Scripting.Dictionary")
With ActiveWorkbook.Worksheets("Supplier Skills")
IDColumn = getSuppNoColumn
.Cells(1, IDColumn).Value = Me.SuppNo.Value
Set ItemsRange = .Range(.Cells(2, IDColumn), .Cells(.Rows.count, IDColumn).End(xlUp))
If Not ItemsRange Is Nothing Then
For Each c In ItemsRange
dItems(c.text) = vbNullString
Next
End If
End With
For Each n In CGTreeView.Nodes
n.Checked = dItems.exists(n.FullPath)
Next
End Sub
'------------------------------------------------------------------------------------------
Private Sub CGTreeView_NodeCheck(ByVal Node As MSComctlLib.Node)
Dim IDColumn As Long
Dim dItems As Object
Dim n As Node
If Me.SuppNo.Value <> "" Then
IDColumn = getSuppNoColumn
With ActiveWorkbook.Worksheets("Supplier Skills")
.Range(.Cells(1, IDColumn), .Cells(.Rows.count, IDColumn).End(xlUp)).Offset(1).Clear
Set dItems = CreateObject("Scripting.Dictionary")
For Each n In CGTreeView.Nodes
If n.Checked Then dItems(n.FullPath) = vbNullString
Next
If dItems.count > 0 Then .Cells(2, IDColumn).Resize(dItems.count) = Application.Transpose(dItems.Keys)
End With
End If
End Sub
Function getSuppNoColumn() As Long
Dim f As Range
With ActiveWorkbook.Worksheets("Supplier Skills")
Set f = .Range("1:1").Find(What:=Me.SuppNo, LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=False)
If f Is Nothing Then
getSuppNoColumn = IIf(.Cells(1, 1) = "", 1, .Cells(1, .Columns.count).End(xlToLeft).Offset(0, 1).Column)
Else
getSuppNoColumn = f.Column
End If
End With
End Function

How to use each value in a range to autofilter data in another sheet?

I'm a bit new to VBA, so was hoping to get some help on this page from you experts! I have the following requirement:
I have a table with unique values of Column2 in Sheet2(range)
I need to filter out data in Sheet1 using each value in the above range and save it in a separate sheet.
Snapshot of example attached:
enter link description here
So, how do I go about doing this?
Take a look at this (there is comments in the code to help you) :
Sub MKDev1()
Dim WsSrc As Worksheet, _
WsFilt As Worksheet, _
RgSrc As Range, _
RgFilt As Range, _
rCell As Range, _
ColumnToFilter As Integer, _
OutPutWs As Worksheet
'Define the name of your sheets
Set WsSrc = Sheets("Sheet1")
Set WsFilt = Sheets("Sheet2")
Set RgSrc = WsSrc.Range("A1:" & _
ColLet(WsSrc.Columns(WsSrc.Columns.Count).End(xlToLeft).Column) & _
WsSrc.Rows(WsSrc.Rows.Count).End(xlUp).Row)
'Change the column to the one in which you have the unique values
Set RgFilt = WsFilt.Range("A1:A" & Range("A" & WsFilt.Rows.Count).End(xlUp).Row)
'Set your column to filter (here "A"// 1st column)
ColumnToFilter = 1
If WsSrc.AutoFilterMode = False Then WsSrc.AutoFilterMode = True
For Each rCell In RgFilt
Set OutPutWs = DeleteAndAddSheet(rCell.Value)
RgSrc.AutoFilter Field:=ColumnToFilter, Criteria1:=rCell.Value
'2 ways of copying
'Less efficient (tested)
RgSrc.AutoFilter.Range.Copy Destination:=OutPutWs.Range("A1")
'More efficient (untested here)
'OutPutWs.Range("A1").Value = RgSrc.AutoFilter.Range.Value
'Get rid of previous filters
RgSrc.AutoFilter.ShowAllData
Next rCell
ThisWorkbook.Save
Set WsSrc = Nothing
Set WsFilt = Nothing
Set RgSrc = Nothing
Set RgFilt = Nothing
Set OutPutWs = Nothing
End Sub
Function to get the letter reference of a column from is index :
Public Function ColLet(x As Integer) As String
With ActiveSheet.Columns(x)
ColLet = Left(.Address(False, False), InStr(.Address(False, False), ":") - 1)
End With
End Function
Function to delete, create and set a new sheet from its name :
Public Function DeleteAndAddSheet(ByVal SheetName As String) As Worksheet
For Each aShe In Sheets
If aShe.Name <> SheetName Then
Else
Application.DisplayAlerts = False
aShe.Delete
Application.DisplayAlerts = True
Exit For
End If
Next aShe
Sheets.Add after:=Sheets(Sheets.Count)
Sheets(Sheets.Count).Name = SheetName
Set DeleteAndAddSheet = ThisWorkbook.Worksheets(Worksheets.Count)
End Function

Identifier too long

I am faced with this problem: "Identifier too long". What can I do to fix this? I tried using a break "_" . But there will another error: Missing end bracket. Thanks guys.
Sub Netting()
Dim Found As Range
Dim LR As Long
Dim ws As Worksheet
Dim cell As Range
Dim a As Variant, v As Variant, num
Set ws = Sheets("PAYABLES - OUTFLOWS")
Set Found = ws.Rows(1).Find(What:="Invoice Amount", _
LookIn:=xlValues, lookat:=xlWhole)
If Found Is Nothing Then Exit Sub
a = [{"991",1042;"916", 1042;"954",261;"975",3004;"938",726;"901",762;"482",728; _
"482",728;"934",723;"200",724;"201",724;"952",724;"992",3030;"980",3207;"116",626;"939",722;"390",517;"484",548;"339",59;"141",717;"935",59;"994",3370;"140",8408;"950",775;"370", 734 }] 'create 2-d lookup array
LR = ws.Cells(ws.Rows.Count, Found.Column).End(xlUp).Row
Found.Offset(0, 1).EntireColumn.Insert
ws.Cells(1, Found.Column + 1).Value = "Netting"
For Each cell In ws.Range(ws.Range("C2"), ws.Cells(LR, 3))
num = CStr(Mid(cell.Value, 3, 3))
v = Application.VLookup(num, a, 2, False)
cell.EntireRow.Cells(Found.Column + 1).Value = IIf(IsError(v), "", v)
Next cell
End Sub
There is a limit on the number of characters per line. You can accomplish this using a much simpler way. Add your data to a spreadsheet ("Sheet1" for example) and in column A and column B.
Sub Assign2DVector()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim a As Variant
a = ws.Range("A1:B20").Value ' set to whatever your actual range is
End Sub

Two VBA codes needed to paste in same unused row

I need these two sets of VBA to paste on same line instead of one line below each other.The problem is they both look for an unused row, which is needed, but they should be on the same unused row. Thanks.
Sub Macro10()
Dim refTable As Variant, trans As Variant
refTable = Array("A = B4", "B = B5", "C = J5")
Dim Row As Long
Row = Worksheets("Customer List").UsedRange.Rows.Count + 1
For Each trans In refTable
Dim Dest As String, Field As String
Dest = Trim(Left(trans, InStr(1, trans, "=") - 1)) & Row
Field = Trim(Right(trans, Len(trans) - InStr(1, trans, "=")))
Worksheets("Customer List").Range(Dest).Value = Worksheets("Order Entry").Range(Field).Value
Next
End Sub
Private Sub CommandButton1_Click()
Application.ScreenUpdating = False
Dim copySheet As Worksheet
Dim pasteSheet As Worksheet
Set copySheet = Worksheets("Order Entry")
Set pasteSheet = Worksheets("Customer List")
copySheet.Range("A8:K22").Copy
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 3).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
Here is the Macro that Calls both of these macros.
Sub SUBMIT()
Call Macro10
Call CommandButton1_Click
Call Check
Call Macro6
Call Macro12
End Sub
One solution is to create a function that funds the row and save the as a variable. Create a function that saves the row as an integer and use this value in both subs.
Function findEmptyRow() As Integer
Row = Worksheets("Customer List").UsedRange.Rows.Count + 1
End Function
Sub SUBMIT()
row = findEmptyRow()
Call Macro10(row)
Call CommandButton1_Click(row)
Call Check
Call Macro6
Call Macro12
End Sub
Amend Macro10 so it can accept variables
Sub Macro10(row As Integer)
Dim refTable As Variant, trans As Variant
refTable = Array("A = B4", "B = B5", "C = J5")
For Each trans In refTable
Dim Dest As String, Field As String
Dest = Trim(Left(trans, InStr(1, trans, "=") - 1)) & row
Field = Trim(Right(trans, Len(trans) - InStr(1, trans, "=")))
Worksheets("Customer List").Range(Dest).Value = Worksheets("Order Entry").Range(Field).Value
Next
End Sub
Then similar for CommandButton1_Click. This used a different method to find the empty row so it has to be adjusted slightly.
Private Sub CommandButton1_Click(row As Integer)
Application.ScreenUpdating = False
Dim copySheet As Worksheet
Dim pasteSheet As Worksheet
Set copySheet = Worksheets("Order Entry")
Set pasteSheet = Worksheets("Customer List")
copySheet.Range("A8:K22").Copy
pasteSheet.Cells(row, 1).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub