Remove duplicate rows but retain data in three columns using Excel 2007 - vba

I will be importing an Excel 2007 file into Access 2007, but before I do that, I must massage the Excel file, as follows:
1.) Remove duplicate rows associated with numeric data in column A.
2.) I need to retain the data in three columns (columns I, P and Q), and combine that alpha numeric data, semi-colon separated, in the retained rows cells of columns I, P and Q.
3.) If any data in columns I, P and Q from the duplicate rows already exists, then do not retain that duplicated data
From This...
To This...
I'd be eternally grateful for assistance here. Kinda got pulled into this "mini-project" because I knew what Excel and Access were. Nice. :)

hoping to deserve that eternal gratitude...
open your worksheet with data, press ALT+F11 to launch the IDE and click Insert->Module. this will add a "Module" in your VBA "Project"
in the "Project Manager Window" (click View-> "Project Manager Window" to possibly show it) double click over the "Module1" node to open the module code pane and place this code in it
Option Explicit
Sub RemoveDupesAndRetainData()
Dim cell As Range
Dim nDupes As Long
With ActiveWorkbook.Worksheets("Data") '<~~ change sheet name as per your needs
With .Range("A1:Q" & .Cells(.Rows.Count, 1).End(xlUp).Row) '<~~ data are in columns A to P and start from row 1 (headers)
.Sort key1:=.Range("A1"), order1:=xlAscending, Header:=xlYes '<~~ sort rows by "Order"
For Each cell In .Offset(1).Resize(, 1).SpecialCells(xlCellTypeConstants) '<~~ loop through each cell in columns A containing values
nDupes = WorksheetFunction.CountIf(.Columns(1), cell.Value) - 1 '<~~ count duplicates
If nDupes > 0 Then '<~~ if there are any ...
.AutoFilter Field:=1, Criteria1:=cell.Value '<~~ ...filter data by "order" as current cell content -> only rows with same current cell content will be displayed...
With .Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible) ''<~~ ...consider only visible cells of data range, skipping headers row...
Intersect(cell.EntireRow, .Columns("I")).Value = Join(Application.Transpose(Intersect(.Cells, .Columns("I").EntireColumn)), ";") ' ...concatenate "Resource" field...
Intersect(cell.EntireRow, .Columns("P")).Value = Join(Application.Transpose(Intersect(.Cells, .Columns("P").EntireColumn)), ";") ' ...concatenate "Special" field...
Intersect(cell.EntireRow, .Columns("Q")).Value = Join(Application.Transpose(Intersect(.Cells, .Columns("Q").EntireColumn)), ";") ' ...concatenate "Notes" field...
cell.Offset(1).Resize(nDupes).EntireRow.Delete '<~~ delete duplicate rows
End With
.AutoFilter '<~~ remove filters
End If
Next cell
End With
End With
End Sub
back to Excel UI, press Alt+F8 to have the Macro dialog box pop out
select "RemoveDupesAndRetainData" in the combobox and then press the "Execute" button
watch what happens... in case of errors you can press "Debug" button in the error message box to throw you in the VBA editor right at the line causing the error
another way of running the macro is the following:
in the VBA IDE (ALT+F11 from Excel UI) module code pane (double click on the wanted Module node in the Project Manager Window ) place the mouse cursor in any point between Sub RemoveDupesAndRetainData and End Sub statements and press F8 to have your macro start with its first line yellow shaded
now press F8 to step through each code line that will be executed and that will yellow shaded as well
at every step you can query every variable value by hovering the mouse over any of its occurrence within the code or by typing ? variable_name in the Immediate Window (that you can visualize by clicking "Ctrl+G" or selecting View->Immediate Window)
placing the mouse cursor within any significative code "word" and pressing "F1" will launch the relevant help topic to learn about that specific object. each topic will have hyperlinks to dig into and get more corresponding info
of course the web is another invaluable knowledge source where to find almost everything you currently need, with dozens of blogs specific to Excel and VBA
I think what above will get you started and, more important, going on
it's a long way but everybody here who's helping coding fellows started like that, and never reached the end of it

Additional variant from my side:
Sub test()
Dim cl As Range, Data As Range, key$, item$, k
Dim Dic As Object: Set Dic = CreateObject("Scripting.Dictionary")
Dic.CompareMode = vbTextCompare
With Sheets("SheetName") 'specify Sheet Name
Set Data = .Range("A2:Q" & .[A:A].Find("*", , , , xlByRows, xlPrevious).Row)
Data.RemoveDuplicates Array(1, 9, 16, 17), xlYes
End With
For Each cl In Data.Columns(1).Cells
key = cl.Value2
item = cl.Offset(, 8).Value2 & "|" & cl.Offset(, 15).Value2 & "|" & cl.Offset(, 16).Value2
If Not Dic.exists(key) Then
Dic.Add key, item
Else
Dic(key) = Split(Dic(key), "|")(0) & ";" & Chr(10) & Split(item, "|")(0) & "|" & _
Split(Dic(key), "|")(1) & ";" & Chr(10) & Split(item, "|")(1) & "|" & _
Split(Dic(key), "|")(2) & ";" & Chr(10) & Split(item, "|")(2) & "|"
End If
Next cl
Data.RemoveDuplicates (1), xlYes
For Each k In Dic
If Dic(k) Like "*;*" Then
Set cl = Data.Columns(1).Find(k)
With cl
.Offset(, 8).Value2 = Split(Dic(k), "|")(0)
.Offset(, 15).Value2 = Split(Dic(k), "|")(1)
.Offset(, 16).Value2 = Split(Dic(k), "|")(2)
End With
End If
Next k
End Sub
before:
after:

Related

How to pause macro, then do my stuff and continue/resume from where I left?

I got data in one sheet form B2:ZY191, and I want to copy each row (B2:ZY2,B3:ZY3, and so on till B191:ZY191) to another workbook worksheet for analysis. Now while doing so I sometimes need to stop and mark my results in between and then continue from where I left. For example, I started the macro and it copied from B2:ZY2 to B52:ZY52 then I pause the macro & mark my results. Now I want to continue from B52:ZY52 onwards then again if I want to stop after copying data till B95:ZY95 I should be able to pause the macro, mark my result and continue from B95:ZY95 thereon. I should be able to do this as many times as I want.
If provided with buttons like start, pause and resume would be very helpful.
you could adopt the following workaround:
choose the "sets" you want to virtually divide your data range into
let's say:
set#1 = rows 1 to 20
set#2 = rows 21 to 30
... and so on
mark with any character in column "A" the final rows of all chosen sets
so you'd put a "1" (or any other character other than "|I|" or "|E|" - see below) in the following cells of column "A" (i.e. the one preceding your data range):
A21
A31
..., and so on
(since your data starts at row 2 then its ith row is in worksheet row I+1)
then you put the following code in any module of your data range workbook:
Option Explicit
Sub DoThings()
Dim dataRng As Range, rngToCopy As Range
'assuming Analysis.xlsx is already open
Set dataRng = Worksheets("BZ").Range("B2:ZY191") '<--| this is the whole data range. you can change it (both worksheet name and range address) but be sure to have a free column preceeding it
Set rngToCopy = GetCurrentRange(dataRng) '<--| try and set the next "set" range to copy
If rngToCopy Is Nothing Then '<--| if no "set" range has been found...inform the user and exit sub!
MsgBox "There's an '|E|' at cell " _
& vbCrLf & vbCrLf & vbTab & dataRng(dataRng.Rows.Count, 1).Offset(, -1).Address _
& vbCrLf & vbCrLf & " marking data has already been entirely copied" _
& vbCrLf & vbCrLf & vbCrLf & "Remove it if you want to start anew", vbInformation
Exit Sub
End If
With rngToCopy
Workbooks("Analysis").Worksheets("Sheet1").Range(.Address).value = .value
End With
End Sub
Function GetCurrentRange(dataRng As Range) As Range
Dim f As Range
Dim iniRow As Long, endRow As Long
With dataRng
With .Offset(, -1)
Set f = .Resize(, 1).Find(what:="|E|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for the "all copied" mark ("|E|")
If Not f Is Nothing Then Exit Function '<--| if "all copied" mark was there then exit function
Set f = .Resize(, 1).Find(what:="|I|", lookat:=xlWhole, LookIn:=xlValues) '<--| look for any "initial" mark put by a preceeding sub run
If f Is Nothing Then '<--|if there was no "initial" mark ...
iniRow = 1 '<--| ...then assume first row as initial one
Else
iniRow = f.row - .Cells(1).row + 1 '<--| ... otherwise assume "marked" row as initial one
f.ClearContents '<--| and clear it not to found it the next time
End If
endRow = .Cells(iniRow, 1).End(xlDown).row - .Cells(1).row + 1 '<--| set the last row as the next one with any making in column "A"
If endRow >= .Rows.Count Then '<--| if no mark has been found...
endRow = .Rows.Count '<--| ...set the last row as data last row...
.Cells(endRow, 1).value = "|E|" '<--|... and put the "all copied" mark in it
Else
.Cells(endRow, 1).ClearContents '<--| ...otherwise clear it...
.Cells(endRow + 1, 1).value = "|I|" '<--| ... and mark the next one as initial for a subsequent run
End If
End With
Set GetCurrentRange = .Rows(iniRow).Resize(endRow - iniRow + 1) '<--| finally, set the range to be copied
End With
End Function
and make it run as many times as you need: after each time it ends and you can mark your result and then make it run again and it'll restart form where it left
you can use Stop and Debug.Print to achieve the desired results when placed within your code. For example if you're looping through a range, add the statement of choice with an if statement:
for a = 1 to 150
if a = 20 or a = 40 then
debug.Print "The value of a is: " & a.value 'or whatever you want to see
end if
next
This will print to the immediates window, or use stop to pause your code in a strategic place in the same manner.
I dont understand what you mean by buttons? They surely aren't a good idea as the code will run too fast?

Add (A,i) to combobox if (M,i) has a value

I've got a problem :
Inside my Excel spreadsheet, I have a ComboBox (from Developer>insert>form>combobox)
I want to populate this combobox with values from the A column IF the same number in the M column has a value.
What VBA code would I use for it?
See below some that i made up (which obviously doesn't work)
Thanks in advance!
For i = 10 To 239
cell1temp = i
If Sheets("MASTER SHEET").cell(M, cell1temp).Value <> "" Then
DropDown35.AddItem "" & Worksheets("MASTER SHEET").Cells(A, cell1temp) & _
" " & Worksheets("MASTER SHEET").Cells(B, cell1temp)
End If
Next i
In facts, if you go to see the Format Control, in the Control tab, you'll see that for the form that you inserted can only take a range as an input for the values proposed in the list.
If you go ahead and use the Macro recorder, you'll see that these objects are actually called DropDown as seen in the code you posted.
So if you want to stick to that object for a particular reason, you'll have to :
Pre-extract the values to add,
Paste them in adjacents cells to use the range in the next step,
Set the ActiveSheet.Shapes.Range("Drop Down 35").ListFillRange with the address of the previous range
Now if you can change, to a real ComboBox (from Developer>Insert>ActiveX>ComboBox) :
You can use a structure like the one you posted :
Dim ValA As String, _
Ws As Worksheet
Set Ws = ThisWorkbook.Sheets("MASTER SHEET")
For i = 10 To 239
If Ws.Cells(i, "M").Value <> vbNullString Then _
Ws.Shapes.Range("ComboBox1").AddItem Ws.Cells(i, "A") & " " & Ws.Cells(i, "B")
Next i
Set Ws = Nothing

Find first empty row in Excel and select

I tried adapting this post: Error in finding last used cell in VBA to my needs but couldn't quite get it to work.
I'm pasting data into a new worksheet and then want to select the first empty row after data. Currently what's happening is the data is pasted and then the very first row in the sheet is selected. See code below. Any thoughts?
'runs when user enters data
If Target.Cells.Count = 1 And _
Not Application.Intersect(Target, [I3:I10000]) Is Nothing Then
Application.EnableEvents = False
'User inputs type of event
Archive = InputBox("Was this event a Win, Loss, or Close? (Please input Win/Loss/Close)")
With Target
If Archive = "Win" Then
'all data to transfer is selected and cut
.EntireRow.Select
Selection.Cut
'the receiving sheet is selected and data is pasted to the selected cell
Sheets("Win").Select
ActiveSheet.Paste
'the selection on the sheet the data was cut from is deleted
Sheets("Begin").Select
Selection.Delete
'this is the issue I'm having - I want to select the row below the row I just copied into.
Sheets("Win").Select
lastRow = Range("C" & .Rows.Count).End(xlUp).Row
ActiveSheet.Range("C" & lastRow & ":C" & lastRow).EntireRow.Select
Sheets("Begin").Select
Try replacing this:
'this is the issue I'm having - I want to select the row below the row I just copied into.
Sheets("Win").Select
lastRow = Range("C" & .Rows.Count).End(xlUp).Row
ActiveSheet.Range("C" & lastRow & ":C" & lastRow).EntireRow.Select
with this:
With Sheets("Win")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
.Cells(lastRow + 1, 1).EntireRow.Select
End With
Just to add to the existing answer. You can avoid doing so much selection by using a construction more like this:
On Error GoTo problem
Dim Archive As String
If (Target.Cells.Count = 1) And _
Not (Excel.Application.Intersect(Target, [I3:I10000]) Is Nothing) Then
Excel.Application.EnableEvents = False
'User inputs type of event
Archive = InputBox("Was this event a Win, Loss, or Close? (Please input Win/Loss/Close)")
With Target
'>>>> good idea to defend against users entering "win" instead of "Win"
If (LCase(Archive) = "win") Then
'>>>> find the last row in Win sheet at the beginning
With Sheets("Win")
lr = .Range("C" & .Rows.Count).End(Excel.xlUp).Row
End With
'>>>> as you are cutting there should be no need to do any subsequent deletion or clearcontents
.EntireRow.Cut Sheets("Win").Rows(lr + 1)
End If
End With
End If
problem:
Excel.Application.EnableEvents = True

Broken VBA Loop

I'm sure this is simple I just can't find anything on the Web.
I'm writing a Macro to format XL spreadsheets that i download from a 3rd party application. They come formatted all wacky so i'm trying to make it easier to get the data we need from them.
This is a simple VBA Do Loop that causes the cells in Column BL to update. The data in these cells contain line breaks which don't show up until you double click in the cell. The VBA below causes an update to the cells which achieves the same effect, just with less work. However it is currently crashing excel and I can't figure out why. It works in a single instance, but when I loop -- BOOM!!! -- frozen. Any help would be gently appreciated.
Sub updateCell()
Dim currentValue As String
ActiveSheet.Range("BL1").Select
Do Until ActiveCell.Value = ""
ActiveCell.Offset(1, 0).Select
currentValue = ActiveCell().Value
ActiveCell().Value = currentValue & ""
Loop
End Sub
Try something a bit more direct:
With ActiveSheet
lrow = .Range("BL" & .Rows.Count).End(xlUp).Row '~~> find last row on BL
With .Range("BL1:BL" & lrow) '~~> work on the target range
.Value = .Value '~~> assign its current value to it
End With
End With
Above code is like manually pressing F2 then pressing Enter.
Edit1: Explanation on getting the last row
ActiveSheet.Rows.Count '~~> Returns the number of rows in a sheet which is 1048576
MsgBox ActiveSheet.Rows.Count '~~> run this to confirm
So this line actually concatenates BL to 1048576.
.Range("BL" & .Rows.Count) '~~> Count is a property of the Rows Collection
Same as:
.Range("BL" & 1048576)
And same as:
.Range("BL1048576")
Then to get to the last row, we use Range Object End Method.
.Range("BL" & .Rows.Count).End(xlUp)
So basically, above code go to Cell BL1048576 then like manually pressing Ctrl+Arrow Up.
To return the actual row number of the range, we use the Range Object Row property.
lrow = .Range("BL" & .Rows.Count).End(xlUp).Row
See here more about With Statement.
It has the same effect (with your code) without the loop. HTH
But if what you want is to remove Line Breaks produced by Alt+Enter on a cell, try below:
Dim lrow As Long, c As Range
With ActiveSheet
lrow = .Range("BL" & .Rows.Count).End(xlUp).Row
For Each c In .Range("BL1:BL" & lrow)
c.Value = Replace(c.Value, Chr(10), "")
Next
End With
Where Chr(10) is the equivalent of Line Break replaced with "" using Replace Function.

Automatic spreadsheet generation in Excel VBA

My friend and I currently have a master spreadsheet that I need to be broken out into smaller spreadsheets regularly. This used to be a manual process, but I'd like to automate it. I created a three step solution in VBA which would help me accomplish this that did the following:
Apply relevant filters to spreadsheet
Export data currently visible after filter into new spreadsheet
Save spreadsheet and go back to 1 (different criteria)
Unfortunately I am having a hard time implementing it. Whenever I try to generate the spreadsheet, my document hangs, starts performs several calculations and then gives this me this error message:
Upon debugging the code, I get an error message at this line:
One Excel workbook is left open and only one row is visible (the second row pulled from the Master which contains header information) and nothing else.
What exactly is going on here?
This is my code so far:
The heart of it all
' This bit of code get's all the primary contacts in column F, it does
' this by identifying all the unique values in column F (from F3 onwards)
Sub GetPrimaryContacts()
Dim Col As New Collection
Dim itm
Dim i As Long
Dim CellVell As Variant
'Get last row value
LastRow = Cells.SpecialCells(xlCellTypeLastCell).Row
'Loop between all column F to get unique values
For i = 3 To LastRow
CellVal = Sheets("Master").Range("F" & i).Value
On Error Resume Next
Col.Add CellVal, Chr(34) & CellVal & Chr(34)
On Error GoTo 0
Next i
' Once we have the unique values, apply the TOKEN NOT ACTIVATED FILTER
Call TokenNotActivated
For Each itm In Col
ActiveSheet.Range("A2:Z2").Select
Selection.AutoFilter Field:=6, Criteria1:=itm
' This is where the magic happens... creating the individual workbooks
Call TokenNotActivatedProcess
Next
ActiveSheet.AutoFilter.ShowAllData
End Sub
The "token not activated" filter
Sub TokenNotActivated()
'Col M = Yes
'Col U = provisioned
ThisWorkbook.Sheets(2).Activate
ActiveSheet.Range("A2:Z2").Select
Selection.AutoFilter Field:=13, Criteria1:="Yes"
Selection.AutoFilter Field:=21, Criteria1:="provisioned", Operator:=xlFilterValues
End Sub
Running the process to get the workbooks saved
Function TokenNotActivatedProcess()
Dim r As Range, n As Long, itm, FirstRow As Long
n = Cells(Rows.Count, 1).End(xlUp).Row
Set r = Range("A1:A" & n).Cells.SpecialCells(xlCellTypeVisible)
FirstRow = ActiveSheet.Range("F2").End(xlDown).Row
itm = ActiveSheet.Range("F" & FirstRow).Value
If r.Count - 2 > 0 Then Debug.Print itm & " - " & r.Count - 2
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Workbooks.Add
ActiveSheet.Paste
Application.CutCopyMode = False
ActiveWorkbook.SaveAs Filename:="C:\Working\Testing\TokenNotActivated - " & itm + ".xls", FileFormat:=52, CreateBackup:=False
End Function
This error is caused by trying to filter an empty range. After analysing your code, my guess is that you are missing a worksheet activation here, since repeating the line ActiveSheet.Range("A2:Z2").Select after calling the function TokenNotActivated does not make sense and maybe your code is trying to filter some empty range/worksheet.