I found a great solution from this post: Removing duplicate rows after checking all columns
Sub Remove_DuplicateRows()
Dim intArray As Variant, i As Integer
Dim rng As Range
Dim ws As Worksheet
Call Open_Workbook
Set ws = Workbooks("Sales2021.xlsm").Sheets("Reporting Template")
ws.Activate
Set rng = ws.UsedRange.Rows
With rng
ReDim intArray(0 To .Columns.Count - 1)
For i = 0 To UBound(intArray)
intArray(i) = i + 1
Next i
.RemoveDuplicates Columns:=(intArray), Header:=xlYes
End With
End Sub
I tried the script, and wanted to adjust to my case: I want to delete all duplicated rows based on all columns except the first column (i.e., columns B to U). Should I use ws.Range("B2:U3000") instead of UsedRange?
You can either use ws.Range("B2:U3000") or below code
Set rng = ws.UsedRange.Offset(0, 1).Resize(ws.UsedRange.Rows.Count, ws.UsedRange.Columns.Count - 1)
The final code should look like this.
Sub Remove_DuplicateRows()
Dim intArray As Variant, i As Integer
Dim rng As Range
Dim ws As Worksheet
Call Open_Workbook
Set ws = Workbooks("Sales2021.xlsm").Sheets("Reporting Template")
ws.Activate
Set rng = ws.UsedRange.Offset(0, 1).Resize(ws.UsedRange.Rows.Count, ws.UsedRange.Columns.Count - 1)
With rng
ReDim intArray(0 To .Columns.Count - 1)
For i = 0 To UBound(intArray)
intArray(i) = i + 1
Next i
.RemoveDuplicates Columns:=(intArray), Header:=xlYes
End With
End Sub
Related
I have a list of row numbers that I need to keep. All other rows need deleted.
This macro deletes entire rows based on row numbers in a list. It works exactly as intended.
How can it be altered to delete all rows EXCEPT those rows on the list?
Dim deleteRows As Range
Dim data() As Variant
Dim i As Double
Dim SourceWks As Worksheet
Dim oldWks As Worksheet
Set SourceWks = Sheets("TBDws")
Set oldWks = Sheets("TBDsamples")
With SourceWks
data = .Range(.Cells(1, 1), .Cells(1, 1).End(xlDown))
End With
Set deleteRows = oldWks.Rows(data(1, 1))
For i = 2 To UBound(data, 1)
Set deleteRows = Union(deleteRows, oldWks.Rows(data(i, 1)))
Next i
deleteRows.Delete Shift:=xlUp
End Sub
This will delete all the rows on the sheet TBDsamples that aren't listed in column A on TBDws
Sub DeleteThings()
Dim SourceWks As Worksheet
Dim oldWks As Worksheet
Dim deleteRange As Range
Dim arrRows() As Variant
Dim Res As Variant
Dim I As Long
Set SourceWks = Sheets("TBDws")
Set oldWks = Sheets("TBDsamples")
With SourceWks
arrRows = .Range(.Cells(1, 1), .Cells(1, 1).End(xlDown))
End With
For I = 1 To oldWks.Range("A" & Rows.Count).End(xlUp).Row
Res = Application.Match(I, arrRows, 0)
If IsError(Res) Then
If deleteRange Is Nothing Then
Set deleteRange = oldWks.Rows(I)
Else
Set deleteRange = Union(deleteRange, oldWks.Rows(I))
End If
End If
Next I
deleteRange.Delete Shift:=xlUp
End Sub
I am trying to achieve the following automation in VBA. I have different Sheets with wrong headers. I have another sheet called "HeadersMap", which contains the list of all Sheets's correct headers. What I want to do is, if I open a "Sheet1" then the code should go to the "HeadersMap" sheet > check the opened sheet name in the "SheetNames" column > check the Original header in "OriginalHeaders" column and copy correct header name from the "Correct Headers" column and replace the headers in the "Sheet1". And similarly , if I open "Sheet2", it should do the same.
"SHEET1"
A
B
C
1
aplpe
baanann
Roange
2
3
SHEET "HEADERSMAP"
A
B
C
1
SheetNames
OriginalHeaders
CorrectHeaders
2
Sheet1
aplpe
Apple
3
Sheet1
baanann
Banana
4
Sheet1
Roange
Orange
5
Sheet2
sgura
Sugar
6
Sheet2
Jggaery
Jaggery
7
Sheet3
Dtergetn
Detergent
8
Sheet3
poas
Soap
9
Sheet3
Lfua
Lufa
Desired Result "SHEET1"
A
B
C
1
Apple
Banana
Orange
2
3
Try,
Sub test()
Dim Ws As Worksheet
Dim vDB As Variant
Dim rngHeader As Range
Dim i As Integer
Set Ws = Sheets("HEADERSMAP")
vDB = Ws.Range("a1").CurrentRegion
For i = 2 To UBound(vDB, 1)
If isHas(vDB(i, 1)) Then
Set Ws = Sheets(vDB(i, 1))
Set rngHeader = Ws.Rows(1)
rngHeader.Replace vDB(i, 2), vDB(i, 3)
End If
Next i
End Sub
Function isHas(v As Variant) As Boolean
Dim Ws As Worksheet
For Each Ws In Worksheets
If Ws.Name = v Then
isHas = True
Exit Function
End If
Next Ws
End Function
Correct Headers
Edit
After reading your comment, it may be best to copy the complete code to the ThisWorkbook module (if you insist on this functionality). There is no need for adding another module.
It is assumed that the data in worksheet HeadersMap starts in cell A1.
Standard Module e.g. Module1
Option Explicit
Sub correctHeaders(ws As Worksheet)
Const sName As String = "HeadersMap"
Const sFirst As String = "A1"
Dim rg As Range
Dim Data As Variant
Set rg = ThisWorkbook.Worksheets(sName).Range(sFirst).CurrentRegion
If IsNumeric(Application.Match(ws.Name, rg.Columns(1), 0)) Then
Data = rg.Value
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
Dim Result() As Variant
Dim r As Long, j As Long
For r = 1 To UBound(Data, 1)
If StrComp(Data(r, 1), ws.Name, vbTextCompare) = 0 Then
j = j + 1
ReDim Preserve Result(1 To 2, 1 To j)
Result(1, j) = Data(r, 2)
Result(2, j) = Data(r, 3)
End If
Next r
If j > 0 Then
Set rg = ws.UsedRange.Rows(1)
Data = rg.Value
Dim cIndex As Variant
For j = 1 To j
cIndex = Application.Match(Result(1, j), Data, 0)
If IsNumeric(cIndex) Then
Data(1, cIndex) = Result(2, j)
End If
Next j
rg.Value = Data
End If
End If
End Sub
Additional Functionality (you have to run it)
Sub correctHeadersApply
Dim ws As Worksheet
For Each ws in Thisworkbook.Worksheets
correctHeaders ws
Next ws
End Sub
ThisWorkbook Module
Option Explicit
Private Sub Workbook_Open()
correctHeaders ActiveSheet
End Sub
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If Sh.Type = xlWorksheet Then
correctHeaders Sh
End If
End Sub
Bare minimum would probably be putting this in ThisWorkbook:
Private Sub Workbook_NewSheet(ByVal Sh As Object)
Dim targetRange As Range, i As Long
Set targetRange = Worksheets("HEADERSMAP").Range("A1:A9")
i = 1
For Each entry In targetRange
If entry.Value = Sh.NAME Then
Sh.Cells(1, i) = entry.Offset(, 2).Value
i = i + 1
End If
Next
End Sub
If the data is looking like your examples.
Later you might want ot change Range("A1:A9") to look for the last row, and Offset(, 2) to maybe Offset(, 1) since the "OriginalHeaders" column is superflous in reality.
The Module version would be something like:
Sub headers()
Dim targetRange As Range, i As Long, Sh As Worksheet
Set Sh = Worksheets(InputBox("Enter name of sheet"))
Set targetRange = Worksheets("HEADERSMAP").Range("A1:A9")
i = 1
For Each entry In targetRange
If entry.Value = Sh.NAME Then
Sh.Cells(1, i) = entry.Offset(, 2).Value
i = i + 1
End If
Next
End Sub
That is if the name of the sheet and the item in the list correlate.
You could set a second variable with a second inputbox, and replace Sh.NAME to select from the list manually.
Like so:
Sub headers()
Dim targetRange As Range, i As Long, Sh As Worksheet, name As String
Set Sh = Worksheets(InputBox("Enter name of sheet"))
name = InputBox("Enter name from map")
Set targetRange = Worksheets("HEADERSMAP").Range("A1:A9")
i = 1
For Each entry In targetRange
If entry.Value = name Then
Sh.Cells(1, i) = entry.Offset(, 2).Value
i = i + 1
End If
Next
End Sub
Then you can manually type witch sheet get what headers, if you like to do that.
I'm trying to compare sheet1 "A" column values to sheet2 "E:E" column values and copy/paste the whole line of every match to sheet3. Please help me to complete this task. I'm very new to VBA.
Thank you very much in advance!
Sub DelDups_TwoLists()
Dim iListCount As Integer
Dim iCtr As Integer
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
' Get count of records to search through (list that will be deleted).
iListCount = Sheets("sheet1").Cells(Rows.Count, "A").End(xlUp).Row
' Loop through the "master" list.
For Each x In Sheets("Sheet2").Range("E:E" & Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row)
' Loop through all records in the second list.
For iCtr = iListCount To 1 Step -1
' Do comparison of next record.
' To specify a different column, change 1 to the column number.
If x.Value = Sheets("Sheet1").Cells(iCtr, 1).Value Then
' If match is true then delete row.
Sheets("Sheet1").Cells(iCtr, 1).EntireRow.Copy
Sheets("Sheet3").Select.Paste
End If
Next iCtr
Next
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
Sub DelDupsTwoLists()
Dim lastRowWs1 As Long, lastRowWs2 As Long
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Set ws1 = Worksheets(1)
Set ws2 = Worksheets(2)
Set ws3 = Worksheets(3)
lastRowWs1 = LastRow(ws1.Name, 1)
lastRowWs2 = LastRow(ws2.Name, 5) 'E = 5
Dim myCell1 As Range, myCell2 As Range
Dim ws1Range As Range, ws2Range As Range
Set ws1Range = ws1.Range(ws1.Cells(1, "A"), ws1.Cells(lastRowWs1, 1))
Set ws2Range = ws2.Range(ws2.Cells(1, "E"), ws2.Cells(lastRowWs2, 1))
Dim rangeToDelete As Range
For Each myCell1 In ws1Range
For Each myCell2 In ws2Range
If myCell1.Value = myCell2.Value Then
Dim lastRowWs3: lastRowWs3 = LastRow(ws3.Name, 1) + 1
myCell2.EntireRow.Copy Destination:=ws3.Cells(lastRowWs3, 1)
If Not rangeToDelete Is Nothing Then
Set rangeToDelete = Union(rangeToDelete, myCell2.EntireRow)
Else
Set rangeToDelete = myCell2.EntireRow
End If
End If
Next
Next
If Not rangeToDelete Is Nothing Then
Debug.Print "Deleting rangeToDelete - "; rangeToDelete.Address
rangeToDelete.Delete
End If
Debug.Print "Done!"
End Sub
Public Function LastRow(wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
Set ws = Worksheets(wsName)
LastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
End Function
Pretty much I rewrote the whole code from scratch. It pretty much uses the initial n2 complexity, but is rather faster than that, because the deletion of the rows in WorkSheet(2) is done in a single last step rangeToDelete.Delete, which saves a lot of time.
Pretty much, the code defines 2 ranges with which is works - ws1Range and ws2Range, using the LastRow function. Once it defines them, it starts looping through them and comparing them. Hence the n2 complexity. In case of equal values, the row is copied and the cell is added to the rangeToDelete.
Note - it will probably not work as "out of the box solution", but try to debug further with F8 and see what happens.
Additionally:
Using Integer is not a great idea in VBA.
"_" in the Sub name is used for Events in VBA, thus it is not a great idea to use it. (although it works)
How to avoid using Select in Excel VBA
Give this a try (see comments in code for more details):
Sub DelDups_TwoLists()
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
With ActiveWorkbook
Dim wsSrc As Worksheet: Set wsSrc = .Sheets("Sheet1") 'declare and set the source worksheet
Dim wsDst As Worksheet: Set wsDst = .Sheets("Sheet3") 'declare and set the destination worksheet
Dim R1 As Long, R2 As Long, C As Long, lRow As Long, lCol As Long 'declare variables to use
With wsSrc
lCol = .Cells(1, Columns.Count).End(xlToLeft).Column 'get the last column value in the source sheet, at row 1, will reuse this laster
Dim arrData_1 As Variant: arrData_1 = .Range(.Cells(1, 1), .Cells(.Cells(Rows.Count, 1).End(xlUp).Row, 1)) 'declare and allocate the source data to an array
End With
With .Sheets("Sheet2")
Dim arrData_2 As Variant: arrData_2 = .Range("E1:E" & .Cells(Rows.Count, 1).End(xlUp).Row) 'declare and allocate the compare data to an array
End With
End With
With wsDst
For R1 = LBound(arrData_1) To UBound(arrData_1) 'for each row in the source data
For R2 = LBound(arrData_2) To UBound(arrData_2) 'for each row in the compare data
If arrData_1(R1, 2) = arrData_2(R2, 1) Then 'if there is a match
lRow = .Cells(Rows.Count, 1).End(xlUp).Row + 1 'get the last row in the destination sheet
.Range(.Cells(lRow, 1), .Cells(lRow, lCol)).Value = _
wsSrc.Range(wsSrc.Cells(R1, 1), wsSrc.Cells(R1, lCol)).Value 'allocate the matching values
Exit For 'exit early here if there is a match, go to next row to check
End If
Next R2
Next R1
End With
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
I have a sheet containing data. I want to delete the columns based on row value.
My code doesn't stop and when I hit escape, it has deleted all of the column from my starting columns.
I want to check values in row 2 from column D to the last used column (I have about 100 columns now) that if they contain C15, C17 and so on then don't do anything, else, delete the columns.
I only have 40k rows. My range, column and row will expand every week so I want to use VBA to cut down formatting time.
Sub test()
'start
Dim LR1 As Long
Dim i As Long
Set ws = ThisWorkbook.ActiveSheet
With ws
LR1 = .Cells(2, .Columns.Count).End(xlToLeft).Column
Dim arr As Variant
Dim x
arr = Array("C15", "C17", "C19", "C20", "C21", "C22", "C23", "C24", "C25", "C28", "C29", "C30", "C32")
For x = LBound(arr) To UBound(arr)
For i = LR1 To 4 Step -1
If .Cells(2, i).Value = arr(x) Then
Else
.Columns(i).Delete
End If
Next i
Next x
End With
End Sub
Besides all the points made in the comments, the main issue is that your looping logic is off. Your outer loop should be the columns, and the inner loop should be the array. But with Select Case this can be simplified this to just one loop anyway.
Perhaps something like this:
Option Explicit
Sub Test()
Dim ws As Worksheet
Set ws = ThisWorkbook.ActiveSheet
With ws
Dim lastCol As Long, i As Long
Dim rng As Range
lastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
For i = lastCol To 4 Step -1
Select Case .Cells(2, i).Value
Case "C15", "C17", "C19", "C20", "C21", "C22", "C23", "C24", "C25", "C28", "C29", "C30", "C32"
' keep
Case Else
If rng Is Nothing Then
Set rng = .Columns(i)
Else
Set rng = Union(rng, .Columns(i))
End If
End Select
Next i
If Not rng Is Nothing Then
rng.Delete
End If
End With
End Sub
I have a small userform with 1 combobox, 2 textbox and 1 command button. Image is attached.
Also the image of worksheet is attached below.
Upon Initialization of Userform, combobox is populated with account heads listed in Table1.
Selection of item from combobox will populate the textbox with Account Code listed in Table1.
Group Head textbox will be entered manually.
Below is my code...
Private Sub ComboBox1_Change()
Dim ws As Worksheet, tbl As ListObject, rng As Range, cmb As ComboBox
Dim accountcode As String, rng1 As Range
Set ws = Sheets("Sheet1")
Set tbl = ws.ListObjects("Table1")
Set rng = tbl.ListColumns(1).DataBodyRange
Set rng1 = tbl.ListColumns(2).DataBodyRange
Me.TextBox1.Value = Application.WorksheetFunction.Index(rng, Application.WorksheetFunction.Match(Me.ComboBox1.Value, rng1, 0))
End Sub
Private Sub CommandButton1_Click()
Dim ws As Worksheet, tbl As ListObject, row As ListRow
Set ws = Sheets("Sheet1")
Set tbl = ws.ListObjects("Table2")
Set row = tbl.ListRows.Add
prefix = Me.TextBox1.Value & "-"
Dim NextNum As Long
Dim LastRow As Long, lRow As Long
Dim myArr() As Long
With Sheets("Sheet1")
'Find Last Row in Group Head Code Column
LastRow = .Cells(.Rows.Count, "E").End(xlUp).row
ReDim myArr(1 To LastRow)
' read all cells contents and convert them to array of numbers
For lRow = 5 To LastRow
If Mid(.Cells(lRow, 5), 4) <> "" Then
myArr(lRow) = CLng(Mid(.Cells(lRow, 5), 4))
End If
Next lRow
' find maximum value in array
NextNum = WorksheetFunction.Max(myArr)
End With
row.Range(1, 1).Value = Me.ComboBox1.Value
row.Range(1, 2).Value = prefix & NextNum + 1
row.Range(1, 3).Value = Me.TextBox2.Value
End Sub
Private Sub UserForm_Initialize()
Dim ws As Worksheet, tbl As ListObject, rng As Range, cmb As ComboBox
Set ws = Sheets("Sheet1")
Set tbl = ws.ListObjects("Table1")
Set rng = tbl.ListColumns(2).DataBodyRange
Set cmb = Me.ComboBox1
For Each rng In rng
cmb.AddItem rng.Value
Next rng
End Sub
The command button reads the value in Table 2, COlumn and 2, Generate the serial number and post the values in the Table.
What i want with the command button is, if i select any other head from the combobox, the code should read the value associated with that prefix and then generate the next serial number. Currently it is not reading the prefix.
Kindly advise what changes need to be made in my command button code to achieve this.
Thanks
Salman
I think the problem you're experiencing is that you're trying to use Sheet1 as both the output display and data storage system. Often this isn't a problem, but in your case, it's causing you to have to search Table2 each time an entry is made.
You'd be better off using a module-level variable in VBA to keep track of the incrementing number for each code. There's also no need to look up Table 1 each time a selection is made. You could either store the codes in another module-level variable or exploit the ability for ComboBoxes to have more than one column. In the sample below I've gone for the latter because it's easy to read the ListObject straight into the combo box List property - if you want to go the same route, then you'd need to change the combo box ColumnCount property to 2 and, if you want the codes to be invisible, change the ColumnWidths property to something like 0 pt;130 pt.
Your code, then, could look something like the below:
Option Explicit
Private mNextInc() As Long
Private Function IndexOf(val As String, arr As Variant) As Long
Dim i As Long
'Get array index of lookup item
For i = 1 To UBound(arr, 1)
If arr(i, 1) = val Then
IndexOf = i
Exit Function
End If
Next
End Function
Private Sub ComboBox1_Change()
Me.TextBox1.Text = Me.ComboBox1.Value
End Sub
Private Sub CommandButton1_Click()
Dim tbl As ListObject
Dim rng As Range
Dim v As Variant
With Me.ComboBox1
'Create the output array
v = Array(.Text, _
.Value & "-" & mNextInc(.ListIndex), _
Me.TextBox2.Text)
'Write new row to table
Set tbl = ThisWorkbook.Worksheets("Sheet1") _
.ListObjects("Table2")
tbl.Range.Activate
If tbl.InsertRowRange Is Nothing Then
Set rng = tbl.HeaderRowRange.Offset(tbl.ListRows.Count + 1)
Else
Set rng = tbl.InsertRowRange
End If
rng.Value = v
'Increment the digit of code
mNextInc(.ListIndex) = mNextInc(.ListIndex) + 1
End With
End Sub
Private Sub UserForm_Initialize()
Dim tbl As ListObject
Dim lRow As ListRow
Dim rng As Range
Dim inc As Long
Dim i As Long
'Populate the combobox
Set rng = ThisWorkbook.Worksheets("Sheet1") _
.ListObjects("Table1").DataBodyRange
Me.ComboBox1.List = rng.Value
'Set the increment values to 1
ReDim mNextInc(rng.Rows.Count - 1)
For i = 1 To UBound(mNextInc)
mNextInc(i) = 1
Next
'Find current max value for each group head code
Set tbl = ThisWorkbook.Worksheets("Sheet1") _
.ListObjects("Table2")
For Each lRow In tbl.ListRows
With lRow.Range
i = IndexOf(.Cells(1), rng.Value)
inc = CLng(Mid(.Cells(2), 4, Len(.Cells(2)) - 3)) + 1
End With
If inc > mNextInc(i) Then mNextInc(i) = inc
Next
'Set the combo box to first item
Me.ComboBox1.ListIndex = 0
End Sub