What the code is supposed to do:
Remove all the duplicate data in specified data range
Inform the user how many duplicates have been deleted in total (I have done this by removing the duplicate data and removing the blank rows and subtracting the original data set amount by the remainder)
**Im struggling with this: run a second time, get a msgbox to appear and say "Number of duplicates = 0
"
Sub Delete_Duplicate()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim sh As Worksheet
Dim rn As Range
Set sh = ThisWorkbook.Sheets("Data")
Dim k As Long
Set rn = sh.UsedRange
k = rn.Rows.Count + rn.Row - 1
Range("A11:F11").Select
Range(Selection, Selection.End(xlDown)).Select
ActiveSheet.Range("$A$10:$F$57250").RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5 _
, 6), Header:=xlYes
On Error Resume Next
Selection.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
On Error GoTo 0
k = rn.Rows.Count + rn.Row - 1
response = MsgBox("Total Duplicate Rows Removed = " & 57250 - k & Chr(10) & "Continue?", _
vbYesNoCancel + vbQuestion, "MsgBox Demonstration")
Your code looks like a flying time bomb because it deletes indiscriminately.
Any duplicates on the ActiveSheet which could be any sheet in any open workbook.
Entire rows in which any blank cell is found within its UsedRange. This could easily be every single row in the worksheet.
I have re-written your code to make it less dangerous. Before running it please change the name of the worksheet in the line Set Sh = ThisWorkbook.Sheets("Duplicates") and make sure that the line Const Rstart As Long = 11 correctly defines the worksheet row in which the first duplicate or blank is to be looked for (the row immediately below whatever headers or captions your sheet may have). Observe that the code looks in column A for the last used row in the worksheet as well as for blank cells where the entire row is presumed blank.
Option Explicit
Sub Delete_Duplicates()
Const Rstart As Long = 11 ' first data row (excl captions)
Dim Sh As Worksheet
Dim Rend As Long
Dim Rn As Range
Dim k As Long
Dim Response As VbMsgBoxResult
Dim R As Long
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Set Sh = ThisWorkbook.Sheets("Duplicates")
With Sh
Rend = .Cells(.Rows.Count, "A").End(xlUp).Row
Set Rn = Range(.Cells(Rstart, "A"), .Cells(Rend, "F"))
Rn.RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5, 6)
k = Rend
Rend = .Cells(.Rows.Count, "A").End(xlUp).Row
k = k - Rend
' there can be only one blank row because
' others were removed as duplicates
R = Rn.Cells(1).End(xlDown).Row + 1
If R < Rend Then
.Rows(R).Delete
k = k + 1
End If
End With
Application.Calculation = xlAutomatic
Application.ScreenUpdating = True
Response = MsgBox(k & " duplicate and blank rows were removed." & _
Chr(10) & "Continue?", _
vbYesNo Or vbQuestion, _
"MsgBox Demonstration")
If Response = vbYes Then Delete_Duplicates
End Sub
Related
I am trying to copy my data depending on the column value. If column R has invalid, then it should copy all information from sheet1 to sheet2.
I have below code running. Due to some reason it does not copy the last two rows of my sheet1.
I have 551 rows in sheet1 , and I have the 551 row column R as invalid. 'It checks only till 548 rows and skips the last row without moving them.
Could someone help me to fix this issue
Sub Tab()
Dim cell As Range
Dim nextrow As Long
Dim a As Double
Application.ScreenUpdating = False
' get the count of rows in column r
a = Sheets("sheet1").Cells(Rows.count, "R").End(xlUp).Row
MsgBox (a)
For Each cell In Sheets("sheet1").Range("R5:R" & a)
' if the cell in column R has invalid, then copy the entire row to another sheet
If cell.Value = "Invalid" Then
nextrow = Application.WorksheetFunction.CountA(Sheets("sheet2").Range("R:R"))
Rows(cell.Row).Copy Destination:=Sheets("sheet2").Range("A" & nextrow + 1)
End If
Next
Application.ScreenUpdating = True
End Sub
Instead of
Rows(cell.Row).Copy Destination:=Sheets("sheet2").Range("A" & nextrow + 1)
try
Sheets("sheet1").Rows(cell.Row).Copy Destination:=Sheets("sheet2").Range("A" & nextrow + 1)
Your code can be written as
Sub Demo()
Dim cell As Range
Dim nextrow As Long, a as Long
Dim srcSht As Worksheet, destSht As Worksheet
Application.ScreenUpdating = False
Set srcSht = ThisWorkbook.Sheets("Sheet3")
Set destSht = ThisWorkbook.Sheets("Sheet6")
nextrow = Application.WorksheetFunction.CountA(destSht.Range("R:R"))
With srcSht
a = .Cells(.Rows.Count, "R").End(xlUp).Row
MsgBox a
For Each cell In .Range("R5:R" & a)
' if the cell in column R has invalid, then copy the entire row to another sheet
If cell.Value = "Invalid" Then
.Rows(cell.Row).Copy Destination:=destSht.Range("A" & nextrow + 1)
nextrow = nextrow + 1
End If
Next
End With
Application.ScreenUpdating = True
End Sub
Also instead of pasting data row by row you can also use UNION.
I will not go into the part on the variables and methodology (everyone has their way of scripting). I will respond based on your base code above, hopefully it is clear for your understanding.
Sub Tab()
Dim cell As Range
Dim nextrow As Long
Dim a As Double
Application.ScreenUpdating = False
' get the count of rows in column r
a = Sheets("sheet1").Cells(Rows.count, "R").End(xlUp).Row
MsgBox (a)
'This is assuming that you will always populate starting from the first row Range("A1") in Sheet2
nextrow = 1
For Each cell In Sheets("sheet1").Range("R5:R" & a)
' if the cell in column R has invalid, then copy the entire row to another sheet
If cell.Value = "Invalid" Then
'Use the EntireRow function to copy the whole row to the Sheet2.
'During the next iteration, it will +1 to nextrow, so the next record will be copied to Range("A2"), next Range("A3") and so forth.
cell.EntireRow.Copy Destination:=Sheets("Sheet2").Range("a" & nextrow)
nextrow = nextrow + 1
End If
Next
Application.ScreenUpdating = True
End Sub
I'm writing a macro to sort through a large file of data at work. I've inserted a blank row at the top of different section of data. I want my code to realize when a row is blank in column C, then fill in a set of headers in that row. It should then continue to find the next blank in column C. This should continue until my code finds 2 consecutive blanks, which signals the end of my data.
Currently, my code inserts the desired headers, but only in the first row of my worksheet. I believe that I need to change the loop contained inside my "Do... Loop Until" function. I just can't seem to get the correct code to achieve my desired results.
I've included a screencapture of roughly what my spreadsheet will look like.
Any help or advice is greatly appreciated.
This is the code I have so far:
Sub AddHeaders()
'Add headers below each section title
Dim Headers() As Variant
Dim ws As Worksheet
Dim wb As Workbook
Dim LastRow As Long, Row As Long
Application.ScreenUpdating = False 'turn this off for the macro to run a
little faster
Set wb = ActiveWorkbook
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
ActiveCell = Cells(1, 3)
Headers() = Array("Item", "Configuration", "Drawing/Document Number",
"Title", "ECN", "Date", "Revisions")
' Set Do loop to stop when two consecutive empty cells are reached.
Do
For Row = 1 To LastRow 'Add a loop to go through the cells in each row?
If IsEmpty(ActiveCell) = True Then 'If row is empty, then go in and add headers
For i = LBound(Headers()) To UBound(Headers())
Cells(Row, 1 + i).Value = Headers(i)
Next i
Rows(Row).Font.Bold = True
'Loop here
End If
Next Row
ActiveCell = ActiveCell.Offset(1, 0)
Loop Until IsEmpty(ActiveCell) And IsEmpty(ActiveCell.Offset(1, 0))
Application.ScreenUpdating = True 'turn it back on
MsgBox ("Done!")
Is this what you are looking for?
I removed the activecell stuff and used range instead.
Also removed the do loop and only use the for loop.
I think it works but Not sure. It does not look like you have on your picture but I keept your text code.
Sub AddHeaders()
'Add headers below each section title
Dim Headers() As Variant
Dim ws As Worksheet
Dim wb As Workbook
Dim LastRow As Long, Row As Long
Application.ScreenUpdating = False 'turn this off for the macro to run a
Set wb = ActiveWorkbook
LastRow = Cells(Rows.Count, 3).End(xlUp).Row
ActiveCell = Cells(1, 3)
Headers() = Array("Item", "Configuration", "Drawing/Document Number", "Title", "ECN", "Date", "Revisions")
' Set Do loop to stop when two consecutive empty cells are reached.
For Row = 1 To LastRow 'Add a loop to go through the cells in each row?
If Range("C" & Row).Value = "" Then 'If row is empty, then go in and add headers
For i = LBound(Headers()) To UBound(Headers())
Cells(Row, 1 + i).Value = Headers(i)
Next i
Rows(Row).Font.Bold = True
'Loop here
End If
Next Row
Application.ScreenUpdating = True 'turn it back on
MsgBox ("Done!")
End Sub
Edit; Include image of output of above code.
Here's how I would do it:
Sub AddHeaders()
Dim nRow As Integer
nRow = 1
Do Until Range("C" & nRow) = "" And Range("C" & nRow + 1) = ""
If Range("C" & nRow) = "" Then
Range("A" & nRow & ":D" & nRow) = "Header"
End If
nRow = nRow + 1
Loop
End Sub
The point of this code is to take user inputs from a "Remove Flags" tab in which the user puts an item number and what program it belongs to, filters the "Master List" tab by the item number and the program, then match the name of the flag to the column and delete the flag. However the offset is not working. It is instead deleting the header. When I step through it everything works fine until the line I marked with '*******.
I am fairly new to VBA and am self taught so any and all help is greatly appreciated. Thank you very much for your time.
EDIT: Removed "On Error Resume Next" and fixed some spelling errors. Current issue is with rng not having >1 rows when it is filtered and definitely has two rows (one row is the header, one row is the returned data.)
Sub RemoveFlag()
Dim cel As Range
Dim rg As Range
Dim d As Double
Dim i As Integer
Dim m As Integer
Dim n As Integer
Dim rng As Range
Dim wsMaster As Worksheet
Dim wsFlag As Worksheet
Set wsMaster = Worksheets("Master List")
Set wsFlag = Worksheets("Remove Flags")
i = 6
'If there is no data. Do nothing.
If wsFlag.Range("C6") = "" Then
wsFlag.Activate
Else
Application.ScreenUpdating = False
'Add Leading zeroes
wsFlag.Activate
Set rg = Range("C6")
Set rg = Range(rg, rg.Worksheet.Cells(Rows.Count, rg.Column).End(xlUp))
rg.NumberFormat = "#"
For Each cel In rg.Cells
If IsNumeric(cel.Value) Then
d = Val(cel.Value)
cel.Value = Format(d, "000000000000000000") 'Eighteen digit number
End If
Next
'Clear all the filters on the Master List tab.
wsMaster.Activate
If wsMaster.AutoFilterMode = True Then
wsMaster.AutoFilterMode = False
End If
'Loop through all lines of data
Do While wsFlag.Cells(i, 3).Value <> ""
'Filter by the SKU number
wsMaster.Range("A1").AutoFilter Field:=4, Criteria1:=wsFlag.Cells(i, 3).Value
'Filter by the Program
wsMaster.Range("A1").AutoFilter Field:=2, Criteria1:=wsFlag.Cells(i, 2).Value
'If the filter is not empty find the column of the flag
Set rng = wsMaster.UsedRange.SpecialCells(xlCellTypeVisible)
If (rng.Rows.Count > 1) Then
wsMaster.Range("A1:Z1").Find(wsFlag.Cells(i, 4), LookIn:=xlValues).Activate
n = ActiveCell.Column
Sheets("Master List").Range.Offset(1, 0).SpecialCells(xlCellTypeVisible).Select
m = ActiveCell.Row
Cells(m, n) = ""
wsFlag.Activate
wsFlag.Range(Cells(i, 2), Cells(i, 4)).ClearContents
Else
wsFlag.Activate
wsFlag.Range(Cells(i, 2), Cells(i, 4)).Copy
wsFlag.Range("F4").End(xlDown).Offset(1, 0).Activate
ActiveCell.PasteSpecial Paste:=xlPasteValues
wsFlag.Range(Cells(i, 2), Cells(i, 4)).ClearContents
End If
wsMaster.Activate
wsMaster.AutoFilterMode = False
i = i + 1
Loop
'Make sure the entire Master List tab is not highlighted and pull the 'highlighted cell' to A1 in both tabs.
wsMaster.Activate
wsMaster.Range("A1").Activate
wsFlag.Activate
Range("A1").Activate
'Unfreeze the screen
Application.ScreenUpdating = True
End If
End Sub
As #Zerk suggested, first set two Worksheet variables at top of code:
Dim wsMaster As Worksheet
Dim wsRemoveFlags As Worksheet
Set wsMaster = Worksheets("Master List")
Set wsRemoveFlags = Worksheets("Remove Flags")
Then replace all other instances of Worksheets("Master List") with wsMaster and Worksheets("Remove Flags") with wsRemoveFlags.
Sometimes it's easier to just loop through your rows and columns. Something like the following:
Replace everything between:
Do While wsFlag.Cells(i, 3).Value <> ""
...
Loop
with:
Do While wsFlag.Cells(i, 3).Value <> ""
Dim r As Long ' Rows
Dim c As Long ' Columns
Dim lastRow As Long
Dim found As Boolean
lastRow = wsMaster.Cells.SpecialCells(xlLastCell).Row
found = False
For r = 2 To lastRow ' Skipping Header Row
' Find Matching Program/SKU
If wsMaster.Cells(r, 2).Value = wsFlag.Cells(i, 2).Value _
And wsMaster.Cells(r, 3) = wsFlag.Cells(i, 3).Value Then
' Find Flag in Row
For c = 1 To 26 ' Columns A to Z
If wsMaster.Cells(r, c) = wsFlag.Cells(i, 4) Then
' Found Flag
wsMaster.Cells(r, c) = ""
found = True
Exit For ' if flag can be in more than one column, remove this.
End If
Next 'c
End If
Next 'r
If Not found Then
' Here is where you need to put code if Flag wsFlag.Cells(i, 4) not found.
End If
Loop
I have two for loops in vba that are iterating over column b and checking to see if the first word in the current cell is the same as the first word in any other cell and if so copying them into another column, therefore grouping similar items. But, when I go to copy and paste the matches it finds, it only copy and pastes the matches, not the original cells that it is comparing against. I would like to have the matches and the original cells as well in the grouping but I am unsure where to modify my code so it will do so. I am rather new to vba so any help would be greatly appreciated.
Sub FuzzySearch()
Dim WrdArray1() As String, WrdArray2() As String, i As Long, Count As Long, Rng1 As Range
Dim WS As Worksheet, positionx As Long, positiony As Long
Dim rng2 As Range
Set WS = ThisWorkbook.ActiveSheet
With WS
Set Rng1 = .Range("B2:B" & .Range("B" & .Rows.Count).End(xlUp).Row)
End With
For i = 1 To Rng1.Rows.Count
With Columns("B")
.Find(what:="*", after:=.Cells(1, 1), LookIn:=xlFormulas).Activate
End With
position = 1
For j = 1 To Rng1.Rows.Count
WrdArray1 = Split(ActiveCell.Value, " ")
ActiveCell.Offset(1).Activate
WrdArray2 = Split(ActiveCell.Value, " ")
If UBound(WrdArray2) < 0 Then
End
End If
If WrdArray1(0) = WrdArray2(0) Then
ActiveCell.Copy Destination:=ActiveSheet.Range("C" & position)
position = position + 1
Count = Count + 1
End If
Next j
Next i
End Sub
Given that you are using a mixture of arrays and Ranges it would probably be easier and less confusing to populate one of the arrays with the final output (including the comparator) within a loop and then transfer the array to the worksheet in a single command.
However, perhaps consider the following approach which lets Excel do all the 'heavy lifting'. It's the same number of code lines but I have annotated it for your information. This illustrates the filling of an array in a loop and transferring it to a Range. Change the various variables to suit your situation.
Sub grpAndCount()
Dim ws As Worksheet
Dim strow As Long, endrow As Long, stcol As Long
Dim coloffset As Long, r As Long
Dim newstr As String
Dim drng As Range
Dim strArr() As String
'Data start r/c
strow = 6 'Row 6
stcol = 2 'Col B
'Offset no of Cols from Data to place results
coloffset = 2
Set ws = Sheets("Sheet1")
With ws
'find last data row
endrow = Cells(Rows.Count, stcol).End(xlUp).Row
'for each data row
For r = strow To endrow
'get first word
newstr = Left(.Cells(r, stcol), InStr(.Cells(r, stcol), " ")-1)
'put string into array
ReDim Preserve strArr(r - strow)
strArr(r - strow) = newstr
Next r
'put array to worksheet
Set drng = .Range(.Cells(strow, stcol + coloffset), .Cells(endrow, stcol + coloffset))
drng = Application.Transpose(strArr)
'sort newly copied range
drng.Sort Key1:=.Cells(strow, stcol + coloffset), Order1:=xlAscending, Header:=xlNo
'provide a header row for SubTotal
.Cells(strow - 1, stcol + coloffset) = "Header"
'resize range to include header
drng.Offset(-1, 0).Resize(drng.Rows.Count + 1, 1).Select
'apply Excel SubTotal function
Application.DisplayAlerts = False
Selection.Subtotal GroupBy:=1, Function:=xlCount, Totallist:=Array(1)
Application.DisplayAlerts = True
'remove 'Header' legend
.Cells(strow - 1, stcol + coloffset) = ""
End With
End Sub
I'm trying to find the LastRow in multiple column ranges ignoring certain columns... I have two attempts but can't get either working correctly:
BOTH Examples are ignoring columns N and O
My first attempt is the following, however it doesn't get the correct last range, if I have something in A15 for example and T10, it thinks the last row is 10 when it should be 15.
Sub LastRowMacro()
LastRowString = "A1:M" & Rows.Count & ", P1:Z" & Rows.Count
LastRowTest = Range(LastRowString).Find(What:="*", After:=[A1], SearchOrder:=xlByRows, searchDirection:=xlPrevious).Row
End Sub
My second attempt is as follows, but it seems rather long winded.
Sub LastRowMacro()
Dim i As Long
LastRow = 1
IgnoreColumnList = "N;O"
For i = 1 To Cells(1, Columns.Count).End(xlToLeft).Column
ColumnLetter = Split(Cells(1, i).Address(True, False), "$")(0)
For Each varFind In Split(IgnoreColumnList, ";")
If varFind = ColumnLetter Then
varNotFound = False
Exit For
End If
varNotFound = True
Next
If varNotFound Then
CurrentLastRow = Cells(Rows.Count, i).End(xlUp).Row
If CurrentLastRow >= LastRow Then
LastRow = CurrentLastRow
End If
varNotFound = False
End If
Next
End Sub
Ideally I'd like my first attempt to work however if it just doesn't work then someone surely can improve my second version...
Try this
*There is an ignoreList variable with all the columns that you want to ignore. Make sure you populate this correctly - currently ignoring N, O, P
*You may need to set the sh variable to the correct sheet - currently it's Sheet1
*btw. this snippet will always find the last last row on the spreadsheet. you can add another elseif to check whether there are 2 columns with the same high last row in case there was 2 columns with the highest lastRows.
Sub FindingLastRow() ' ignoring some columns
Dim ignoreList
ignoreList = Array("N", "O", "P") ' MODIFY IGNORE LIST
Dim sh As Worksheet
Set sh = Sheet1 ' SET CORRECT SHEET
Dim currentlast As Range
Set currentlast = sh.Cells(1, 1)
Dim iteratingCell As Range
With sh
For j = 1 To .UsedRange.Columns.Count
Set iteratingCell = .Cells(1, j)
If Not isIgnored(iteratingCell, ignoreList) Then
If iteratingCell.Cells(Rows.Count).End(xlUp).Row >= currentlast.Cells(Rows.Count).End(xlUp).Row Then
Set currentlast = iteratingCell
End If
End If
Next
Set currentlast = .Range("$" & Split(currentlast.Address, "$")(1) & "$" & currentlast.Cells(Rows.Count).End(xlUp).Row)
End With
MsgBox currentlast.Address
End Sub
Function isIgnored(currentlast As Range, ignoreList As Variant) As Boolean
Dim ignore As Boolean
Dim letter As Variant
For Each letter In ignoreList
If StrComp(Split(currentlast.Address, "$")(1), letter, vbTextCompare) = 0 Then
ignore = True
Exit For
End If
Next
isIgnored = ignore
End Function