Need a way to make the loop faster in Excel VBA - excel

I work for a construction company. I have been writing a macro for the inventory department which can retrieve the latest date on which a particular type of material was supplied to a specific Flat No. at site.
The code which I have is doing the job but it's taking very long to compute all the results. Can anyone tell me how to make this go even faster.
Following is the code:
Sub FillTopSheet()
'Designing a loop to move through the fill data on Top Sheet
Application.ScreenUpdating = False
'Declaring variables for counts
Dim Flat_Row_Num As Long
Dim Tower_Col_Num As Long
Dim InventoryWs As Worksheet
'Debug.Print Application.Workbooks("The Crest DLF Project-In-Out Inventory Data.xlsx").Worksheets("Material-Out").Name
Set InventoryWs = Application.Workbooks("The Crest DLF Project-In-Out Inventory Data.xlsx").Worksheets("Material-Out")
Dim Lookup_Start_Row As Long
Dim Lookup_End_Row As Long
'Lookup_Start_Row = 4
'
'Select Case Application.ThisWorkbook.ActiveSheet.Name
' Case "Kitchen Carcass"
' Do Until InventoryWs.Cells(Lookup_Start_Row, 2).Value = "Kitchen Carcass"
' Lookup_Start_Row = Lookup_Start_Row + 1
' Loop
'
' Lookup_End_Row = Lookup_Start_Row
'
' Do While InventoryWs.Cells(Lookup_End_Row, 2).Value = "Kitchen Carcass"
' Lookup_End_Row = Lookup_End_Row + 1
'
' Loop
'
' Lookup_End_Row = Lookup_End_Row - 1
'
'End Select
Debug.Print Lookup_Start_Row
Debug.Print Lookup_End_Row
Lookup_Start_Row = 6162
Lookup_End_Row = 14754
Flat_Row_Num = 5
Tower_Col_Num = 5
Do Until Tower_Col_Num > 13
Do Until Flat_Row_Num > 154
If Application.ThisWorkbook.ActiveSheet.Cells(Flat_Row_Num, Tower_Col_Num - 1).Value <> "" Then
Do Until Lookup_Start_Row = Lookup_End_Row
If Application.ThisWorkbook.ActiveSheet.Cells(Flat_Row_Num, Tower_Col_Num - 1).Value = _
InventoryWs.Cells(Lookup_Start_Row, 8).Value Then
Application.ThisWorkbook.ActiveSheet.Cells(Flat_Row_Num, Tower_Col_Num).Value = _
InventoryWs.Cells(Lookup_Start_Row, 6).Value
GoTo RowReset
Else
Application.ThisWorkbook.ActiveSheet.Cells(Flat_Row_Num, Tower_Col_Num).Value = "NA"
End If
Lookup_Start_Row = Lookup_Start_Row + 1
Loop
Lookup_Start_Row = 6162
RowReset:
Lookup_Start_Row = 6162
End If
Flat_Row_Num = Flat_Row_Num + 1
Loop
Flat_Row_Num = 5
Tower_Col_Num = Tower_Col_Num + 2
Loop
Application.ScreenUpdating = True
End Sub

Could be something like this but indexes (i,j,k) might be mixed up.
Would be much easier if you could post your input and desired output (eg some screenshot)
Sub FillTopSheet()
'Declaring variables for counts
Dim Flat_Row_Num As Long
Dim Tower_Col_Num As Long
Dim InventoryWs As Worksheet, Ws As Worksheet
Dim ArrLookUp() As Variant, ArrData() As Variant
Dim Lookup_Start_Row As Long, Lookup_End_Row As Long, i As Long, j As Long, k As Long
Dim FlatNo As String
'Designing a loop to move through the fill data on Top Sheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'Debug.Print Application.Workbooks("The Crest DLF Project-In-Out Inventory Data.xlsx").Worksheets("Material-Out").Name
Set InventoryWs = Application.Workbooks("The Crest DLF Project-In-Out Inventory Data.xlsx").Worksheets("Material-Out")
Debug.Print Lookup_Start_Row
Debug.Print Lookup_End_Row
Lookup_Start_Row = 6162
Lookup_End_Row = 14754
Flat_Row_Num = 5
Tower_Col_Num = 2 'Start in Tower A "Flats No. column
With InventoryWs
ArrLookUp = .Range(.Cells(Lookup_Start_Row, 6), .Cells(Lookup_End_Row, 8))
End With
With Ws
ArrData = .Range(.Cells(Flat_Row_Num, Tower_Col_Num), .Cells(154, 13))
End With
For i = LBound(ArrData, 2) To UBound(ArrData, 2) Step 2
For j = LBound(ArrData) To UBound(ArrData)
'loop through "towers" Array
FlatNo = ArrData(j, i) 'take one flat no
If FlatNo <> "" Then
For k = LBound(ArrLookUp) To UBound(ArrLookUp)
'look for this flat no in other array
If FlatNo = ArrLookUp(k, 3) Then
'first match = take Date from other array
'dates sorted descending
ArrData(j, i + 1) = ArrLookUp(k, 1)
'found what was looking for, get out of loop
Exit For
End If
Next k
End If
Next j
Next i
With Ws
'range must be same as when you set the array earlier. But if that range contains some formulas they'll be overwriten with values
'in that case you can loop through array and take out only dates
.Range(.Cells(Flat_Row_Num, Tower_Col_Num), .Cells(154, 13)) = ArrData
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Edit
This still might need some adjustment to fit in your ranges
Check ranges in ArrLookUp = .Range(.Cells(Lookup_Start_Row, 6), .Cells(Lookup_End_Row, 8)) and ArrLookUp = .Range(.Cells(Lookup_Start_Row, 6), .Cells(Lookup_End_Row, 8))
It takes "Towers" and looks for "Flats No." in other sheet. At first match date is taken from other sheet.

Related

Find the number of duplicates (Column for particular record with each serial number)

I need the expert help in VBA Excel code. I need to find the number of duplicate record (AlertToString) for particular device serial number from the source sheet serial number and paste it to the other newly created output sheet by using VBA Macro.
Example (Source sheet):
Expected (Output Sheet with repeat Alert count) :
Source code as below :
Sub Alert700Count()
Dim AlertSource_Sh As Worksheet
Dim AlertOutput_Sh As Worksheet
'Insert a New Blank Worksheet
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("AlertOutput").Delete
Sheets.Add.Name = "AlertOutput"
Application.DisplayAlerts = True
Set AlertSource_Sh = ThisWorkbook.Sheets("SourceSheet")
Set AlertOutput_Sh = ThisWorkbook.Sheets("AlertOutput")
AlertOutput_Sh.Cells(1, 1) = "Serial No"
AlertOutput_Sh.Cells(1, 2) = "A92"
AlertOutput_Sh.Cells(1, 3) = "A95"
AlertOutput_Sh.Cells(1, 4) = "A98"
For Each sh In ActiveWorkbook.Worksheets
With sh.Range("A1:D1")
.Font.Bold = True
.WrapText = True
.CellWidth = 35
.Selection.Font.ColorIndex = 49
.Weight = xlMedium
.LineStyle = xlDash
End With
Next sh
AlertOutput_Sh.Range("A1:D1").Borders.Color = RGB(10, 201, 88)
AlertOutput_Sh.Columns("A:D").ColumnWidth = 12
AlertOutput_Sh.Range("A1:D1").Font.Color = rgbBlueViolet
AlertOutput_Sh.Range("A1:D1").Interior.Color = vbYellow
AlertOutput_Sh.Range("A1:D1").HorizontalAlignment = xlCenter
AlertOutput_Sh.Range("A1:D1").VerticalAlignment = xlTop
' Search the duplicate record and paste in output sheet
Dim A92Count As Long
A92Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A92")
AlertOutput_Sh.Cells(2, 2) = A92Count
Dim A95Count As Long
A95Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A95")
AlertOutput_Sh.Cells(2, 3) = A92Count
Dim A98Count As Long
A98Count = Application.CountIf(AlertSource_Sh.Range("D:D"), "A98")
AlertOutput_Sh.Cells(2, 4) = A98Count
End Sub
Current Output :
Use Dictionaries to build lists of unique values and an array to hold the counts.
Option Explicit
Sub Alert700Count()
Dim wsData As Worksheet, wsOut As Worksheet
Dim dictSerNo As Object, dictAlert As Object
Dim arData, arOut, k, rngOut As Range
Dim lastrow As Long, i As Long
Dim serNo As String, alert As String
Dim r As Long, c As Long, t0 As Single: t0 = Timer
Set dictSerNo = CreateObject("Scripting.Dictionary")
Set dictAlert = CreateObject("Scripting.Dictionary")
On Error Resume Next
Application.DisplayAlerts = False
Sheets("AlertOutput").Delete
Application.DisplayAlerts = True
On Error GoTo 0
Sheets.Add.Name = "AlertOutput"
Set wsOut = Sheets("AlertOutput")
Set wsData = Sheets("SourceSheet")
r = 1: c = 1
With wsData
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
arData = .Range("A1:D" & lastrow).Value2
' get unique serno and alert
For i = 2 To lastrow
serNo = arData(i, 1)
alert = arData(i, 4)
If dictSerNo.exists(serNo) Then
ElseIf Len(serNo) > 0 Then
r = r + 1
dictSerNo.Add serNo, r
End If
If dictAlert.exists(alert) Then
ElseIf Len(alert) > 0 Then
c = c + 1
dictAlert.Add alert, c
End If
Next
' rescan for counts
ReDim arOut(1 To r, 1 To c)
For i = 2 To lastrow
r = dictSerNo(CStr(arData(i, 1)))
c = dictAlert(CStr(arData(i, 4)))
arOut(r, c) = arOut(r, c) + 1
Next
End With
' add headers
arOut(1, 1) = "Serial No"
' sernos and alerts
For Each k In dictSerNo
arOut(dictSerNo(k), 1) = k
Next
For Each k In dictAlert
arOut(1, dictAlert(k)) = k
Next
' output counts
With wsOut
Set rngOut = .Range("A1").Resize(UBound(arOut), UBound(arOut, 2))
rngOut.Value2 = arOut
rngOut.Replace "", 0
.ListObjects.Add(xlSrcRange, rngOut, , xlYes).Name = "Table1"
.Range("A1").AutoFilter
.Range("A1").Select
End With
MsgBox "Done", vbInformation, Format(Timer - t0, "0.0 secs")
End Sub

How to extract numbers from string and if there are more than one, add them together?

Excel spreadsheet
I have a set of over 10,000 lines of text strings in column A (Input), and I need to get the number (in case there is only one) or a sum of both (in case there are two).
Code
Here is the VBA code I have:
Sub ExtractNumericStrings()
Dim rngTemp As Range
Dim strTemp As String
Dim currNumber1 As Currency
Dim currNumber2 As Currency
Dim lngTemp As Long
Dim lngPos As Long
Dim lngLastRow As Long
With ActiveSheet
lngLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For Each rngTemp In .Cells(1, "A").Resize(lngLastRow, 1) ' Set Range to look at
strTemp = rngTemp.Value2 ' Get string value of each cell
lngTemp = Len(strTemp) 'Get length of string
currNumber1 = 0 ' Reset value
currNumber2 = 0 ' Reset value
' Get first number
currNumber1 = fncGetNumericValue(strTemp, 1) ' Strip out first number
' Get second number if exists
' First strip out first number
strTemp = Replace(strTemp, currNumber1, "")
If Len(strTemp) <> 0 Then
currNumber2 = fncGetNumericValue(strTemp, 1)
End If
' now paste to sheet
If currNumber1 <> 0 And currNumber2 <> 0 Then
rngTemp.Offset(0, 1).Value = currNumber1 + currNumber2
rngTemp.Offset(0, 2).Value = "sum of the numbers"
ElseIf currNumber1 <> 0 Then
rngTemp.Offset(0, 1).Value = currNumber1
End If
Next rngTemp
End With
Call MsgBox("Procedure Complete!", vbOKOnly + vbInformation, "Procedure Complete")
End Sub
Private Function fncGetNumericValue(strTemp As String, lngStart As Long) As Currency
Dim varTemp As Variant
Dim lngCount As Long
Dim lngTemp As Long
' Reset
lngCount = 1
lngTemp = 1
varTemp = ""
On Error Resume Next
If IsNumeric(Left(strTemp, lngCount)) Then
Do While IsNumeric(Left(strTemp, lngCount)) = True
varTemp = Left(strTemp, lngCount)
lngCount = lngCount + 1
If lngCount > Len(strTemp) Then
Exit Do
End If
Loop
Else
' First clear non-numerics from string
lngTemp = 1
Do While IsNumeric(Left(strTemp, 1)) = False
lngTemp = lngTemp + 1
strTemp = Mid(strTemp, 2, Len(strTemp) - 1)
If lngTemp > Len(strTemp) Then
Exit Do
End If
Loop
' Then extract second number if exists
If strTemp <> "" Then
Do While IsNumeric(Mid(strTemp, lngCount, 1)) = True
varTemp = Left(strTemp, lngCount)
lngCount = lngCount + 1
If lngCount > Len(strTemp) Then
Exit Do
End If
Loop
End If
End If
' Retrun Value
If IsNumeric(varTemp) Then
fncGetNumericValue = CCur(varTemp)
Else
fncGetNumericValue = 0
End If
End Function
Here is what I'm trying to do:
https://www.youtube.com/watch?v=EjHnJVxuWJA
I have very limited knowledge of VBA, so please excuse me if I ask any stupid question. Running this thing successfully will save me hips of time. thanks!
Something like this:
Private Sub extract_num()
Dim cell as Range
Dim ws as Worksheet: Set ws = Sheets("Sheet1") ' replace Sheet1 with ur sheet name
Dim lr as Long: Set lr = ws.Cells(Rows.Count, 1).End(xlUp).Row
Dim values() As String
Dim i as Byte
Dim temp as Double
For Each cell in ws.Range("A2:A" & lr)
If Not isEmpty(cell) Then
values = Split(cell, " ")
For i = LBound(values) to UBound(values)
values(i) = Replace(values(i), ",", ".")
If isNumeric(values(i)) Then
temp = temp + values(i)
End If
Next i
cell.Offset(0, 2) = temp
temp = 0
End If
Next cell
End Function
This is presuming:
a) Individual words and numbers are always separated by space "123 abc 321"
b) Commas "," are used as an arithmetic floatpoint separator ##,##
Slightly different approach from Rawrplus
Option Explicit
Sub UpdateTotals()
Dim aRawValues As Variant
Dim iLRow&, iRow&, iArr&
Dim dTotal#
With ThisWorkbook.Worksheets("Sheet1") '<-- Change the sheet name to your sheet
iLRow = .Cells(Rows.Count, 1).End(xlUp).Row ' Get row count
For iRow = 1 To iLRow ' Loop through all rows in the sheet
aRawValues = Split(.Range("A" & iRow).Value, " ") ' Create and array of current cell value
For iArr = LBound(aRawValues) To UBound(aRawValues) ' Loop through all values in the array
dTotal = dTotal + ReturnDouble(Replace(aRawValues(iArr), ",", ".")) ' Add the returned double to total
Next
.Range("B" & iRow).Value = dTotal ' Set value in column B
dTotal = 0# ' Reset total
Next
End With
End Sub
Function ReturnDouble(ByVal sTextToConvert As String) As Double
Dim iCount%
Dim sNumbers$, sCurrChr$
sNumbers = ""
For iCount = 1 To Len(sTextToConvert)
sCurrChr = Mid(sTextToConvert, iCount, 1)
If IsNumeric(sCurrChr) Or sCurrChr = "." Then
sNumbers = sNumbers & sCurrChr
End If
Next
If Len(sNumbers) > 0 Then
ReturnDouble = CDbl(sNumbers)
Else
ReturnDouble = 0#
End If
End Function

How to receive all combinations of all columns?

I am trying to get all row combinations of all columns (say 8 columns). The following vba macro can do that but I get an error that says data overload:
Option Explicit
Const sTitle As String = "shg Cartesian Product"
Sub CartesianProduct()
' shg 2012, 2013
' Choose one from col A, one from col B, ...
Dim rInp As Range
Dim avInp As Variant ' ragged input list
Dim nCol As Long ' # columns in list
Dim rOut As Range ' output range
Dim iCol As Long ' column index
Dim iRow As Long ' row index
Dim aiCum() As Long ' cum count of arrangements from right to left
Dim aiCnt() As Long ' count of items in each column
Dim iArr As Long ' arrangement number
Dim avOut As Variant ' output buffer
Application.ScreenUpdating = False
Set rInp = Range("rgnInp")
If VarType(rInp.Value) = vbEmpty Then
MsgBox Prompt:="No input!", _
Buttons:=vbOKOnly, _
Title:=sTitle
Exit Sub
End If
Set rInp = rInp.CurrentRegion
If rInp.Columns.Count < 2 Or rInp.Rows.Count < 2 Then
MsgBox Prompt:="Must have more than one row and more than one columns!", _
Buttons:=vbOKOnly, _
Title:=sTitle
Exit Sub
End If
With rInp
.Style = "Input"
avInp = .Value
nCol = .Columns.Count
Set rOut = .Resize(1).Offset(.Rows.Count + 1)
Range(rOut.Offset(-1, -1), Cells(Rows.Count, Columns.Count)).Clear
End With
ReDim aiCum(1 To nCol + 1)
ReDim aiCnt(1 To nCol)
aiCum(nCol + 1) = 1
For iCol = nCol To 1 Step -1
For iRow = 1 To UBound(avInp, 1)
If IsEmpty(avInp(iRow, iCol)) Then Exit For
aiCnt(iCol) = aiCnt(iCol) + 1
Next iRow
aiCum(iCol) = aiCnt(iCol) * aiCum(iCol + 1) <------ This is where it says error is
Next iCol
If aiCum(1) > Rows.Count - rOut.Row + 1 Then
MsgBox Prompt:=Format(aiCum(1), "#,##0") & _
" is too many rows!", _
Buttons:=vbOKOnly, Title:=sTitle
Exit Sub
End If
ReDim avOut(1 To aiCum(1), 1 To nCol)
For iArr = 1 To aiCum(1)
For iCol = 1 To nCol
avOut(iArr, iCol) = avInp((Int((iArr - 1) * aiCnt(iCol) / aiCum(iCol))) Mod aiCnt(iCol) + 1, iCol)
Next iCol
Next iArr
With rOut.Resize(aiCum(1), nCol)
.NumberFormat = "#"
.Value = avOut
.Style = "Code"
.Cells(1, 0).Value = 1
.Cells(2, 0).Value = 2
.Cells(1, 0).Resize(2).AutoFill .Columns(0)
End With
ActiveWindow.FreezePanes = False
rOut.EntireColumn.AutoFit
ActiveSheet.UsedRange
Beep
End Sub
Is there away to adjust for this? I also want it to not bring back the same values for a row. So lets say that two columns had the exact same data. If column A has lets say Ice cream, cake, and cookies and so does Column B, I don't want Row 1 to have cookies in column B if it is already picked in Column A.

How to change my code to run it more speedy?

I've one workbook with 170K rows, I will delete all rows when the result between cells is 0,
For those operation, normally I use the code below, but with 170K (the rows will be deleted are 90K) the code run very slowly.
Someone know another way more performance.
Thank
Last = Cells(Rows.Count, "K").End(xlUp).Row
For i = Last To 2 Step -1
If (Cells(i, "K").Value + Cells(i, "L").Value) < 1 Then
Cells(i, "A").EntireRow.Delete
End If
Next i
As long as your fine putting the data on a new tab, the code below will do everything you need in 1.5 seconds.
Sub ExtractRows()
Dim vDataTable As Variant
Dim vNewDataTable As Variant
Dim vHeaders As Variant
Dim lastRow As Long
Dim i As Long, j As Long
Dim Counter1 As Long, Counter2 As Long
With Worksheets(1)
lastRow = .Cells(Rows.Count, "K").End(xlUp).row
vHeaders = .Range("A1:L1").Value2
vDataTable = .Range("A2:L" & lastRow).Value2
End With
For i = 1 To UBound(vDataTable)
If vDataTable(i, 11) + vDataTable(i, 12) > 0 Then
Counter1 = Counter1 + 1
End If
Next
ReDim vNewDataTable(1 To Counter1, 1 To 12)
For i = 1 To UBound(vDataTable)
If vDataTable(i, 11) + vDataTable(i, 12) > 0 Then
Counter2 = Counter2 + 1
For j = 1 To 12
vNewDataTable(Counter2, j) = vDataTable(i, j)
Next j
End If
Next
Worksheets.Add After:=Worksheets(1)
With Worksheets(2)
.Range("A1:L1") = vHeaders
.Range("A2:L" & Counter1 + 1) = vNewDataTable
End With
End Sub
Here, my approach for your problem according to rwilson's idea.
I already tested it. It very very reduce executing time. Try it.
Sub deleteRow()
Dim newSheet As Worksheet
Dim lastRow, newRow As Long
Dim sheetname As String
Dim startTime As Double
sheetname = "sheetname"
With Sheets(sheetname)
Set newSheet = ThisWorkbook.Worksheets.Add(After:=Sheets(.Name))
'Firstly copy header
newSheet.Rows(1).EntireRow.Value = .Rows(1).EntireRow.Value
lastRow = .Cells(.Rows.Count, "K").End(xlUp).row
newRow = 2
For row = 2 To lastRow Step 1
If (.Cells(row, "K").Value + .Cells(row, "L").Value) >= 1 Then
newSheet.Rows(newRow).EntireRow.Value = .Rows(row).EntireRow.Value
newRow = newRow + 1
End If
Next row
End With
Application.DisplayAlerts = False
Sheets(sheetname).Delete
Application.DisplayAlerts = True
newSheet.Name = sheetname
End Sub
Here is a non-VBA option you can try:
In column M compute the sum of columns K and L
Highlight column M and the click Find and select > Find
Type in 0 in the Find what box and also select values in the Look in box
Select Find all and in the box that shows the found items select all entires (click in the box and press CTRL + A)
On the ribbon select Delete and then Delete sheet rows
Now manually delete column M
I haven't tried this with 170k+ rows but maybe worth assessing performance versus the VBA loop.
thank at all for your ideas but the really fast code is: use an array tu populate whit the correct date and replare all table of the end sort the table:
Sub Macro13(control As IRibbonControl)
Dim avvio As Date
Dim arresto As Date
Dim tempo As Date
Application.ScreenUpdating = False
Application.Calculation = xlManual
avvio = Now()
Dim sh As Worksheet
Dim arng As Variant
Dim arrdb As Variant
Dim UR As Long, x As Long, y As Long
Dim MyCol As Integer
Set sh = Sheets("Rol_db")
MyCol = 1
sh.Select
UR = sh.Cells(Rows.Count, MyCol).End(xlUp).Row
ReDim arrdb(2 To UR, 1 To 12) As Variant
For x = 2 To UR
If Cells(x, 11) + Cells(x, 12) > 0 Then
For y = 1 To 12
arrdb(x, y) = Cells(x, y)
Next y
Else
For y = 1 To 12
arrdb(x, y) = ""
Next y
End If
Next x
sh.Range("A2:L" & UR) = arrdb
arresto = Now()
tempo = arresto - avvio
Debug.Print "Delete empty rows " & tempo
Range("A2:L" & UR).Sort key1:=Range("A2:L" & UR), _
order1:=xlAscending, Header:=xlNo
Range("A4").Select
ActiveWindow.FreezePanes = True
conclusioni:
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
time for my sheet 170K 00:00:07.
as soon as I have a minute I feel a loop of the columns

How do I use excel to create random names with values between a certain amount?

I have 100 names in one column. And next to each name in the next cell is a numerical value that the name is worth.There are 6 positions in a company that each name could potentially hold. And that is also in a cell next to each name.
So the spreadsheet looks something like this.
John Smith Lawyer $445352
Joe Doe Doctor $525222
John Doe Accountant $123192
etc....
I want excel to give me 10 people who make a combined amount between 2 and 3 million dollars. But I require that 2 of the people be doctors 2 be lawyers and 2 be accountants etc. How would I create this?
I set up sheet 1 with the following data:
Goal:
Return 10 people
Salary between 1000000 and 6000000 range
Min 2 each doc, lawyer, accountant
Run this Macro:
Sub macro()
Dim rCell As Range
Dim rRng As Range
Dim rangelist As String
Dim entryCount As Long
Dim totalnum As Long
Set rRng = Sheet1.Range("A1:A12")
Dim OccA As String
Dim OccCntA As Long
Dim OccASalmin As Long
Dim OccASalmax As Long
Dim OccB As String
Dim OccCntB As Long
Dim OccBSalmin As Long
Dim OccBSalmax As Long
Dim OccC As String
Dim OccCntC As Long
Dim OccCSalmin As Long
Dim OccCSalmax As Long
'Set total number of results to return
totalnum = 10
'Set which occupations that must be included in results
OccA = "Accountant"
OccB = "Doctor"
OccC = "Lawyer"
'Set minimum quantity of each occupation to me returned in results
OccCntA = 2
OccCntB = 2
OccCntC = 2
'Set min and max salary ranges to return for each occupation
OccASalmin = 1000000
OccASalmax = 6000000
OccBSalmin = 1000000
OccBSalmax = 6000000
OccCSalmin = 1000000
OccCSalmax = 6000000
'Get total number of entries
entryCount = rRng.Count
'Randomly get first required occupation entries
'Return list of rows for each Occupation
OccAList = PickRandomItemsFromList(OccCntA, entryCount, OccA, OccASalmin, OccASalmax)
OccBList = PickRandomItemsFromList(OccCntB, entryCount, OccB, OccBSalmin, OccBSalmax)
OccCList = PickRandomItemsFromList(OccCntC, entryCount, OccC, OccCSalmin, OccCSalmax)
For Each i In OccAList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
For Each i In OccBList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
For Each i In OccCList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
'Print the rows that match criteria
Dim rCntr As Long
rCntr = 1
Dim nRng As Range
Set nRng = Range(rangelist)
For Each j In nRng
Range(j, j.Offset(0, 2)).Select
Selection.Copy
Range("E" & rCntr).Select
ActiveSheet.Paste
rCntr = rCntr + 1
Next j
'Get rest of rows randomly and print
OccList = PickRandomItemsFromListB(totalnum - rCntr + 1, entryCount, rangelist)
For Each k In OccList
Set Rng = Range("A" & k)
Range(Rng, Rng.Offset(0, 2)).Select
Selection.Copy
Range("E" & rCntr).Select
ActiveSheet.Paste
rCntr = rCntr + 1
Next k
End Sub
Function PickRandomItemsFromListB(nItemsToPick As Long, nItemsTotal As Long, avoidRng As String)
Dim rngList As Range
Dim idx() As Long
Dim varRandomItems() As Variant
Dim i As Long
Dim j As Long
Dim booIndexIsUnique As Boolean
Set rngList = Range("B1").Resize(nItemsTotal, 1)
ReDim idx(1 To nItemsToPick)
ReDim varRandomItems(1 To nItemsToPick)
For i = 1 To nItemsToPick
Do
booIndexIsUnique = True ' Innoncent until proven guilty
idx(i) = Int(nItemsTotal * Rnd + 1)
For j = 1 To i - 1
If idx(i) = idx(j) Then
' It's already there.
booIndexIsUnique = False
Exit For
End If
Next j
Set isect = Application.Intersect(Range("A" & idx(i)), Range(avoidRng))
If booIndexIsUnique = True And isect Is Nothing Then
Exit Do
End If
Loop
varRandomItems(i) = idx(i)
Next i
PickRandomItemsFromListB = varRandomItems
' varRandomItems now contains nItemsToPick unique random
' items from range rngList.
End Function
Function PickRandomItemsFromList(nItemsToPick As Long, nItemsTotal As Long, Occ As String, Salmin As Long, Salmax As Long)
Dim rngList As Range
Dim idx() As Long
Dim varRandomItems() As Variant
Dim i As Long
Dim j As Long
Dim booIndexIsUnique As Boolean
Set rngList = Range("B1").Resize(nItemsTotal, 1)
ReDim idx(1 To nItemsToPick)
ReDim varRandomItems(1 To nItemsToPick)
For i = 1 To nItemsToPick
Do
booIndexIsUnique = True ' Innoncent until proven guilty
idx(i) = Int(nItemsTotal * Rnd + 1)
For j = 1 To i - 1
If idx(i) = idx(j) Then
' It's already there.
booIndexIsUnique = False
Exit For
End If
Next j
If booIndexIsUnique = True And Range("B" & idx(i)).Value = Occ And Range("B" & idx(i)).Offset(0, 1).Value >= Salmin And Range("B" & idx(i)).Offset(0, 1).Value <= Salmax Then
Exit Do
End If
Loop
varRandomItems(i) = idx(i)
Next i
PickRandomItemsFromList = varRandomItems
End Function
Results are printed in column E with the first results meeting the criteria. After those, the rest are random but don't repeat the previous ones:
I'm not doing very much error checking such as what happens if there are not 2 doctors or not enough entries left to meet the required number of results. You'll have to fine tune it for your purposes. You'll probably also want to set up the inputs as a form so you don't have to mess with code every time you change your criteria.

Resources