I cleaned this up some and here is the demo.
In the demo there are three tables
tblRequirements7
tblRequirements50
tblRequirements200
The first table is the original problem set
The second is 50 states with possible calls per state from 0 - 100
The third is a notional 200 "state" with required calls from 0 - 1000
In order to see this work rename one of those to "tblRequirements"
In the form first select "Initialize", then choose "Improve". This will show the swaps and the improving Standard dev
I did not try to add a steal/give function but the swap works pretty well. Less then your 7 assignments and this may not work very well. More then a thousand and it may not run.
The alogrithm is neither optimal or efficient. It can probably handle up to a thousand different assignments. With 200 it takes about 40 seconds to improve. With 200 requirements the starting Sdev is 312 and the improved is 1.4. The difference in staffer with the most calls compared to staffer with the least calls goes from about 730 down to 3. Probably not going to get a lot of complaints with one staffer having to call 24,629 and the other 24,626.
Here is the basic algorithm
Sort the required assignments by required number of calls
Deal out Assignments
Do while able to improve
Get Staffer with Most total calls
If Staffer has more than the avg
Try to improve by swapping down with someone who has less than avg
else
Try to improve by swapping up with someone who has more than avg
end if
Next Staffer
Loop while improving
SwapUp
for the current Staffer get their smallest assignment
get biggest assignment from other staffers that have more than avg
if biggest available greater then smallest assignment
Try to improve by swapping
(decrease the Standarddev among total assignments)
end if
next other staffer largest assignments
next current staffer smallest assignment
Here is the updated code.
Code:
Option Compare Database
Option Explicit
Public TxtOut As TextBox
Public Sub InitialAssignments()
Dim RSState As DAO.Recordset
Dim RSStaffers As DAO.Recordset
Dim RSAssignment As DAO.Recordset
Dim strSql As String
Dim StafferID As Long
Dim StateID As String
ClearAssignments
Set RSState = CurrentDb.OpenRecordset("SELECT * FROM tblRequirements ORDER BY RequiredCalls")
Set RSStaffers = CurrentDb.OpenRecordset("SELECT * FROM tblStaffers ORDER BY StafferID")
Do While Not RSState.EOF
If RSStaffers.EOF Then RSStaffers.MoveFirst
StateID = RSState!StateID
StafferID = RSStaffers!StafferID
RSStaffers.MoveNext
strSql = "INSERT INTO tblAssignments (StafferID, StateID) VALUES (" & StafferID & ", '" & StateID & "')"
CurrentDb.Execute strSql
RSState.MoveNext
Loop
MsgBox "Initial Assignments"
End Sub
Public Sub ClearAssignments()
Dim strSql As String
strSql = "Delete * from tblAssignments"
CurrentDb.Execute strSql
End Sub
Public Sub ImproveBySwapping()
Dim RSTotals As DAO.Recordset
Dim strSql As String
Dim StafferID As Long
Dim TotalCalls As Long
Dim AvgCalls As Double
Dim DiffFromAvg As Double
Dim improved As Boolean
'kick start
improved = True
Do While improved
improved = False
Set RSTotals = CurrentDb.OpenRecordset("Select * from qryStafferTotals ORDER BY SumOfRequiredCalls")
Do While Not RSTotals.EOF
StafferID = RSTotals!StafferID
TotalCalls = GetTotalCalls(StafferID)
AvgCalls = GetAvgCalls
DiffFromAvg = AvgCalls - TotalCalls
If DiffFromAvg > 0 Then
'Can possibly improve by adding more
improved = TryToSwapForMore(StafferID)
'If improved Then RSTotals.MoveFirst
ElseIf DiffFromAvg < 0 Then
'Can possibly improve by reducing
improved = TryToSwapForLess(StafferID)
'If improved Then RSTotals.MoveFirst
End If
TxtOut = TxtOut & "Staffer " & StafferID & " TotalCalls " & TotalCalls & " AvgCalls " & AvgCalls & vbCrLf
RSTotals.MoveNext
Loop
Loop
MsgBox "Done Swaps"
TxtOut = TxtOut & "StandardDev: " & GetCurrentSDev
End Sub
Public Function TryToSwapForMore(StafferID As Long) As Boolean
Dim PriorSDev As Double
Dim RSMyAssignments As DAO.Recordset
Dim RSAvailableAssignments As DAO.Recordset
Dim GiveState As String
Dim GiveAmount As Long
Dim TakeState As String
Dim TakeAmount As Long
Dim TakeStaffer As Long
Dim isSwapped As Boolean
PriorSDev = GetCurrentSDev
Set RSMyAssignments = CurrentDb.OpenRecordset("Select * from QryAssignments where StafferID = " & StafferID & " ORDER BY RequiredCalls")
Set RSAvailableAssignments = CurrentDb.OpenRecordset("Select * from QryAssignments where StafferID <> " & StafferID & " AND StafferID not in (SELECT StafferID From qryStaffersLessThanAverage) ORDER BY RequiredCalls Desc")
Do While Not RSMyAssignments.EOF
GiveState = RSMyAssignments!StateID
GiveAmount = RSMyAssignments!RequiredCalls
If GetAvgCalls <= GetTotalCalls(StafferID) Then Exit Function
Do While Not RSAvailableAssignments.EOF And Not isSwapped
TakeState = RSAvailableAssignments!StateID
TakeAmount = RSAvailableAssignments!RequiredCalls
TakeStaffer = RSAvailableAssignments!StafferID
If TakeAmount > GiveAmount Then
PriorSDev = GetCurrentSDev
' Debug.Print "GIVE: " & GiveState & " " & GiveAmount
' Debug.Print "TAKE: " & TakeState & " " & TakeAmount
SwapAssignments StafferID, TakeStaffer, GiveState, TakeState
'Debug.Print PriorSDev & " " & GetCurrentSDev
If PriorSDev - GetCurrentSDev < 0.0001 Then
'SwapBack
' Debug.Print "swapBack"
SwapAssignments StafferID, TakeStaffer, TakeState, GiveState
Else
TxtOut = TxtOut & "Swapped Up: Staffer " & StafferID & " Give: " & GiveState & " " & GiveAmount & " Staffer " & TakeStaffer & " Take: " & TakeState & " " & TakeAmount & vbCrLf
TxtOut = TxtOut & "SDev: " & GetCurrentSDev & vbCrLf & vbCrLf
isSwapped = True
TryToSwapForMore = True
End If
End If
RSAvailableAssignments.MoveNext
Loop
RSMyAssignments.MoveNext
Loop
End Function
Public Function TryToSwapForLess(StafferID As Long) As Boolean
Dim PriorSDev As Double
Dim RSMyAssignments As DAO.Recordset
Dim RSAvailableAssignments As DAO.Recordset
Dim GiveState As String
Dim GiveAmount As Long
Dim TakeState As String
Dim TakeAmount As Long
Dim TakeStaffer As Long
Dim isSwapped As Boolean
PriorSDev = GetCurrentSDev
Set RSMyAssignments = CurrentDb.OpenRecordset("Select * from QryAssignments where StafferID = " & StafferID & " ORDER BY RequiredCalls Desc")
Set RSAvailableAssignments = CurrentDb.OpenRecordset("Select * from QryAssignments where StafferID <> " & StafferID & " AND StafferID not in (SELECT StafferID From qryStaffersMoreThanAverage)ORDER BY RequiredCalls")
Do While Not RSMyAssignments.EOF
GiveState = RSMyAssignments!StateID
GiveAmount = RSMyAssignments!RequiredCalls
If GetAvgCalls >= GetTotalCalls(StafferID) Then Exit Function
Do While Not RSAvailableAssignments.EOF And Not isSwapped
TakeState = RSAvailableAssignments!StateID
TakeAmount = RSAvailableAssignments!RequiredCalls
TakeStaffer = RSAvailableAssignments!StafferID
If TakeAmount < GiveAmount Then
PriorSDev = GetCurrentSDev
' Debug.Print "GIVE: " & GiveState & " " & GiveAmount
' Debug.Print "TAKE: " & TakeState & " " & TakeAmount
SwapAssignments StafferID, TakeStaffer, GiveState, TakeState
'Debug.Print PriorSDev & " " & GetCurrentSDev
If PriorSDev - GetCurrentSDev < 0.0001 Then
'SwapBack
' Debug.Print "swapBack"
SwapAssignments StafferID, TakeStaffer, TakeState, GiveState
Else
TxtOut = TxtOut & "Swapped Down: Staffer " & StafferID & " Give: " & GiveState & " " & GiveAmount & " Staffer " & TakeStaffer & " Take: " & TakeState & " " & TakeAmount & vbCrLf
TxtOut = TxtOut & "SDev: " & GetCurrentSDev & vbCrLf & vbCrLf
isSwapped = True
TryToSwapForLess = True
End If
End If
RSAvailableAssignments.MoveNext
Loop
RSMyAssignments.MoveNext
Loop
End Function
Public Sub SwapAssignments(GiveStaffer As Long, TakeStaffer As Long, GiveState As String, TakeState As String)
Dim strSql As String
strSql = " UPDATE tblAssignments SET StafferID = " & GiveStaffer & " WHERE StateID= '" & TakeState & "'"
' Debug.Print strSql
CurrentDb.Execute strSql
strSql = " UPDATE tblAssignments SET StafferID = " & TakeStaffer & " WHERE StateID= '" & GiveState & "'"
'Debug.Print strSql
CurrentDb.Execute strSql
End Sub
Public Function GetCurrentSDev() As Double
GetCurrentSDev = DLookup("STdevRequiredCalls", "qryStats")
End Function
Public Function GetAvgCalls() As Double
GetAvgCalls = DLookup("avgRequiredCalls", "qryStats")
End Function
Public Function GetTotalCalls(StafferID As Long) As Long
GetTotalCalls = DLookup("SumOfRequiredCalls", "qryStafferTotals", "StafferID = " & StafferID)
End Function