I have a string of predefined worksheets, that I need to run specific code for. I get a compile error.
The code is set up to copy data from one sheet to another.
How do I do the same for multiple sheets?
When I step through the code sht is showing the MHP60,MHP61,MHP62 and not just MHP60.
I get a subscript out of range error.
Sub Prepare_CYTD_Report()
Dim addresses() As String
Dim addresses2() As String
Dim SheetNames() As String
Dim SheetNames2() As String
Dim wb1 As Workbook, wb2 As Workbook
Dim my_Filename
'Declare variables for MHP60, MHP61, MHP62 Trial Balance Values
Dim i, lastcol As Long
Dim tabNames, cell As Range
Dim tabName As String
Dim sht As Variant
addresses = Strings.Split("A9,A12:A26,A32:A38,A42:A58,A62:A70,A73:A76,A83:A90", ",") 'Trial Balance string values
addresses2 = Strings.Split("G9,G12:G26,G32:G38,G42:G58,G62:G70,G73:G76,G83:G90", ",") 'Prior Month string values
SheetNames = Strings.Split("MHP60,MHP61,MHP62")
'SheetNames2 = Strings.Split("MHP60-CYTDprior,MHP61-CYTDprior,MHP62-CYTDprior")
Set wb1 = ActiveWorkbook 'Revenue & Expenditure Summary Workbook
'*****************************Open CYTD files
my_Filename = Application.GetOpenFilename(fileFilter:="Excel Files,*.xl*;*.xm*", Title:="Select File to create CYTD Reports")
If my_Filename = False Then
Exit Sub
End If
Application.ScreenUpdating = False
Set wb2 = Workbooks.Open(my_Filename)
'*****************************Load Column Header Strings & Copy Data
For Each sht In SheetNames
lastcol = wb1.Sheets(sht).Cells(5, Columns.Count).End(xlToLeft).Column
On Error Resume Next
Set tabNames = wb1.Sheets(sht).Cells(4, 3).Resize(1, lastcol - 2).SpecialCells(xlCellTypeConstants)
'actual non-formula text values on row 4 from column C up to column lastCol'
On Error GoTo 0
If Err.Number <> 0 Then
MsgBox "No headers were found on row 4 of MHP60", vbCritical
Exit Sub
End If
For Each cell In tabNames
tabName = Strings.Trim(cell.Value2)
'dedicated variable in case of requirement for further parsing (space/comma elimination?)'
If CStr(wb1.Sheets(sht).Evaluate("ISREF('[" & wb2.Name & "]" & tabName & "'!$A$1)")) = "True" Then
'If wb2 has a tab named for the value in tabName
For i = 0 To UBound(addresses)
wb2.Sheets(tabName).Range(addresses(i)).Value2 = wb1.Sheets(sht).Range(addresses(i)).Offset(0, cell.Column - 1).Value2
'Debug.Print "data for " & wb2.Sheets(tabName).Range(addresses(i)).Address(, , , True) & " copied from " & wb1.Sheets("MHP60").Range(addresses(i)).Offset(0, cell.Column - 1).Address(, , , True)
Next i
Else
Debug.Print "A tab " & tabName & " was not found in " & wb2.Name
End If
Next cell
Next sht
MsgBox "CYTD Report Creation Complete", vbOKOnly
Application.ScreenUpdating = True
End Sub
Split by what?
SheetNames = Strings.Split("MHP60,MHP61,MHP62")
Split by comma? Then use the following instead:
SheetNames = Strings.Split("MHP60,MHP61,MHP62", ",")
Alternative
Dim SheetNames() As Variant ' needs to be Variant to work with Array()
SheetNames = Array("MHP60", "MHP61", "MHP62")
This should be quicker as your macro does not need to split the string and has it as array directly.
I am getting an error for connecting to the sql error is named pipes provider could not open a connection to SQL Server 1265. Here is the code and it worked yesterday and when I check it today it is not working and I get the error.
Here is the vb code:
'Require all variables to be defined
'to prevent rogue variables and limit
'debugging time
Option Explicit
'====================================================================================
' GLOBAL VARIABLES
'====================================================================================
Private Const g_sqlServer = "EWNVM-2017U3"
Private g_lStartDate As Long
Private g_nDaysInMonth As Integer
Public Enum mrReportType
mrDailyReport
mrMonthlyReport
mrYearlyReport
End Enum
'====================================================================================
' GetData(nYear, nMonth)
'====================================================================================
Public Sub GetData(ByVal eReportType As mrReportType, ByVal nYear As Integer, Optional ByVal nMonth As Integer = 1, Optional ByVal nDay As Integer)
' On Error GoTo ErrorHandler
Dim cMRReport As New MRReport
Dim adoConn As New ADODB.Connection
Dim adoRS As New ADODB.Recordset
Dim sSqlQuery As String
Dim sStartDateFmt As String
Dim i, k As Integer
Dim sLink As String
'Get Start Date
g_lStartDate = cMRReport.GetStartDate(nYear, nMonth, nDay)
'Write report Date to RawData sheet to use on other sheets
RawData.Range("A1") = Format(g_lStartDate, "mm/yyyy")
'Show Progress Bar Form
cMRReport.ShowProgressBar
'===========================================================================================================================================
'Historian Database Queries
'===========================================================================================================================================
adoConn.ConnectionString = "Provider='SQLNCLI11';Data Source='" & g_sqlServer & "';Initial Catalog='MR_Carrolton_DB';User ID='mrsystems';Password='Reggie#123';"
adoConn.CursorLocation = adUseClient
adoConn.Open
'Daily Report Type
RawData.Range("B4", "AZ39").ClearContents
cMRReport.SetHeader Sheet2, Positioncenter, "Monthly WAS Tank Blower Runtimes Report" & vbCr & Format(g_lStartDate, "mmmm yyyy")
cMRReport.SetHeader Sheet2, PositionRight, "Pee Dee River WWTP" & vbCr & "City of Florence, SC"
QueryRuntimesDaily adoConn, adoRS, cMRReport
'Close Historian DB Connection
adoConn.Close
'-------------------------------------------------------------------------------------------------------------------------------------------
'Cleanup memory by closing
'classes we initialized
Set adoRS = Nothing
Set adoConn = Nothing
Set cMRReport = Nothing
Exit Sub
ErrorHandler:
'Clean Up
If Not adoConn Is Nothing Then
If adoConn.State = adStateOpen Then adoConn.Close
End If
Set adoConn = Nothing
cMRReport.HandleError err, "Report", "GetData"
End Sub
'===========================================================================================================================================
'Historian Database Queries Functions
'===========================================================================================================================================
'-----------------------------------------
'Query for Flow Totals Daily
'-----------------------------------------
Private Sub QueryRuntimesDaily(ByVal adoConn As ADODB.Connection, ByRef adoRS As ADODB.Recordset, cMRReport As MRReport)
' On Error GoTo ErrorHandler
Dim sSqlQuery As String
Dim i As Integer
Dim startDateSerial
Dim endDateSerial
startDateSerial = CDec(DateAdd("n", 1 * i, g_lStartDate))
' MsgBox startDateSerial
endDateSerial = CDec(DateAdd("n", 1 * i + 15, g_lStartDate))
' MsgBox endDateSerial
For i = 0 To 95
' sSqlQuery = "SELECT LogDateTime, CL2_RESIDUAL,ZW1_TURBIDITY,ZW2_TURBIDITY,ZW3_TURBIDITY,ZW4_TURBIDITY FROM MR_Carrolton_DB.dbo.DailyRuntimes ORDER BY LogDateTime"
sSqlQuery = "SELECT LogDateTime, CL2_RESIDUAL " & _
" FROM MR_Carrolton_DB.dbo.DailyRuntimes" & _
" WHERE LogDateTime >= " & startDateSerial & _
" AND LogDateTime < " & endDateSerial & _
" ORDER BY LogDateTime"
'Copy sSqlQuery value to RawData worksheet for troubleshooting
RawData.Range("B2").Value = sSqlQuery
'Open recordset (executes SQL query)
adoRS.Open sSqlQuery, adoConn, 0, 1, 1
'If recordset is not empty then copy data to raw sheet
If adoRS.BOF = False And adoRS.EOF = False Then
RawData.Cells((i + 4), 2).CopyFromRecordset adoRS
End If
'Close recordset after each query
adoRS.Close
'Update Progress Bar
cMRReport.UpdateProgressBar i, 96, "Querying for Daily Runtimes..."
'Prevent VBA from locking up Excel
'while running through loops
DoEvents
Next i
Exit Sub
ErrorHandler:
'Clean Up
If Not adoConn Is Nothing Then
If adoConn.State = adStateOpen Then adoConn.Close
End If
Set adoConn = Nothing
cMRReport.HandleError err, "Report", "QueryRuntimesMonthly"
End Sub
'-----------------------------------------
' Lock/Unlock Worksheets
'-----------------------------------------
Public Sub LockWorksheets()
Dim ws As Worksheet
Dim i As Integer
For Each ws In Worksheets
ws.Protect "reggie"
Next
End Sub
Public Sub UnLockWorksheets()
Dim ws As Worksheet
Dim i As Integer
For Each ws In Worksheets
ws.Unprotect "reggie"
Next
End Sub
it seems it is more of server administration issue than a coding one. Ping your server to check if there is connectivity problem. Your connection does not persist so you must check your "hosts" file and Sql Server settings if they are properly set.
Visit this page for step by step troubleshooting:
Resolving could not open a connection to sql server errors
Currently my array brings up three different msgboxes, as shown with the Functions 'Expired', 'Expiring', and 'NoTraining'. The array msgboxes display information based on whether a date is Expired (older than current date), Expiring (within 31 days), and is a date is missing (NoTraining). No matter what, these msgboxes for the array will always come up, but will sometimes be blank (depending on the criteria in the SELECT CASE statement). Does anyone know anyway of coding it so that if the msgboxes were to come up blank (if nothing fits the criteria), a different message will be shown in the box? I can't get the collection and boolean NoExpiredTraining to work properly bringing up an overall msgbox instead of the array msgboxes, so I am unsure what to do.
This is my code:
Sub Expire_New()
Dim arr() As Variant
Dim msg(1 To 3) As String
Dim x As Long
Dim nDx As Long
Dim dDiff As Long
LDays = 31
'I would recommend using a named sheet rather than
'ActiveSheet as this can change unexpectedly
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("Support Staff")
With ws
x = .Cells(.Rows.Count, TRAINING_DATE_COL).End(xlUp).Row
arr = .Cells(21, 1).Resize(x - 20, 26).Value
End With
'I am a big fan of collections. They make code easier to read
'and to implement. The collection below will be scanned to
'see if there are any training dates that are set to expire within
'30 days or if there are people without any training
Dim colTrainingDate As Collection
Set colTrainingDate = CopyArrDimToCollection(arr, TRAINING_DATE_COL)
'This boolean will be used to control continued flow of the
'macro. If NoExpiredTraining gets set to false, then there
'are people who must complete training.
'Dim NoExpiredTraining As Boolean: NoExpiredTraining = True
For x = LBound(arr, NAME_COL) To UBound(arr, NAME_COL)
'Since every row requires a Name and Surname columns
'to have data in them, let's check this first.
'If a row doesn't have a name then skip it.
If arr(x, NAME_COL) <> "" And arr(x, SURNAME_COL) <> "" Then
'Always good practice to declare your variables/objects
'relevant to where they will be used
'vDx is an index used to loop through the collection of
'Training Dates. This is checking to see if any training
'Dates are empty or less than 31 days from expiration
Dim vDx As Variant
For Each vDx In colTrainingDate
If vDx = "" Then
'blank date means needs training
NoExpiredTraining = False
ElseIf DateDiff("d", Date, vDx) < 31 Then
'less than 31 days means needs training
NoExpiredTraining = False
End If
Next
'At this point you can determine if you want to continue
'If there is no expired training, display the message and exit
'the sub.
If NoExpiredTraining Then
'msg(4) = MsgBox("There are either no ...
'is only used if want to do something based on
'what button the user pressed. Otherwise use
'the Method form of MsgBox
MsgBox "There are either no expired safeguarding certificates, or no certificate expiring within the next 31 days.", vbCritical, "Warning"
Exit Sub
Else
'There is expired training. Let's collect the status
'of each individual
If arr(x, TRAINING_DATE_COL) = "" Then
'if the training date column is empty
'put a really big default value in dDiff
'otherwise you have to trap an error with DateDiff
'and handle it
dDiff = 100
Else
'training date column has a date value
dDiff = DateDiff("d", Date, arr(x, TRAINING_DATE_COL))
End If
'Now let's see what the training status for the person is
Select Case dDiff
Case Is <= 0: 'Training is expired
msg(1) = Expired(msg(1), _
arr(x, NAME_COL), _
arr(x, 2), _
arr(x, TRAINING_DATE_COL))
Case Is <= 31: 'Training is expiring
msg(2) = Expiring(msg(2), _
arr(x, NAME_COL), _
arr(x, 2), _
arr(x, TRAINING_DATE_COL), dDiff)
End Select
If Len(arr(x, 19)) = 0 And Len(arr(x, 1)) > 0 And Len(arr(x, 2)) > 0 Then
msg(3) = NoTraining(msg(3), arr(x, 1), arr(x, 2), arr(x, 18))
End If
End If
End If
Next x
'Because of the Exit Sub statement above, the code bwlow
'will only execute if there are expired, expiring or missing
'training
For x = LBound(msg) To UBound(msg)
msg(x) = Replace(msg(x), "#NL", vbCrLf)
If Len(msg(x)) < 1024 Then
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
Else
MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
End If
Next x
Erase arr
Erase msg
End Sub
'***************************************************************************
'**
'** This fucntion copies all rows of data for the column specified into
'** a collection
Private Function CopyArrDimToCollection(ByRef mMultiDimArray() As Variant, _
ByVal mColumnToCopy As Long) As Collection
Dim retVal As New Collection
Dim nDx As Long
For nDx = LBound(mMultiDimArray, 1) To UBound(mMultiDimArray, 1)
retVal.Add mMultiDimArray(nDx, mColumnToCopy)
Next
Set CopyArrDimToCollection = retVal
End Function
Private Function Expired(ByRef msg As String, ByRef var1 As Variant, ByRef
var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRED Safeguading
Certificates#NL#NL"
Expired = msg & "(#var3) #var1 #var2#NL"
Expired = Replace(Expired, "#var1", var1)
Expired = Replace(Expired, "#var2", var2)
Expired = Replace(Expired, "#var3", var3)
End Function
Private Function Expiring(ByRef msg As String, ByRef var1 As Variant, ByRef
var2 As Variant, ByRef var3 As Variant, ByRef d As Long) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRING Safeguarding
Certificates#NL#NL"
Expiring = msg & "(#var3) #var1 #var2 (#d days remaining)#NL"
Expiring = Replace(Expiring, "#var1", var1)
Expiring = Replace(Expiring, "#var2", var2)
Expiring = Replace(Expiring, "#var3", var3)
Expiring = Replace(Expiring, "#d", d)
End Function
Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant,
ByRef
var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR #NL#NL"
NoTraining = msg & " #var1 #var2#NL"
NoTraining = Replace(NoTraining, "#var1", var1)
NoTraining = Replace(NoTraining, "#var2", var2)
NoTraining = Replace(NoTraining, "#var3", var3)
End Function
I need a msgbox to appear if nothing has expired, is expiring, and as long as there is data in 1, 2, and 19. Currently it displays it for anyone fitting the above, but it should only come up if every single row fits the above. It should then reject the other msgboxes from coming up.
Please see all the code below.
Sub Expire_New()
Dim arr() As Variant
Dim msg(1 To 4) As String
Dim x As Long
Dim dDiff As Long
With ActiveSheet
x = .Cells(.Rows.Count, 19).End(xlUp).Row
arr = .Cells(21, 1).Resize(x - 20, 26).Value
End With
For x = LBound(arr, 1) To UBound(arr, 1)
If Len(arr(x, 19)) * Len(arr(x, 1)) * Len(arr(x, 2)) Then
dDiff = DateDiff("d", Date, arr(x, 19))
Select Case dDiff
Case Is <= 0: msg(1) = Expired(msg(1), arr(x, 1), arr(x, 2), arr(x, 19))
Case Is <= 31: msg(2) = Expiring(msg(2), arr(x, 1), arr(x, 2), arr(x, 19), dDiff)
End Select
End If
If Len(arr(x, 19)) = 0 And Len(arr(x, 1)) > 0 And Len(arr(x, 2)) > 0 Then
msg(3) = NoTraining(msg(3), arr(x, 1), arr(x, 2), arr(x, 18))
End If
If Len(arr(x, 19)) > 0 And Len(arr(x, 1)) > 0 And Len(arr(x, 2)) > 0 Then
dDiff = DateDiff("d", Date, arr(x, 19))
Select Case dDiff
Case Is > 31: msg(4) = MsgBox("There are either no expired safeguarding certificates, or no certificate expiring within the next 31 days.", vbCritical, "Warning")
End Select
End If
Next x
For x = LBound(msg) To UBound(msg)
msg(x) = Replace(msg(x), "#NL", vbCrLf)
If Len(msg(x)) < 1024 Then
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
Else
MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
End If
Next x
Erase arr
Erase msg
End Sub
Private Function Expired(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRED Safeguading Certificates#NL#NL"
Expired = msg & "(#var3) #var1 #var2#NL"
Expired = Replace(Expired, "#var1", var1)
Expired = Replace(Expired, "#var2", var2)
Expired = Replace(Expired, "#var3", var3)
End Function
Private Function Expiring(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant, ByRef d As Long) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRING Safeguarding Certificates#NL#NL"
Expiring = msg & "(#var3) #var1 #var2 (#d days remaining)#NL"
Expiring = Replace(Expiring, "#var1", var1)
Expiring = Replace(Expiring, "#var2", var2)
Expiring = Replace(Expiring, "#var3", var3)
Expiring = Replace(Expiring, "#d", d)
End Function
Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR #NL#NL"
NoTraining = msg & " #var1 #var2#NL"
NoTraining = Replace(NoTraining, "#var1", var1)
NoTraining = Replace(NoTraining, "#var2", var2)
NoTraining = Replace(NoTraining, "#var3", var3)
End Function
I think it is the part below causing the problem. I don't think this should be in the main array?
If Len(arr(x, 19)) > 0 And Len(arr(x, 1)) > 0 And Len(arr(x, 2)) > 0 Then
dDiff = DateDiff("d", Date, arr(x, 19))
Select Case dDiff
Case Is > 31: msg(4) = MsgBox("There are either no expired safeguarding certificates, or no certificate expiring within the next 31 days.", vbCritical, "Warning")
End Select
End If
So what I effectively want with "msg(4)" is that I want this to come up only if the criteria for msg(1), msg(2), and msg(3) are not matched. If msg(4) comes up, then the other 3 msg should not. msg1 finds any row/cell where the date listed is older than the current date. msg2 finds the row/cell where the current date is within 31 days of the date listed. msg3 finds the row/cell where there is no date listed, but where there is a name in column 1 or 2. So if the date listed (in cell in column 19) is more than 31 days, and there is a name in 1 and 2, then msg4 should come up and not 1, 2, or 3. 1 and 2 contain names, and 19 contains a date.
Code is on sheet 3 here: https://www.dropbox.com/s/9m1hx2tylv1k470/SCR%20as%20of%2017%2009%2018%20-%20Copy%20-%20Copy.xlsm?dl=0
Public Sub Expire_New(ByRef ws As Worksheet, ByVal Name As String)
Dim msg(1 To 3) As String
Dim x As Long
Dim nDx As Long
Dim dDiff As Long
'Establish the location of the first cell (range) of the Safegaurding Training block
'Find the first instance of Safeguarding Training on the sheet
Dim sgTrainingCol As Range
With ws.Range("A1:AA1000") 'Using something large to provide a range to search
Set sgTrainingCol = .Find("Safeguarding Training", LookIn:=xlValues)
End With
'Establish the location of the first cell (range) of the heading column
'for the table on the sheet. Find the first instance of what is contained
'in mTitleFirstHeadingColumn
Dim HeadingRangeStart As Range
With ws.Range("A1:AA1000") 'Using something large to provide a range to search
Set HeadingRangeStart = .Find(Name, LookIn:=xlValues)
End With
Dim TrainingInfoRange As Range
Dim personFNSR As Range
With ws
'finds the last row of the Heading column that has data, there can NOT be any empty rows
'in the middle of this search. It assumes that the name column date is contigous until
'reaching the end of the data set.
x = .Cells(HeadingRangeStart.Row, HeadingRangeStart.Column).End(xlDown).Row
'Set the TrainingInfoRange to point to the data contained in the 4 columns under Safeguarding Training
Set TrainingInfoRange = .Range(.Cells(sgTrainingCol.Row + 2, sgTrainingCol.Column), .Cells(x, sgTrainingCol.Column + 3))
'Set pseronFNSR to the First Name/Name, Surname range
Set personFNSR = .Range(.Cells(HeadingRangeStart.Row + 1, HeadingRangeStart.Column), .Cells(x, HeadingRangeStart.Column + 1))
End With
'I am a big fan of collections and scripting dictionaries.
'They make code easier to read and to implement.
Dim trainingDate As Scripting.Dictionary
Set trainingDate = CopyRngDimToCollection(personFNSR, TrainingInfoRange)
'This boolean will be used to control continued flow of the
'macro. If NoExpiredTraining gets set to false, then there
'are people who must complete training.
Dim NoExpiredTraining As Boolean: NoExpiredTraining = True
'person training inquiry object - see class definition
Dim personInquiryTraining As clPersonTraining
'this is an index variable used to loop through items
'contained in the Scripting Dictionary object
Dim Key As Variant
For Each Key In trainingDate.Keys
'Assing the next object in the trainingDate Scripting Dictionary
'to the person training inquiry object
Set personInquiryTraining = trainingDate(Key)
'Check to see if there are any training issues
'if so, then set NoExpiredTraining to False
'because there is expired, expiring or missing training
If personInquiryTraining.ExpiringTraining _
Or personInquiryTraining.NoTraining _
Or personInquiryTraining.TrainingExpired Then
NoExpiredTraining = False
End If
Next
If NoExpiredTraining Then
'msg(4) = MsgBox("There are either no ...
'is only used if want to do something based on
'what button the user pressed. Otherwise use
'the Method form of MsgBox
MsgBox "There are either no expired safeguarding certificates, " _
& "or no certificate expiring within the next 31 days.", _
vbCritical, "Warning"
Exit Sub
End If
'If this code executes, then there is expired training.
'Let's collect the status for each individual
For Each Key In trainingDate.Keys
Set personInquiryTraining = trainingDate(Key)
If personInquiryTraining.TrainingExpired _
And personInquiryTraining.trainingDate <> DateSerial(1900, 1, 1) Then 'Training
is expired
msg(1) = Expired(msg(1), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
personInquiryTraining.trainingExpiryDate)
End If
If personInquiryTraining.ExpiringTraining _
And personInquiryTraining.trainingExpiryDate <> DateSerial(1900, 1, 1) Then
'Training is expiring
msg(2) = Expiring(msg(2), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
personInquiryTraining.trainingExpiryDate, _
DateDiff("d", Date, personInquiryTraining.trainingExpiryDate))
End If
If personInquiryTraining.NoTraining Then 'Training is None
msg(3) = NoTraining(msg(3), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
"NONE")
End If
Next
'Because of the Exit Sub statement above, the code bwlow
'will only execute if there are expired, expiring or missing
'training
For x = LBound(msg) To UBound(msg)
msg(x) = Replace(msg(x), "#NL", vbCrLf)
If Len(msg(x)) < 1024 Then
Select Case msg(x)
Case msg(1)
If Len(msg(x)) & vbNullString > 0 Then
'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf
& msg(x), vbExclamation, "Safeguarding Certificate Notification"
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
End If
Case msg(2)
If Len(msg(x)) & vbNullString > 0 Then
'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf
& msg(x), vbExclamation, "Safeguarding Certificate Notification"
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
End If
Case msg(3)
If Len(msg(x)) & vbNullString > 0 Then
'MsgBox "(If this box is blank, there is nothing Expired)" & vbCrLf & vbCrLf
& msg(x), vbExclamation, "Safeguarding Certificate Notification"
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
End If
End Select
Else
MsgBox "String length for notification too long to fit into this MessageBox",
vbExclamation, "Invalid String Length to Display"
End If
Next x
End Sub
'***************************************************************************
'**
'** This fucntion copies all rows of data for the column specified into
'** a scripting dictionary
Private Function CopyRngDimToCollection(ByRef mFNSR As Range, ByRef mTrainInfo) As
Scripting.Dictionary
Dim retVal As New Scripting.Dictionary
'nDx will become a key for each of the scripting dictionary items
Dim nDx As Long: nDx = 1
'person training inquiry object - see class definition
Dim personTraining As clPersonTraining
Dim mRow As Range
For Each mRow In mFNSR.Rows
'instantiate a new person training inquiry object
Set personTraining = New clPersonTraining
With personTraining
.firstName = mRow.Value2(1, 1)
.surName = mRow.Value2(1, 2)
End With
retVal.Add nDx, personTraining
nDx = nDx + 1
Next
nDx = 1
For Each mRow In mTrainInfo.Rows
'Retrieve the person training inquiry object
'from the scripting dictionary (retVal)
Set personTraining = retVal(nDx)
'Add the training data information to
'the person training inquiry object
With personTraining
'Next two equations determine if the excel range has a null value
'if so then the person training inquiry object's date field is set to a
'default value of 1-1-1900 - this could be any valid date
'otherwise the value is set to what is in the excel range from the sheet
.trainingDate = IIf(mRow.Value2(1, 1) = vbNullString, DateSerial(1900, 1, 1),
mRow.Value2(1, 1))
.trainingExpiryDate = IIf(mRow.Value2(1, 2) = vbNullString, DateSerial(1900,
1, 1), mRow.Value2(1, 2))
.trainingLevel = mRow.Value2(1, 3)
.certSeenBy = mRow.Value2(1, 4)
End With
'Update the object stored at the current key location
'given by the value of nDx
Set retVal(nDx) = personTraining
nDx = nDx + 1
Next
'Set the return value for the function
Set CopyRngDimToCollection = retVal
End Function
Private Function Expired(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As
Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRED Safeguading Certificates:#NL#NL"
Expired = msg & "#var1 #var2 (#var3)#NL"
Expired = Replace(Expired, "#var1", var1)
Expired = Replace(Expired, "#var2", var2)
Expired = Replace(Expired, "#var3", var3)
End Function
Private Function Expiring(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As
Variant, ByRef var3 As Variant, ByRef d As Long) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRING Safeguarding Certificates:#NL#NL"
Expiring = msg & "#var1 #var2 (#var3) (#d days remaining)#NL"
Expiring = Replace(Expiring, "#var1", var1)
Expiring = Replace(Expiring, "#var2", var2)
Expiring = Replace(Expiring, "#var3", var3)
Expiring = Replace(Expiring, "#d", d)
End Function
Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As
Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR: #NL#NL"
NoTraining = msg & " #var1 #var2#NL"
NoTraining = Replace(NoTraining, "#var1", var1)
NoTraining = Replace(NoTraining, "#var2", var2)
NoTraining = Replace(NoTraining, "#var3", var3)
End Function
and
Option Explicit
Public firstName As String
Public surName As String
Public trainingDate As Date
Public trainingExpiryDate As Date
Public trainingLevel As String
Public certSeenBy As String
Public dDiff As Long
Public Property Get TrainingExpired() As Boolean
If DateDiff("d", Date, trainingExpiryDate) <= 0 Then
TrainingExpired = True
Else
TrainingExpired = False
End If
End Property
Public Property Get ExpiringTraining() As Boolean
If DateDiff("d", Date, trainingExpiryDate) > 0 Then
dDiff = DateDiff("d", Date, trainingExpiryDate)
Select Case dDiff
Case Is <= 31
ExpiringTraining = True
Case Else
ExpiringTraining = False
End Select
End If
End Property
Public Property Get NoTraining() As Boolean
If trainingDate = DateSerial(1900, 1, 1) Then
NoTraining = True
Else
NoTraining = False
End If
End Property
After looking at your decision statements, the issue was with your logic. In the code below I cleaned up the logic. The inline comments explain what was done. After looking at your workbook in more detail, you're mixing what should be a database application producing reports, with a report you're trying to treat as a database. People do this all the time. Most people write the report in Excel and then try to do analytics or database actions.
You should consider standardizing all of your tables and using Excel Tables which are ListObjects.
I also used the Scripting Dictionary add-in from Microsoft. You must add this to your workbook references. In the VBE click on the Tools menu item and then click on References. (Tools->Reference). Once the dialog box appears scroll down until you find Microsoft Scripting Runtimes. Click on the check box and then click Ok.
You will also need to change the code on the worksheets. You can delete everything there and replace it with
'In this case use of the ActiveSheet
'is ok since the button pressed
'is on the ActiveSheet
Expire_New ActiveSheet, "First Name"
Note, the second parameter of the Expire_New subroutine, must reflect the title you used on each sheet for persons first name in column A.
Option Explicit
'**************************************************************************
'**
'** This sub takes two parameters:
'** ws as Worksheet is the Worksheet object passed from the calling
'** routine
'** mTitleFirstHeadingColumn as string is the title of the first column
'** in the training table on every sheet. THis was added because
'** on one sheet the value is First Name on other sheets it's Name
Public Sub Expire_New(ByRef ws As Worksheet, ByVal mTitleFirstHeadingColumn As String)
Dim msg(1 To 3) As String
Dim x As Long
Dim nDx As Long
Dim dDiff As Long
'Establish the location of the first cell (range) of the Safegaurding Training block
'Find the first instance of Safeguarding Training on the sheet
Dim sgTrainingCol As Range
With ws.Range("A1:AA1000") 'Using something large to provide a range to search
Set sgTrainingCol = .Find("Safeguarding Training", LookIn:=xlValues)
End With
'Establish the location of the first cell (range) of the heading column
'for the table on the sheet. Find the first instance of what is contained
'in mTitleFirstHeadingColumn
Dim HeadingRangeStart As Range
With ws.Range("A1:AA1000") 'Using something large to provide a range to search
Set HeadingRangeStart = .Find(mTitleFirstHeadingColumn, LookIn:=xlValues)
End With
Dim TrainingInfoRange As Range
Dim personFNSR As Range
With ws
'finds the last row of the Heading column that has data, there can NOT be any empty rows
'in the middle of this search. It assumes that the name column date is contigous until
'reaching the end of the data set.
x = .Cells(HeadingRangeStart.Row, HeadingRangeStart.Column).End(xlDown).Row
'Set the TrainingInfoRange to point to the data contained in the 4 columns under Safeguarding Training
Set TrainingInfoRange = .Range(.Cells(sgTrainingCol.Row + 2, sgTrainingCol.Column), .Cells(x, sgTrainingCol.Column + 3))
'Set pseronFNSR to the First Name/Name, Surname range
Set personFNSR = .Range(.Cells(HeadingRangeStart.Row + 1, HeadingRangeStart.Column), .Cells(x, HeadingRangeStart.Column + 1))
End With
'I am a big fan of collections and scripting dictionaries.
'They make code easier to read and to implement.
Dim trainingDate As Scripting.Dictionary
Set trainingDate = CopyRngDimToCollection(personFNSR, TrainingInfoRange)
'This boolean will be used to control continued flow of the
'macro. If NoExpiredTraining gets set to false, then there
'are people who must complete training.
Dim NoExpiredTraining As Boolean: NoExpiredTraining = True
'person training inquiry object - see class definition
Dim personInquiryTraining As clPersonTraining
'this is an index variable used to loop through items
'contained in the Scripting Dictionary object
Dim Key As Variant
For Each Key In trainingDate.Keys
'Assing the next object in the trainingDate Scripting Dictionary
'to the person training inquiry object
Set personInquiryTraining = trainingDate(Key)
'Check to see if there are any training issues
'if so, then set NoExpiredTraining to False
'because there is expired, expiring or missing training
If personInquiryTraining.ExpiringTraining _
Or personInquiryTraining.NoTraining _
Or personInquiryTraining.TrainingExpired Then
NoExpiredTraining = False
End If
Next
If NoExpiredTraining Then
'msg(4) = MsgBox("There are either no ...
'is only used if want to do something based on
'what button the user pressed. Otherwise use
'the Method form of MsgBox
MsgBox "There are either no expired safeguarding certificates, " _
& "or no certificate expiring within the next 31 days.", _
vbCritical, "Warning"
Exit Sub
End If
'If this code executes, then there is expired training.
'Let's collect the status for each individual
For Each Key In trainingDate.Keys
Set personInquiryTraining = trainingDate(Key)
If personInquiryTraining.TrainingExpired _
And personInquiryTraining.trainingDate <> DateSerial(1900, 1, 1) Then 'Training is expired
msg(1) = Expired(msg(1), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
personInquiryTraining.trainingDate)
End If
If personInquiryTraining.ExpiringTraining _
And personInquiryTraining.trainingExpiryDate <> DateSerial(1900, 1, 1) Then 'Training is expiring
msg(2) = Expired(msg(2), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
personInquiryTraining.trainingDate)
End If
If personInquiryTraining.NoTraining Then 'Training is None
msg(3) = Expired(msg(3), _
personInquiryTraining.firstName, _
personInquiryTraining.surName, _
"NONE")
End If
Next
'Because of the Exit Sub statement above, the code bwlow
'will only execute if there are expired, expiring or missing
'training
For x = LBound(msg) To UBound(msg)
msg(x) = Replace(msg(x), "#NL", vbCrLf)
If Len(msg(x)) < 1024 Then
MsgBox msg(x), vbExclamation, "Safeguarding Certificate Notification"
Else
MsgBox "String length for notification too long to fit into this MessageBox", vbExclamation, "Invalid String Length to Display"
End If
Next x
End Sub
'***************************************************************************
'**
'** This fucntion copies all rows of data for the column specified into
'** a scripting dictionary
Private Function CopyRngDimToCollection(ByRef mFNSR As Range, ByRef mTrainInfo) As Scripting.Dictionary
Dim retVal As New Scripting.Dictionary
'nDx will become a key for each of the scripting dictionary items
Dim nDx As Long: nDx = 1
'person training inquiry object - see class definition
Dim personTraining As clPersonTraining
Dim mRow As Range
For Each mRow In mFNSR.Rows
'instantiate a new person training inquiry object
Set personTraining = New clPersonTraining
With personTraining
.firstName = mRow.Value2(1, 1)
.surName = mRow.Value2(1, 2)
End With
retVal.Add nDx, personTraining
nDx = nDx + 1
Next
nDx = 1
For Each mRow In mTrainInfo.Rows
'Retrieve the person training inquiry object
'from the scripting dictionary (retVal)
Set personTraining = retVal(nDx)
'Add the training data information to
'the person training inquiry object
With personTraining
'Next two equations determine if the excel range has a null value
'if so then the person training inquiry object's date field is set to a
'default value of 1-1-1900 - this could be any valid date
'otherwise the value is set to what is in the excel range from the sheet
.trainingDate = IIf(mRow.Value2(1, 1) = vbNullString, DateSerial(1900, 1, 1), mRow.Value2(1, 1))
.trainingExpiryDate = IIf(mRow.Value2(1, 2) = vbNullString, DateSerial(1900, 1, 1), mRow.Value2(1, 2))
.trainingLevel = mRow.Value2(1, 3)
.certSeenBy = mRow.Value2(1, 4)
End With
'Update the object stored at the current key location
'given by the value of nDx
Set retVal(nDx) = personTraining
nDx = nDx + 1
Next
'Set the return value for the function
Set CopyRngDimToCollection = retVal
End Function
Private Function Expired(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRED Safeguading Certificates#NL#NL"
Expired = msg & "(#var3) #var1 #var2#NL"
Expired = Replace(Expired, "#var1", var1)
Expired = Replace(Expired, "#var2", var2)
Expired = Replace(Expired, "#var3", var3)
End Function
Private Function Expiring(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant, ByRef d As Long) As String
If Len(msg) = 0 Then msg = "Persons with EXPIRING Safeguarding Certificates#NL#NL"
Expiring = msg & "(#var3) #var1 #var2 (#d days remaining)#NL"
Expiring = Replace(Expiring, "#var1", var1)
Expiring = Replace(Expiring, "#var2", var2)
Expiring = Replace(Expiring, "#var3", var3)
Expiring = Replace(Expiring, "#d", d)
End Function
Private Function NoTraining(ByRef msg As String, ByRef var1 As Variant, ByRef var2 As Variant, ByRef var3 As Variant) As String
If Len(msg) = 0 Then msg = "SAFEGUARDING TRAINING NOT COMPLETED FOR #NL#NL"
NoTraining = msg & " #var1 #var2#NL"
NoTraining = Replace(NoTraining, "#var1", var1)
NoTraining = Replace(NoTraining, "#var2", var2)
NoTraining = Replace(NoTraining, "#var3", var3)
End Function
You will also need to add a class to your workbook. In the VB Editor window, click on Insert->Class Module. When that has been added, change the name of the class to clPersonTraining. And paste the following code into that class:
Option Explicit
Public firstName As String
Public surName As String
Public trainingDate As Date
Public trainingExpiryDate As Date
Public trainingLevel As String
Public certSeenBy As String
Public Property Get TrainingExpired() As Boolean
If DateDiff("d", Date, trainingExpiryDate) < 1 Then
TrainingExpired = True
Else
TrainingExpired = False
End If
End Property
Public Property Get ExpiringTraining() As Boolean
If DateDiff("d", Date, trainingExpiryDate) < 31 Then
ExpiringTraining = True
Else
ExpiringTraining = False
End If
End Property
Public Property Get NoTraining() As Boolean
If trainingDate = DateSerial(1900, 1, 1) Then
NoTraining = True
Else
NoTraining = False
End If
End Property
It's very simple class that provides the answers. For more information about VBA Classes, I recommend getting a book on the VBA programming language. It will cover the topic in much better detail than possible here
I'm trying to use the FindFirst command in vba for access with an Array, the Array contains usernames, and I want to search through a table and find the ID of each of the usernames in the array. but I keep getting "Compile Error: Type Mismatch" whenever I press the search button on the access form.
Any ideas? - Also does it look like i am passing the array correctly to the next private sub?
The bit that i'm trying to search with the array starts when I create the username() array.
Private Sub createrel_Click()
'declare variables
Dim stDocName As String, db As Database, RS As Recordset, FindPraNumber As String
Dim vary As Variant
Dim Msg As String
Dim Response As Integer
Dim username() As String
Dim varx() As Variant
If IsNull(Me![userlist]) Then
Msg = "Please choose a pra number from the list"
MsgBox Msg, vbCritical, MsgText
DoCmd.GoToControl "userlist"
Exit Sub
End If
If IsNull(Me![folderlist]) Then
Msg = "Please choose a folder from the list"
MsgBox Msg, vbCritical, MsgText
DoCmd.GoToControl "folderlist"
Exit Sub
End If
username() = Split(Me.userlist, ",")
MsgBox Join(username())
Set db = DBEngine(0)(0)
Set RS = db.OpenRecordset("tblPra", DB_OPEN_DYNASET)
RS.FindFirst "[praNo] = """ & username() & """"
varx() = DLookup("praID", "tblPra", "[praNo] = 'username()'")
Set RS = db.OpenRecordset("tblFolder", DB_OPEN_DYNASET)
RS.FindFirst "[folder] = """ & Me.folderlist & """"
vary = DLookup("folderID", "tblFolder", "[folder] = " & "forms!frmrelationship!folderlist")
Response = MsgBox("You are about to create a relationship. Continue?", vbYesNo)
If Response = vbNo Then
Exit Sub
Else
cmdAddRecord varx(), vary
End If
End Sub
Private Sub cmdAddRecord(x(), y)
Dim stDocName As String, db As Database, RS As Recordset, FindPraNumber As String
Dim exists As Boolean
Dim total As Integer
Set db = DBEngine(0)(0)
Set RS = db.OpenRecordset("tblRelationship", DB_OPEN_DYNASET)
exists = False
If Not RS.EOF Then RS.MoveLast
total = RS.RecordCount
'check to see if relationship exists already
RS.FindFirst "[praID] = " & x() & ""
If RS.NoMatch Then
exists = False
Else
If RS("folderID") = y Then
exists = True
Else
For i = 1 To total Or exists = True
RS.FindNext "[praID] = " & x & ""
If RS.NoMatch Then
Else
If RS("folderID") = y Then exists = True
End If
Next i
End If
End If
If exists = False Then
RS.addNew
RS("praID").Value = x
RS("folderID").Value = y
RS.Update
Msg = "Relationship has now been created"
MsgBox Msg, vbInformation, MsgText
Else
Msg = "Relationship already exists"
MsgBox Msg, vbCritical, MsgText
End If
End Sub
You can't do this:
varx() = DLookup("praID", "tblPra", "[praNo] = 'username()'")
You can't assign an array using DLookup, DLookup can't pull an array of IDs, username() should be username(n), and the concatenating of username is wrong. In fact, the only valid parts in this sentence are "tblPra" and "[praNo] = ".
So rethink your concept. There is no reason to complicate matters when straight recordsets or a query can do the job.