I have a manual selection process that I have tried but failed to automate, so I am reaching out for help. I have attached an image of my Excel sheet as a visual guide when reading my process. Excel Snapshot.
I select cell "L2" and run the code below. It finds the first instance of the value within "A2:J1501" and cuts the whole row. It pastes the row onto the sheet named Lineups. Then it highlights each of the values of the cut row in column "L:L" to let me know that value has been used. I then manually select the next non-highlighted value (in the image example it would be "L2") and run the code again, and again, and again, until every row of L:L is highlighted. This process can take some time depending on the number of rows in L:L so I was hoping I can get some help to automate.
Thank you very much.
Sub ManualSelect()
Dim rng As Range
Set rng = Range("A1:J1501")
Dim ac As Range
Set ac = Application.ActiveCell
rng.Find(what:=ac).Select
Range("A" & ActiveCell.Row).Resize(1, 10).Cut
ActiveWindow.ScrollRow = 1
Sheets("Lineups").Select
nextRow = Cells(Rows.Count, 1).End(xlUp).Row + 1
Cells(nextRow, 1).Select
ActiveSheet.Paste
Sheets("Data").Select
Dim wsData As Worksheet
Dim wsLineups As Worksheet
Dim rngToSearch As Range
Dim rngLineupSet As Range
Dim rngPlayerID As Range
Dim Column As Long
Dim Row As Long
Dim LastRow As Long
Set wsData = Sheets("Data")
Set wsLineups = Sheets("Lineups")
Set rngPlayerID = wsData.Range("L2:K200")
Set rngToSearch = rngPlayerID
LastRow = wsLineups.Cells(Rows.Count, 1).End(xlUp).Row
For Row = 2 To LastRow
For Column = 1 To 10
Set rngLineupSet = rngPlayerID.Find(what:=wsLineups.Cells(Row, Column), LookIn:=xlValues)
If Not rngLineupSet Is Nothing Then rngLineupSet.Interior.Color = 65535
Next Column
Next Row
End Sub
This should be pretty close:
Sub ManualSelect()
Dim wsData As Worksheet, c As Range, dict As Object, v, rw As Range
Dim wsLineups As Worksheet, c2 As Range, f As Range
Set dict = CreateObject("scripting.dictionary") 'for tracking already-seen values
Set wsLineups = ThisWorkbook.Worksheets("Lineups")
Set wsData = ThisWorkbook.Worksheets("Data")
For Each c In wsData.Range("L2", wsData.Cells(Rows.Count, "L").End(xlUp))
v = c.Value
If dict.exists(CStr(v)) Then
c.Interior.Color = vbYellow 'already seen this value in L or a data row
Else
'search for the value in
Set f = wsData.Range("A2:J1501").Find(v, lookat:=xlWhole, LookIn:=xlValues, searchorder:=xlByRows)
If Not f Is Nothing Then
Set rw = f.EntireRow.Columns("A").Resize(1, 10) 'A to J
For Each c2 In rw.Cells 'add all values from this row to the dictionary
dict(CStr(c2)) = True
Next c2
rw.Cut Destination:=wsLineups.Cells(Rows.Count, "A").End(xlUp).Offset(1)
c.Interior.Color = vbYellow
Else
'will there always be a match?
c.Interior.Color = vbRed 'flag no matching row
End If
End If 'haven't already seen this col L value
Next c 'next Col L value
End Sub
I believe this should do it (updated):
Sub AutoSelect()
Dim wsData As Worksheet, wsLineups As Worksheet
Dim rng As Range, listIDs As Range
Set wsData = ActiveWorkbook.Sheets("Data")
Set wsLineups = ActiveWorkbook.Sheets("Lineups")
Set rng = wsData.Range("A2:J1501")
'get last row col L to define list
LastRowL = wsData.Range("L" & Rows.Count).End(xlUp).Row
Set listIDs = wsData.Range("L2:L" & LastRowL)
'loop through all cells in list
For i = 1 To listIDs.Rows.Count
myCell = listIDs.Cells(i)
'retrieve first mach in listID
checkFirst = Application.Match(myCell, listIDs, 0)
'only check first duplicate in list
If checkFirst = i Then
'get new row for target sheet as well (if sheet empty, starting at two)
newrow = wsLineups.Range("A" & Rows.Count).End(xlUp).Row + 1
'check if it is already processed
Set processedAlready = wsLineups.Cells(2, 1).Resize(newrow - 1, rng.Columns.Count).Find(What:=myCell, lookat:=xlWhole, LookIn:=xlValues)
'if so, color yellow, and skip
If Not processedAlready Is Nothing Then
listIDs.Cells(i).Interior.Color = vbYellow
Else
'get fist match for value, if any (n.b. "xlWhole" ensures whole match)
Set foundMatch = rng.Find(What:=myCell, lookat:=xlWhole, LookIn:=xlValues)
'checking for a match
If Not foundMatch Is Nothing Then
'get the row
foundRow = foundMatch.Row - rng.Cells(1).Row + 1
'specify target range and set it equal to vals from correct row in rng
wsLineups.Cells(newrow, 1).Resize(1, rng.Columns.Count).Value2 = rng.Rows(foundRow).Value
'clear contents rng row
rng.Rows(foundRow).ClearContents
'give a color to cells that actually got a match
listIDs.Cells(i).Interior.Color = vbYellow
Else
'no match
listIDs.Cells(i).Interior.Color = vbRed
End If
End If
Else
'duplicate already handled, give same color as first
listIDs.Cells(i).Interior.Color = listIDs.Cells(checkFirst).Interior.Color
End If
Next i
End Sub
Also, I think, slightly faster than the other solution offered (because of the nested loop there?). Update: I got a bit confused about the nested loop in the answer by Tim Williams, but I missed that you also want to "accept" the values in the list that matched on a row that is already gone. I fixed this in the updated version by checking if a value that fails to match on the data range has already been transferred to Lineups. Provided that doing so is permissible, this method avoids the nested loop.
I checked both methods for speed (n = 50) on a list (n = 200) for the full data range, ended up with average of 1.70x faster... But maybe speed is not such a big deal, if you're coming from manual labor :)
Related
I am starting out with a table containing a name which corresponds to a job code and the start date of said job. See below:
The desired outcome of this is to almost flip it (it is becoming part of a bigger macro, must use VBA for this)
I need dates along the column headings, and the list of unique names. In the column will appear the job for that date. See below for an example:
I have been able to get the code to select all of the rows containing a persons name, however I can't workout how to one by one go through each of the selected rows, copy the job code and paste it to the new table under the correct corresponding date.
Since some jobs have multiple people this code uses InStr() to find occurances of the unqiue names
Sub NewTable()
Dim Rng As Range
Dim Cell As Object
Dim Found As Range
Dim Ws As Worksheet
Set Ws = Worksheets("Sheet1")
Set Rng = Ws.Range("D:D")
searchString = "Emily"
For Each Cell In Rng
If InStr(Cell, searchString) > 0 Then
If Not Found Is Nothing Then
Set Found = Union(myUnion, Cell.EntireRow)
Else
Set Found = Cell.EntireRow
End If
End If
Next
If Found Is Nothing Then
MsgBox "The text was not found in the selection"
Else
Found.Select
End If
End Sub
Any help would be appreciated
Try this out:
Sub Tester()
Dim rw As Range, wsData As Worksheet, wsPivot As Worksheet, arr, e, r, c
Set wsData = ThisWorkbook.Worksheets("Input") 'sheet with original data
Set wsPivot = ThisWorkbook.Worksheets("Pivot") 'sheet for the final table
'loop over each row in the input table
For Each rw In wsData.Range("B6:E" & wsData.Cells(Rows.Count, "B").End(xlUp).Row).Rows
If Application.CountA(rw) = 3 Then 'row has data?
'try to match the date: add as new date if no match
c = Application.Match(CLng(rw.Cells(3).Value), wsPivot.Rows(1), 0)
If IsError(c) Then
c = wsPivot.Cells(1, Columns.Count).End(xlToLeft).Column + 1
If c < 4 Then c = 4 'dates start in D1
wsPivot.Cells(1, c).Value = rw.Cells(3).Value 'add the date
End If
arr = Split(rw.Cells(2).Value, ",") 'get array of all names
'check row for each name: add as new name if no match
For Each e In arr
'look for the name in Col B
r = Application.Match(Trim(e), wsPivot.Columns("B"), 0)
'if name not found, then add it in the next empty cell
If IsError(r) Then
r = wsPivot.Cells(Rows.Count, "B").End(xlUp).Row + 1
If r < 4 Then r = 4 'names begin in B4
wsPivot.Cells(r, "B").Value = e
End If
wsPivot.Cells(r, c).Value = rw.Cells(1).Value 'add the Job Code
Next e
End If
Next rw
End Sub
all.
I'm running this code:
Sub ISN_Flyer_Performance()
Dim FlyerSh As Worksheet
Dim QlikSh As Worksheet
Dim SKURng As Range
Dim QlikSKURng As Range
Dim SKU As Range
Dim qlr As Long
Dim QlikSKU As Range
Dim TotalSales As Double
Dim FirstQlikSku As Range
Set FlyerSh = ActiveSheet
i = 2
lr = FlyerSh.Range("A" & Rows.Count).End(xlUp).Row
Set QlikSh = Application.InputBox("Click any cell on the Qlikview Sheet you want to lookup against", "Find Qlikview Sheet", Type:=8).Worksheet
qlr = QlikSh.Range("A" & Rows.Count).End(xlUp).Row
Set QlikSKURng = Range(Cells(2, QlikSh.Rows(1).Find(What:="Item Number", LookAt:=xlWhole).Column), Cells(qlr, QlikSh.Rows(1).Find(What:="Item Number", LookAt:=xlWhole).Column))
Set SKURng = Range(FlyerSh.Cells(i, 1), FlyerSh.Cells(lr, 1))
Set SKU = FlyerSh.Cells(i, 1)
For Each SKU In SKURng
Set QlikSKU = QlikSKURng.Find(What:=SKU.Value, LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=False)
If QlikSKU Is Nothing Then
SKU.Offset(0, 2).Value = 0
GoTo NextSku
Else
TotalSales = QlikSKU.Offset(0, 5).Value
Set FirstQlikSku = QlikSKU
Do
Set QlikSKU = QlikSKURng.FindNext(QlikSKU)
If QlikSKU.Address = FirstQlikSku.Address Then Exit Do
TotalSales = TotalSales + QlikSKU.Offset(0, 5).Value
Loop
SKU.Offset(0, 2) = TotalSales
End If
NextSku:
Next SKU
End Sub
It's essentially like an XLookup, where it gets the thing to seach on one workbook, then finds it on a second, sends the value back to the first one, and moves on to the next item. I'd use an XLookup, but unfortunately, my sheet will always have duplicates, and I need to count both.
So I'm using this findnext loop to loop through a range (QlikSKURange) which has about 16k rows. The findNext is reasonably quick, like less than a second, EXCEPT the last instance when it goes back to the beginning and finds the first instance again. That instance can take over ten seconds.
Any idea why that might be?
Let me know if you need more info about the code.
I tried to just "Find" after the current iteration, instead of find next, and it has the same slow down.
VBA Lookup Using the Find Method
This is just the basic idea. There are many flaws e.g. if you cancel the input box, if you select a 'wrong' worksheet (e.g. column header not found), if there are error values, blank cells, etc.
Option Explicit
Sub ISN_Flyer_Performance()
' Flyer
Dim fws As Worksheet: Set fws = ActiveSheet ' improve!
Dim fLR As Long: fLR = fws.Range("A" & fws.Rows.Count).End(xlUp).Row
Dim frg As Range
Set frg = fws.Range(fws.Cells(2, "A"), fws.Cells(fLR, "A"))
'Debug.Print fws.Name, fLR, frg.Address
' Qlikview
Dim qws As Worksheet: Set qws = Application.InputBox( _
"Click any cell on the Qlikview Sheet you want to lookup against", _
"Find Qlikview Sheet", Type:=8).Worksheet
Dim qLR As Long: qLR = qws.Range("A" & qws.Rows.Count).End(xlUp).Row
Dim qC As Long
With qws.Rows(1) ' assuming that "Item Number" is surely in the first row
qC = .Find("Item Number", .Cells(.Cells.Count), _
xlFormulas, xlWhole).Column
End With
Dim qrg As Range
Set qrg = qws.Range(qws.Cells(2, qC), qws.Cells(qLR, qC))
'Debug.Print qws.Name, qLR, qC, frg.Address
Application.ScreenUpdating = False
Dim fCell As Range
Dim qCell As Range
Dim qFirstAddress As String
Dim TotalSales As Double
' Loop.
For Each fCell In frg.Cells
Set qCell = qrg.Find(fCell.Value, qrg.Cells(qrg.Cells.Count), _
xlFormulas, xlWhole)
If qCell Is Nothing Then
fCell.Offset(0, 2).Value = 0
Else
qFirstAddress = qCell.Address
Do
TotalSales = TotalSales + qCell.Offset(0, 5).Value
Set qCell = qrg.FindNext(qCell)
Loop Until qCell.Address = qFirstAddress
fCell.Offset(0, 2).Value = TotalSales
TotalSales = 0
End If
Next fCell
Application.ScreenUpdating = True
MsgBox "Lookup done.", vbInformation
End Sub
After doing more digging, someone suggested that the issue was that one of my sheets was a table. It had filters on the header row. I removed those (and conditional formatting on a row to find duplicates, and my code ran in a matter of seconds. After isolating those two, turns out the conditional formatting was the culprit.
For a project I am working on, I am attempting to copy a row from an excel spreadsheet, only if the correct criteria are met.
For example,
I need to copy a row that has the following items in them:
Fruit, Apple, True, Cell<4
I've tried using something like
Sub Database_RoundedRectangle1_Click()
Dim c As Range, i As Long
Dim SrchRng, strSearch
Set SrchRng = ActiveSheet.Range("A4:T60", ActiveSheet.Range("A60:T60").End(xlUp))
For Each strSearch In Array("Apple")
Set c = SrchRng.Find(strSearch, LookIn:=xlValues)
If Not c Is Nothing Then c.EntireRow.Copy
Sheets("Results").Paste
Next strSearch
End Sub
But the problem with this is that it only searches for a single criteria: Apple. I need the script to scan the whole row for all filters to be correct, then copy the row.
The script I used also only copies the row once, and does not seem to copy all rows that include Apple.
I am assuming that your data is consistent i.e. you are looking for Fruit in one column, Apple in another column and likewise for TRUE and <4.
Here in the code I am looking for Fruit in Column A, Apple in Column B, TRUE in Column C and <4 in Column D. You can change column numbers as required.
I've named the sheet where data is as Data and the sheet to paste copied rows as Results
Sub CopyRow()
Dim LastRowCurr As Long, LastRowResult As Long
Dim LastColumn As Long
Dim i As Long
Dim currWS As Worksheet, resultWS As Worksheet
Dim MyRange As Range
Set currWS = ThisWorkbook.Sheets("Data") '---> sheet where data is
Set resultWS = ThisWorkbook.Sheets("Results") '---> sheet to paste copied rows
lastRow = currWS.Cells(Rows.Count, "A").End(xlUp).Row
LastRowResult = resultWS.Cells(Rows.Count, "A").End(xlUp).Row
With currWS
For i = 4 To lastRow
'change column numbers in the below line as required
If .Cells(i, 1).Value = "Fruit" And .Cells(i, 2).Value = "Apple" And .Cells(i, 3).Value = True And .Cells(i, 4).Value < 4 Then
.Rows(i).Copy Destination:=resultWS.Range("A" & LastRowResult)
LastRowResult = LastRowResult + 1
End If
Next i
End With
End Sub
I guess this is what you want.
you have to add another loop for the .find function. On your Code it only looks once for Apples. What you have to do is, you have to add another loop and repeat the .find function until you this .find function gives your the first match again . Try something like this:
Sub Database_RoundedRectangle1_Click()
Dim c As Range, i As Long
Dim SrchRng, strSearch
Dim wsResults As Worksheet
Dim firstAddress
Set SrchRng = ActiveSheet.Range("A1:T60", ActiveSheet.Range("A60:T60").End(xlUp))
Set wsResults = ThisWorkbook.Worksheets("Results")
For Each strSearch In Array("Apple")
Set c = SrchRng.Find(strSearch, LookIn:=xlValues, LookAt:=xlWhole)
If Not c Is Nothing Then
firstAddress = c.Address
Do
c.EntireRow.Copy wsResults.UsedRange.Cells(wsResults.UsedRange.Rows.Count + 1, 1)
Set c = SrchRng.FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
Next strSearch
End Sub
I am trying to delete duplicate values in a temporary list based on a value in a combobox. The code below loops through individual rows to check whether a value matches. It is slow.
Dim ws As Worksheet
Dim i As Long
Set ws = Sheets("TempList3")
On Error Resume Next
For i = Cells(Rows.Count, 2).End(xlUp).Row To 1 Step -1
If Cells(i, 2) <> Sheets("Sheet1").ComboBox2.Value Then
ws.Rows(i).EntireRow.Delete
End If
Next
Is there a way to evaluate the entire column's values against the combobox's value once and then delete all rows on a worksheet. Or perhaps there is a better way?
I used a looping Find function, it deletes the row where the value was found and then it searches again and deletes the next row it finds until it can no longer find the Combo value on the sheet:
Sub find_cell()
Dim find_cell As Range
Set ws = Sheets("TempList3")
stop_loop = False
Do Until stop_loop = True
Set find_cell = ws.Cells.Find(What:=Sheets("Sheet1").ComboBox2.Value, LookAt:=xlWhole)
If Not find_cell Is Nothing Then
ws.Rows(find_cell.Row).EntireRow.Delete
Else
stop_loop = True
End If
Loop
End Sub
Not knowing how many rows you are talking about, I used 10 thousand for my example codes. here are two examples, try the both and see what works best for you.
You can run through the column and unionize the range found, then delete the rows, for example.
See here for example workbook
Sub UnIonRng()
Dim FrstRng As Range
Dim UnIonRng As Range
Dim c As Range, s As String
s = Sheets("Sheet1").ComboBox2
Set FrstRng = Range("B:B").SpecialCells(xlCellTypeConstants, 23)
For Each c In FrstRng.Cells
If c = s Then
If Not UnIonRng Is Nothing Then
Set UnIonRng = Union(UnIonRng, c) 'adds to the range
'MsgBox UnionRng.Address 'remove later
Else
Set UnIonRng = c
End If
End If
Next c
UnIonRng.EntireRow.Delete
End Sub
Or you can try to filter the column B and delete the rows that way:
Sub FilterDeleteRow()
Dim ws As Worksheet
Dim LstRw As Long, Rng As Range, s As String, x
Set ws = Sheets("TempList3")
s = Sheets("Sheet1").ComboBox2
Application.ScreenUpdating = 0
With ws
LstRw = .Cells(.Rows.Count, "B").End(xlUp).Row
x = Application.WorksheetFunction.CountIf(.Range("B:B"), s)
If x > 0 Then
Columns("B:B").AutoFilter Field:=1, Criteria1:=s
Set Rng = .Range("B2:B" & LstRw).SpecialCells(xlCellTypeVisible)
Rng.EntireRow.Delete
.AutoFilterMode = 0
Else: MsgBox "Not Found"
End If
End With
End Sub
I have one excel sheet (lets say sheet A) that has data in it, organized into groupings separated by an empty row and grouped by a common entry in column N. Within each grouping, I need to check another excel sheet (lets say sheet B) in a different workbook to see if any of the entries in column A of sheet A matches any entries in sheet B's column C. If any of the column C entries match those of the column A entries in a single grouping of the first sheet, I do not do anything to that grouping. If there are no matches, I need to delete the whole grouping. Below is my attempt, but I am mostly getting confused with 1. how to delete just a grouping and 2. how to call to each sheet/column correctly.
Sub DeleteAdjacent()
Dim wb1 As Workbook, Dim wb2 As Workbook, sh1 As Worksheet, sh2 As Worksheet
Dim lastrow1 As Long, Dim lastrow2 As Long, Dim i As Long, Dim j As Long
Set wb1 = Workbooks("Workbook1.xlsx")
Set wb2 = Workbooks("Workbook2.xlsx")
Set sh2 = wb2.Sheets(“Sheet B”)
Set sh1 = wb1.Sheets("Sheet A")
lastrow1 = sh1.Cells(Rows.Count, 1).End(xlUp).Row
lastrow2 = sh2.Cells(Rows.Count, 1).End(xlUp).Row
For j = lastrow1 To 1 Step -1
cell = "N" & j
cell1 = "N" & (j - 1)
Do While sh1.Cells(j, cell).Value = sh1.Cells(j, cell1).Value
For i = lastrow2 To 1 Step -1
cell2 = "C" & i
cell3 = "A" & j
If sh1.Cells(j, cell3).Value = sh2.Cells(i, cell2).Value Then
Do While sh1.Cells(j, cell).Value = sh1.Cells(j, cell1).Value
sh1.Range(j, cell).EntireRow.Delete
Loop
End If
Next i
Loop
Next j
End Sub
Edit: Looking at my attempt more closely, it would actually do the opposite of what I'd want to do. I attempted to delete the entire grouping when there was a match, when I actually want the exact opposite. I think then the part below should be changed.
If sh1.Cells(j, cell3).Value = sh2.Cells(i, cell2).Value Then
Do While sh1.Cells(j, cell).Value = sh1.Cells(j, cell1).Value
sh1.Range.Cells(j, cell).EntireRow.Delete
Loop
End If
My attempt at correcting this is maybe too simple?
If sh1.Cells(j, cell3).Value <> sh2.Cells(i, cell2).Value Then
Do While sh1.Cells(j, cell).Value = sh1.Cells(j, cell1).Value
sh1.Range.Cells(j, cell).EntireRow.Delete
Loop
End If
I think if I were attacking this problem I wouldn't compare A with C and do the group looping check in the same process. It might be easier to get your head around the issue if you create a map of values to groups first. Say a value of 10 exists in groups 1,3 and 5, then you could just check for a 10 and immediately eliminate 3 groups from your future checks. A Collection of Collections would serve you well for this as the look up by key is very fast and you don't have to worry about the number of items it stores.
If you also had a collection of Ranges for each group then it would be a simple process of eliminating matching groups and then, in one hit, delete all the remaining Ranges.
The code below should do that for you (but as with any row delete code, I'd suggest you back up your raw data first!):
Public Sub DeleteAdjacent()
Dim ws As Worksheet
Dim valueGroupMap As Collection
Dim groupRanges As Collection
Dim values As Collection
Dim lastRow As Long
Dim groupRng As Range
Dim valueCell As Range
Dim groupCell As Range
Dim rng As Range
Dim v As Variant
Dim r As Long
'Read the Column A worksheet
Set ws = Workbooks("Workbook1.xlsx").Worksheets("Sheet A")
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row + 1 '+1 to get a blank row at end
'Define the value map group ranges
Set valueGroupMap = New Collection
Set groupRanges = New Collection
Set groupRng = ws.Cells(1, "N")
For r = 1 To lastRow
Set valueCell = ws.Cells(r, "A")
Set groupCell = ws.Cells(r, "N")
If Len(CStr(groupCell.Value2)) = 0 Then
'We've reached the end of a group
Set rng = ws.Range(groupRng, groupCell.Offset(-1))
groupRanges.Add rng, CStr(groupRng.Value2)
Set groupRng = Nothing
Else
'We're working within a group
If groupRng Is Nothing Then
Set groupRng = groupCell
End If
'Create the value to group map
Set values = Nothing
On Error Resume Next
Set values = valueGroupMap(CStr(valueCell.Value2))
On Error GoTo 0
If values Is Nothing Then
Set values = New Collection
valueGroupMap.Add values, CStr(valueCell.Value2)
End If
values.Add CStr(groupRng.Value2)
End If
Next
'Read the Column C worksheet
Set ws = Workbooks("Workbook2.xlsx").Worksheets("Sheet B")
lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
On Error Resume Next
For r = 1 To lastRow
'Check if we have the value
Set values = Nothing
Set values = valueGroupMap(CStr(ws.Cells(r, "C").Value2))
If Not values Is Nothing Then
'We do, so remove the group ranges from our list
For Each v In values
groupRanges.Remove CStr(v)
Next
End If
Next
On Error GoTo 0
'Create a range of the groups still remaining in the list
Set rng = Nothing
For Each groupRng In groupRanges
If rng Is Nothing Then
Set rng = groupRng
Else
Set rng = Union(rng, groupRng)
End If
Next
'Delete that range
rng.EntireRow.Delete
End Sub