I have an Excel document with two different Sheets. Sheet 2 has columns header names and rows header names. Sheet 1 has some of these columns with exact header names and rows header names but it's filled with data.
enter image description here, enter image description here
I want to make a macro that will look through all the column/rows headers in Sheet 1 and find their corresponding match in Sheet2. When the match is found, I need to copy the entry of the Sheet column/row header into the matching header of sheet2. Some entries in Sheet2 will not have matches and will remain blank.
I want it to look like this:
enter image description here
This is my code so far, it is working for the column headers but I don't know how to add for row headers as well. Any help is welcomed :)
Sub CopyData()
Application.ScreenUpdating = False
Dim LastRow As Long, header As Range, foundHeader As Range, lCol As Long, srcWS As Worksheet, desWS As Worksheet
Set srcWS = Sheets("Sheet1")
Set desWS = Sheets("Sheet2")
LastRow = srcWS.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
lCol = desWS.Cells(3, Columns.Count).End(xlToLeft).Column
For Each header In desWS.Range(desWS.Cells(3, 2), desWS.Cells(3, lCol))
Set foundHeader = srcWS.Rows(2).Find(header, LookIn:=xlValues, lookat:=xlWhole)
If Not foundHeader Is Nothing Then
srcWS.Range(srcWS.Cells(3, foundHeader.Column), srcWS.Cells(LastRow, foundHeader.Column)).Copy desWS.Cells(4, header.Column)
End If
Next header
Application.ScreenUpdating = True
End Sub
You can use built-in Range.Consolidate method (https://learn.microsoft.com/en-us/office/vba/api/excel.range.consolidate):
(Edit2)
Option Explicit
Sub ConsolidateThis()
Dim rng1 As Range, rng2 As Range, addr As String
With ThisWorkbook
' determine source and destination ranges
Set rng1 = getTableRange(.Worksheets("Sheet1").Range("A2"))
Set rng2 = getTableRange(.Worksheets("Sheet2").Range("A3"))
' make full address of consolidated range like "'[Consolidate.xlsm]Sheet1'!R3C1:R6C5"
addr = "'[" & .Name & "]" & rng1.Parent.Name & "'!" & rng1.Address(ReferenceStyle:=xlR1C1)
' do consolidation
rng2.Consolidate Sources:=Array(addr), Function:=xlSum, TopRow:=True, LeftColumn:=True
End With
End Sub
' Returns the range that starts with the top left corner cell and is bounded
' on the right and bottom by empty cells
Function getTableRange(LeftTopCornerCell As Range) As Range
Dim ws As Worksheet, rightEdge As Long, downEdge As Long
With LeftTopCornerCell(1)
Set ws = .Parent
rightEdge = ws.Cells(.Row, ws.Columns.Count).End(xlToLeft).Column
downEdge = ws.Cells(ws.Rows.Count, .Column).End(xlUp).Row
End With
Set getTableRange = ws.Range(LeftTopCornerCell(1), ws.Cells(downEdge, rightEdge))
End Function
Your best solution might to set 2 ranges, each taking values from tables in Sheet1 and Sheet2. Let's call them rgSrcTable and rgDestTable. Then you need to loop using For Each through each range and compare top and left headers, and when you find a match, copy the value of the cell in rgSrcTable to the cell in rgDestTable.
Edit: Code sample. Feel free to adapt ranges to your needs. Since this routine used Range.Value property, you can filter any data (string, numbers, etc.)
Option Explicit
Sub CopyDataWithFilter()
Dim iRowHeader As Integer, iColHeader As Integer
Dim rngSrc As Range, rngDest As Range, celSrc As Range, celDest As Range
iRowHeader = 2
iColHeader = 1
With ThisWorkbook
' Set source and destination ranges. Modify ranges according to your needs
Set rngSrc = .Worksheets("shtSrc").Range("$B$3:$E$5")
Set rngDest = .Worksheets("shtDest").Range("$B$3:$E$5")
' Loop through source range and dest range
For Each celDest In rngDest
For Each celSrc In rngSrc
' Compare top headers and left headers respectively. If matching, copy the value in destination table.
If .Worksheets("shtSrc").Cells(celSrc.Row, iColHeader).Value = .Worksheets("shtDest").Cells(celDest.Row, iColHeader).Value And _
.Worksheets("shtSrc").Cells(iRowHeader, celSrc.Column).Value = .Worksheets("shtDest").Cells(iRowHeader, celDest.Column).Value Then
celDest.Value = celSrc.Value
End If
Next celSrc
Next celDest
End With
End Sub
Result:
Related
I have a workbook with Sheet1, Sheet2, Mapping. I want to copy values from Sheet1 to Sheet2 based on both row/column headers in both sheets. The column headers are not the same, so I've created a mapping table in "Mapping" Sheet to match those headers.
The problem is that those headers are made from multiple rows- see this:
For example I need that (POS1 2019 Emp1) = (HR Department Employee 1).
The following code it's working but only if the mapping table looks like this:
(and also I've not added rows headers, I'm not sure how).
So please do you have any idea how can I make the mapping with multiple headers work? Should I put those headers in two separate named ranges? Any advice will be appreciated :)
Sheet1:
Sheet2:
Result that I want:
Public Sub test()
Application.ScreenUpdating = False
stack "Sheet2", "Sheet1", "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("A2:B" & .Cells(.Rows.Count, "A").End(xlUp).Row), 2, False)
End With
If Not IsError(lkup) Then
Set trgtCell = trgt.Range("$B$2:$F$6").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 am working with the excel-vba, I have to sort the rows in ascending order with merged cells, I know that the merged cell cannot be sorted that is why, this work around is the only solution to my problem. I need to unmerged the cells then copy the value of the first cell and paste it to the second cell, after that, the code will sort the list using the A column and C column. and then after that if the A and C column has an equal value, it will turn to merged cell.
I hope someone could help me with this project.
Also view this image to see the list.
Sort
So, I constructed a code that will do this process but it cant.
Sub Sort()
On Error GoTo myErr
Dim myRange As Range
Dim lstrow As Long
Dim i As Integer
Dim cel As Range
Set myRange = Sheet1.Range("A2:C7")
lstrow = Sheet1.Cells(Rows.Count, 1).End(xlUp).Row
With myRange
.UnMerge
For Each cel In myRange
If IsEmpty(cel) Then
For i = 2 To lstrow
' cel(i).Value = 1
Sheet1.Range(i).Copy Sheet1.Range(cel).PasteSpecial
Sheet1.Range("C3").CurrentRegion.Sort _
key1:=Sheet1.Range("C3"), order1:=xlAscending, _
Header:=xlGuess
Next i
End If
Next cel
End With
myErr:
MsgBox "Unble to sort!"
End Sub
“No one is useless in this world who lightens the burdens of another. -Charles Dickens”
Regards,
If you are going to find lstRow before unmerging, use Column B — if the last row in Column A is merged, then the bottommost cell is empty! Or if you prefer, you can find lstRow after unmerging everything.
By looping through myRange you can both UnMerge any merged cells and populate the newly unmerged cells using the MergeArea.address of the original merged cell. After sorting on columns A and C, you can then loop through those columns, comparing each row to the row beneath. Only re-merge when both the row beneath is the same as the row above for both columns.
Option Explicit
Sub Sort()
Dim myRange As Range
Dim lstrow As Long
Dim l As Long
Dim rng As Range
Dim address As String
Dim contents As Variant
Dim ws As Worksheet
On Error GoTo myErr
Set ws = ThisWorkbook.Sheets("Sheet1")
Set myRange = ws.Range("A1:C7")
' Get lstrow from Column B, if Column A has merged cells
lstrow = ws.Cells(Rows.Count, 2).End(xlUp).Row
' Unmerge and populate
For Each rng In myRange
If rng.MergeCells Then
' Get value from top left cell
contents = rng.MergeArea.Cells(1).Value
address = rng.MergeArea.address
rng.UnMerge
ws.Range(address).Value = contents
End If
Next rng
' Sort
myRange.Sort key1:=ws.Range("A1:A" & lstrow), _
order1:=xlAscending, Header:=xlYes, key2:=ws.Range("C1:C" & lstrow), _
order2:=xlAscending, Header:=xlYes
' Turn off alerts
Application.DisplayAlerts = False
' Re-merge
With ws
For l = 2 To lstrow
If .Cells(l, 1).MergeArea.Cells(1).Value = .Cells(l + 1, 1).MergeArea.Cells(1).Value _
And .Cells(l, 3).MergeArea.Cells(1).Value = .Cells(l + 1, 3).MergeArea.Cells(1).Value Then
' Merge column A
Range(.Cells(l, 1).MergeArea, .Cells(l + 1, 1)).Merge
' Merge column C
Range(.Cells(l, 3).MergeArea, .Cells(l + 1, 3)).Merge
End If
Next l
End With
' Turn on alerts
Application.DisplayAlerts = True
Exit Sub
myErr:
MsgBox "Unable to sort!"
End Sub
I have a list of names on one sheet (InputSheet) in the range c6:H200. The names in this range change twice a month. The group of names in the InputSheet are compared to a list of names on another sheet (NameList) in the range e2:e50. For each name that is found on the NameList, I want to remove the name on the InputSheet. I'm new to vba but have written this code and it is not working (getting run time error). Thanks for any help!
Sub RemoveNonWords()
Dim datasheet As Worksheet
Dim cl As Range
Set wordrange = InputSheet.Range("C6:h200")
Set datasheet = NameList.Range("E1:E50").Value
For Each cl In wordrange
If cl = datasheet Then
cl.Selection.ClearContents
End If
Next
Range("A6").Select
End Sub
There's a lot wrong with your posted code. I think in the end, this is what you're looking for, code commented for clarity:
Sub tgr()
Dim wb As Workbook
Dim wsInput As Worksheet
Dim wsNames As Worksheet
Dim rInputData As Range
Dim rNameList As Range
Dim DataCell As Range
Dim rClear As Range
Dim lRow As Long
Set wb = ActiveWorkbook
Set wsInput = wb.Sheets("InputSheet") 'Change to the actual sheet name of your input sheet
Set wsNames = wb.Sheets("NameList") 'Change to the actual sheet name of your name list sheet
'Get last used row of the C:H columns in wsInput
With wsInput.Range("C:H")
lRow = .Find("*", .Cells(1), , , , xlPrevious).Row
If lRow < 6 Then Exit Sub 'No data
End With
'Use the last used row to define your inputdata range, this was hardcoded to C6:H200 in your question
Set rInputData = wsInput.Range("C6:H" & lRow)
'Define the namelist range using all populated cells in column E of wsNames, this was hardcoded to E2:E50 in your question
Set rNameList = wsNames.Range("E2", wsNames.Cells(wsNames.Rows.Count, "E").End(xlUp))
If rNameList.Row < 2 Then Exit Sub 'No data
'Data has been found and ranges assigned
'Now loop through every cell in rInputData
For Each DataCell In rInputData.Cells
'Check if the cell being looked at exists in the NameList range
If WorksheetFunction.CountIf(rNameList, DataCell.Value) > 0 Then
'Found to exist, add the cell to the Clear Range
If rClear Is Nothing Then
Set rClear = DataCell 'First matching cell added
Else
Set rClear = Union(rClear, DataCell) 'All subsequent matching cells added
End If
End If
Next DataCell
'Test if there were any matches and if so clear their contents
If Not rClear Is Nothing Then rClear.ClearContents
End Sub
I have the following formula in an excel worksheet that I want to make a Macro:
IF(OR(AA2=2,AA2=3,AA2=4),"00",IF(AA2=5,"0"&LEFT(Z2,1),IF(AA2=6,LEFT(Z2,2))))
I want to establish this formula for a certain range based on another column. I have multiple formulas written already that work to do this such as:
Range("B3:B" & Cells(Rows.Count, "M").End(xlUp).Row).Value = "=B2+1"
Basically I want to make the If/Or statement above work in VBA with the desired range.
Any help would be appreciated!
Just setup your function, turn on the Macro Recorder, click on the cell that contains your function, hit F2 and hit Enter. If you want to setup dynamic start and end rows, or columns, you can use the methodologies below.
Sub DynamicRange()
'Best used when only your target data is on the worksheet
'Refresh UsedRange (get rid of "Ghost" cells)
Worksheets("Sheet1").UsedRange
'Select UsedRange
Worksheets("Sheet1").UsedRange.Select
End Sub
OR
Sub DynamicRange()
'Best used when first column has value on last row and first row has a value in the last column
Dim sht As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim StartCell As Range
Set sht = Worksheets("Sheet1")
Set StartCell = Range("D9")
'Find Last Row and Column
LastRow = sht.Cells(sht.Rows.Count, StartCell.Column).End(xlUp).Row
LastColumn = sht.Cells(StartCell.Row, sht.Columns.Count).End(xlToLeft).Column
'Select Range
sht.Range(StartCell, sht.Cells(LastRow, LastColumn)).Select
End Sub
OR
Sub DynamicRange()
'Best used when you want to include all data stored on the spreadsheet
Dim sht As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim StartCell As Range
Set sht = Worksheets("Sheet1")
Set StartCell = Range("D9")
'Refresh UsedRange
Worksheets("Sheet1").UsedRange
'Find Last Row and Column
LastRow = StartCell.SpecialCells(xlCellTypeLastCell).Row
LastColumn = StartCell.SpecialCells(xlCellTypeLastCell).Column
'Select Range
sht.Range(StartCell, sht.Cells(LastRow, LastColumn)).Select
End Sub
OR
Sub DynamicRange()
'Best used when your data does not have any entirely blank rows or columns
Dim sht As Worksheet
Dim StartCell As Range
Set sht = Worksheets("Sheet1")
Set StartCell = Range("D9")
'Select Range
StartCell.CurrentRegion.Select
End Sub
OR
Sub DynamicRange()
'Best used when column length is static
Dim sht As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim StartCell As Range
Set sht = Worksheets("Sheet1")
Set StartCell = Range("D9")
'Refresh UsedRange
Worksheets("Sheet1").UsedRange
'Find Last Row
LastRow = sht.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
'Select Range
sht.Range("D9:M" & LastRow).Select
End Sub
You will have a dynamic range from Excel when the formula is entered via VBA as such:
Range( Cells(2,"AB"), Cells (colMVal, "AB")).Formula = "=IF(OR(AA2=2,AA2=3,AA2=4),""00"",IF(AA2=5,""0""&LEFT(Z2,1),IF(AA2=6,LEFT(Z2,2))))"
Note that the formula is entered into row 2 all the way down to the column M value to dictate the final row (colMVal). Also note the double quotes WITHIN the formula.
If anything is required to be FIXED, rather than dynamic, you would use "$", such that:
Range( Cells(2,"AB"), Cells (colMVal, "AB")).Formula = "=IF(OR(AA$2=2,AA$2=3,AA$2=4),""00"",IF(AA$2=5,""0""&LEFT(Z$2,1),IF(AA$2=6,LEFT(Z$2,2))))"
Where I have locked that ALL references verify that the row is 2, hence AA$2. As Excel fills the formula into each row of the desired range, it will dynamically assign the correct row.
I wrote a code to copy Column D, H, M and paste it on a brand new sheet starting from A-C. I first find the last row , after that I Union the 3 column range together then select the sheet and paste it.
For some reason I don't understand why it does not work. I have never used Union range before so not sure if that is the problem, or if it is something like my for loop. Help would be appreciated.
Dim ws As Worksheet
Dim lastRow As Integer
'for loop variables
Dim transCounter As Integer
Dim range1 As Range
Dim range2 As Range
Dim range3 As Range
Dim multipleRange As Range
Dim lastRow1 As Integer
Dim ittercell As Integer
Set ws = ActiveSheet
For transCounter = 1 To 10
r.AutoFilter Field:=6, Criteria1:=transCounter.Value, Operator:=xlFilterValues
With Application.ActiveSheet
lastRow1 = .Cells(.Rows.Count, "AE").End(xlUp).Row
End With
Set range1 = Sheets("Sheet1").Range("D6:D" & lastRow1).SpecialCells(xlCellTypeVisible)
Set range2 = Sheets("Sheet1").Range("H6:I" & lastRow1).SpecialCells(xlCellTypeVisible)
Set range3 = Sheets("Sheet1").Range("M6:M" & lastRow1).SpecialCells(xlCellTypeVisible)
Set multipleRange = Union(range1, range2, range3)
multipleRange.Copy
Sheets("O1 Filteration").Select
'Range("A3").Select
'Range("A3").PasteSpecial xlPasteValues
ittercell = 1
Cells(3, ittercell).PasteSpecial xlPasteValues
ittercell = ittercell + 6
Next transCounter
There's a couple of issues with your code that might be causing the fault:
r is not defined in your code
use of transCounter.Value instead of just CStr(transCounter) (see #QHarr comment)
iterCell reset every iteration of the loop (see #QHarr comment)
Combination of ActiveSheet, unqualified Cells(... and manual Select on sheets makes the Range qualifications ambiguous
However, I do think the main logic of using Union, then Copy, then PasteSpecial is OK and just some tweaking is required.
Here is some working code where you update the Worksheet and Range references with your own. Please follow the comments.
Option Explicit
Sub CopyUnionColumns()
Dim wsSource As Worksheet '<-- Sheet1 in your code
Dim wsTarget As Worksheet '<-- O1 Filteration in your code
Dim rngFilter As Range '<-- main data range on Sheet1
Dim rngSource As Range '<-- to hold Union'd data after filtering
Dim rngTarget As Range '<-- range in O1 Filteration to paste code to
Dim lngLastRow As Long '<-- last row of main data
Dim lngCounter As Long '<-- loop variable
Dim lngPasteOffsetCol As Long '<-- offset column for pasting in the loop
' set references to source and target worksheets
Set wsSource = ThisWorkbook.Worksheets("Sheet2") '<-- update for your workbook
Set wsTarget = ThisWorkbook.Worksheets("Sheet3") '<-- update for your workbook
' set reference to data for filtering in source worksheet
lngLastRow = wsSource.Cells(wsSource.Rows.Count, 6).End(xlUp).Row
Set rngFilter = wsSource.Range("A1:F" & lngLastRow)
' initialise offset column
lngPasteOffsetCol = 0
' iterate rows
For lngCounter = 1 To 10
' filter data the data per the counter
rngFilter.AutoFilter Field:=6, Criteria1:=CStr(lngCounter), Operator:=xlFilterValues
' set source range as union of columnar data per last row
Set rngSource = Application.Union( _
wsSource.Range("A1:A" & lngLastRow).SpecialCells(xlCellTypeVisible), _
wsSource.Range("C1:C" & lngLastRow).SpecialCells(xlCellTypeVisible), _
wsSource.Range("E1:E" & lngLastRow).SpecialCells(xlCellTypeVisible))
' set target range on target sheet top left cell and offset column
Set rngTarget = wsTarget.Range("A1").Offset(0, lngPasteOffsetCol)
' copy source cells
rngSource.Copy
' paste to target
rngTarget.PasteSpecial Paste:=xlPasteAll
' increment offset
lngPasteOffsetCol = lngPasteOffsetCol + 6
Next lngCounter
' cancel cut copy mode
Application.CutCopyMode = False
' cancel autofilter
wsSource.AutoFilterMode = False
End Sub