Catching DSum Null Results in Incorrect Calculation - ms-access

j and d both evalulate the same function however when I use If IsNull to catch any Null Values the value of d is not correctly evaluated. What is causing this?
Dim d As Integer
Dim j As Integer
j = DSum("Count", "qry_nihr_unique")
If IsNull(d = DSum("Count", "qry_nihr_unique")) Then
MsgBox "No records were found for the data criteria you entered"
GoTo ESub
Else
Me.un_p.Value = d
End If
Debug.Print "j = " & j
Debug.Print "d = " & d
j = 58
d = 0
Updated Code After Answer
Dim d
d = DSum("Count", "qry_nihr_unique")
If IsNull(d) Then
MsgBox "No records were found for the data criteria you entered"
GoTo ESub
Else
Me.un_p.Value = d
End If
After HansUp's answer below I believe this is the most efficient way to write this.

Within IsNull(), the code checks whether d is equal to the DSum() expression. It's an equality test, and nothing is assigned to d. So the value of d remains unchanged --- it was initialized as zero and remains zero.
The situation is much like this Immediate window session:
? DSum("id", "tblFoo")
134
d = 0
? (d = DSum("id", "tblFoo"))
False
? d
0
? IsNull(d = DSum("id", "tblFoo"))
False
? d
0
The following statement will assign the DSum() result to d; not test whether the two are equal:
d = DSum("id", "tblFoo")
? d
134

Related

Using IF to look at multiple fields to return a set value

I have an access database that tracks our internal quality assurance checks for staff. They have 8 items we track them on and each of those items has 4 possible values:
Pass (1)
Non-Pass (2)
FYI (3)
NP&FYI (4).
You can see these 4 set on the left in the column topped by "CPT". [1]. What I would like to have happen is if ANY of those 8 fields are FYI (ID 3) it sets the FYI checkbox to true. If ANY are Non-Pass (ID 2) It sets the passing checkboxes to false. If ANY of the fields are NP&FYI (ID 4) I want the two checkboxes to be set to non-pass and FYI. The code I am trying is:
Private Sub Form_AfterUpdate()
If (Me.Diagnosis And Me.CPT And Me.DOS And Me.UpDownCode And
Me.ChargeCorrections And Me.ChurnEsc And Me.Protocol And Me.BillServProv)
= 1 Then
Me.FYI = 0
Me.QAFail = -1
End If
If (Me.Diagnosis Or Me.CPT Or Me.DOS Or Me.UpDownCode Or
Me.ChargeCorrections Or Me.ChurnEsc Or Me.Protocol Or Me.BillServProv) = 2
Then
Me.QAFail = 0
End If
If (Me.Diagnosis Or Me.CPT Or Me.DOS Or Me.UpDownCode Or
Me.ChargeCorrections Or Me.ChurnEsc Or Me.Protocol Or Me.BillServProv) = 3
Then
Me.FYI = -1
End If
If (Me.Diagnosis Or Me.CPT Or Me.DOS Or Me.UpDownCode Or
Me.ChargeCorrections Or Me.ChurnEsc Or Me.Protocol Or Me.BillServProv) = 4
Then
Me.FYI = -1
Me.QAFail = 0
End If
End Sub
Unfortunately I'm getting inconsistent results. If I set field like CPT to non-pass then the FYI checkbox tics and the QAFail box does not clear. If I set other fields to other values I get all kinds of different results. I'm assuming its because there is confusion between the possible values of the fields being interpreted by the successive If statements. I thought about using CASE, but I couldn't figure out how to set the case value based on 8 different fields. I'm a novice at VBA, can anyone point me in the right direction?
June7's alt code (works)
Dim strCodes As String
With Me
strCodes = Nz(.Diagnosis & .CPT & .DOS & .UpDownCode & _
.ChargeCorrections & .ChurnEsc & .Protocol & .BillServProv, "")
End With
If InStr(strCodes, 2) > 0 Then
Me.QAFail = 0
ElseIf InStr(strCodes, 3) > 0 Then
Me.txtFYI = -1
ElseIf InStr(strCodes, 4) > 0 Then
Me.txtFYI = -1
Me.QAFail = 0
ElseIf strCodes <> "" Then
Me.txtFYI = 0
Me.QAFail = -1
End If
End Sub
The If condition returns either a True or False (-1 or 0) not the 1, 2, 3, 4 you are testing for. Every If block executes so the last one that meets criteria returns result. Use ElseIf then only one End If. Use a With Me block and don't have to repeat the Me qualifier. Why does posted code not show line continuation character? Code assumes every field has a value. If any field is Null or empty string, the test for ID 1 will always return False even if other 7 fields have a 1.
With Me
If (.Diagnosis = 1 And .CPT = 1 And .DOS = 1 And .UpDownCode = 1 And _
.ChargeCorrections = 1 And .ChurnEsc = 1 And .Protocol = 1 And .BillServProv = 1) Then
...
ElseIf (...) Then
...
ElseIf (...) Then
...
ElseIf (...) Then
...
End If
End With
Use that same syntax for the ElseIf expressions using the Or operator.
Make sure testing values in order of priority. If ID 3 should have precedence over 2 and 4 then test for 3 first. I would think 2 has precedence but your narrative contradicts.
Consider alternative code:
Dim strCodes As String
With Me
strCodes = Nz(.Diagnosis & .CPT & .DOS & .UpDownCode & _
.ChargeCorrections & .ChurnEsc & .Protocol & .BillServProv, "")
End With
If InStr(strCodes, 2) > 0 Then
...
ElseIf InStr(strCodes, 3) > 0 Then
...
ElseIf InStr(strCodes, 4) > 0 Then
...
ElseIf strCodes <> "" Then
...
End If

Format elapsed time value string

I receive a CSV file daily in which I filter to look for certain data. This file requires a lot of manual effort within Excel to filter and format the data. I am devising a VBScript to look at each line to return only the data needed to reduce the manual effort.
Within the CSV file is a "time seen" string which is formatted strangely. The "time seen" data differs from line to line. An example of this data is the following:
3hrs27min 35sec
35min 20sec
8min 38sec
1days1hrs25min 30sec
5days12hrs9min 48sec
I am using this code snippet to remove the "days", "hrs", "min ", and "sec" from the data and replace them with a ":".
strLastField0 = arrFields(9)
strLastField1 = Replace(strLastField0,"min ",":")
strLastField2 = Replace(strLastField1,"hrs",":")
strLastField3 = Replace(strLastField2,"days",":")
strLastField4 = Replace(strLastField3,"sec","")
The result is the following:
d:h:m:s
3:27:35
35:20
8:38
1:1:25:30
5:12:9:48
I am looking to have the data come out formatted in the following manner instead of what it currently is.
hh:mm:ss
03:27:35
00:35:20
00:08:38
25:01:25
132:09:48
Here is a function in which I have been working with to accomplish this, but my attempts have failed to get the formatting like I want.
Function funcFormatTime(TimeString)
Dim TimeArray
Dim h, m, s, hh, mm, ss
TimeArray = Split(TimeString, ":", -1, 1)
d = TimeArray(0)
h = TimeArray(1)
m = TimeArray(2)
s = TimeArray(3)
Do Until s < 60
s = s - 60
m = m + 1
Loop
Do Until m < 60
m = m - 60
h = h + 1
Loop
Do Until h < 24
h = h - 24
Loop
If Len(Trim(h)) = 1 Then hh = "0" & h Else hh = h
If Len(Trim(m)) = 1 Then mm = "0" & m Else mm = m
If Len(Trim(s)) = 1 Then ss = "0" & s Else ss = s
funcFormatTime = hh & ":" & mm & ":" & ss
End Function
This uses a regular expression to split the input strings using the .Replace method with a function pointer that will receive as arguments each of the elements in the string if present or an Empty value if not present.
Option Explicit
Dim aStrings
aStrings = Array( _
"3hrs27min 35sec", _
"35min 20sec", _
"8min 38sec", _
"1days1hrs25min 30sec", _
"5days12hrs9min 48sec" _
)
Dim sTime
For Each sTime in aStrings
WScript.Echo funcFormatTime( sTime )
Next
Function funcFormatTime( inputString )
With New RegExp
.Pattern = "^(?:([0-9]+)days)?(?:([0-9]+)hrs)?(?:([0-9]+)min)?(?:\s*([0-9]+)sec)"
funcFormatTime = .Replace( inputString, GetRef("funcCalcTime") )
End With
End Function
Function funcCalcTime( matchedString, d, h, m, s, offset, originalString )
funcCalcTime = LeftZeroPad( CLng("0" & d) * 24 + Clng("0" & h), 2) & ":" & _
LeftZeroPad( CLng("0" & m), 2) & ":" & _
LeftZeroPad( CLng("0" & s), 2)
End Function
Function LeftZeroPad( value, length )
LeftZeroPad = CStr(value)
If Len(LeftZeroPad) < length Then
LeftZeroPad = Right(String(length, "0") & CStr(LeftZeroPad), length)
End If
End Function
Each of the elements in the regular expression have the form
(?:([0-9]+)days)?
Where (?: )? means that the parenthesis do not define a capture group (?:) and that this group could or couldn't be present (the closing ?). Inside this expression there is a ([0-9]+) that define a capture group that match a sequence of numeric digits. Capture groups are passed as arguments to the replace function, where the only work to do is properly format the values.

Comparison Logic

I have an If statement which I was assuming was comparing each value to each other. However it seems no matter what the values are (e.g. all values contain a count of 4) it goes to the else. Am I missing something in the If statement?
If rst![CountOfProvider] = rst![CountOfDelivery Type] = rst![CountOfBU Creator] = rst![CountOfOrigin] = rst![CountOfSub-Destination] = rst![CountOfDestination Zipcode] = rst![CountOfCity] = rst![CountOfState] = rst![CountOfCost Zone] = rst![CountOfRate] = rst![CountOfMarket Name] Then
chk = False
Else
chk = True
End If
VBA doesn't perform that sequence of comparisons as you seem to expect.
Consider this simpler example from the Immediate window ...
Debug.Print 2 = 2
True
Debug.Print 2 = 2 = 2
False
I'm uncertain how VBA handles those multiple equality comparisons, but suspect it may be testing the first and then comparing the result from that with the next ... sort of like this ...
Debug.Print (2 = 2) = 2
False
The first comparison returns True, which is the integer -1 ...
Debug.Print CInt(2 = 2)
-1
So that means the final comparison would be equivalent to this ...
Debug.Print -1 = 2
And naturally that returns False.
The computationally quickest way is to hard code the comparisons. The more
extensible way is to test via a loop.
HansUp makes a good comment - you should be wary of potential null values and add in a handler to deal with them as desired (e.g. using Nz() in Access or IsNull() in any host environment)
'Method 1
If rst![CountOfProvider] = rst![CountOfDelivery Type] And _
rst![CountOfProvider] = rst![CountOfBU Creator] And _
...etc...Then
chk = False
Else
chk = True
End If
'Method 2
chk = True
For Each ele In Array(rst![CountOfDelivery Type], rst![CountOfBU Creator],...your others...)
If ele <> rst![CountOfProvider] Then
chk = False
Exit For
End If
Next ele

Validating that a comma-separated list of values follows a specific sequence

I have a loop I created to check if the values entered match an ordering, depending on the augment passed. So for example the ordering constraint must be
"SU", "M", "TU", "W", "TH", "F", "SA"
therefore if the user enters the following inputs
"SU,M,TU,SA" this is correct
however if the user enters
"SU,TH,M" this is incorrect since M should come before TH
The coding has been implemented and works fine however i don't find this was the best way of coding it, can anyone help me code it more efficiently?
Function validExDays(exDays As String)
Dim found As Boolean
found = False
If Len(exDays) >= 1 And Not IsNull(exDays) Then
Dim NumOfCommas As Integer
NumOfCommas = InstrCount(exDays, ",")
Dim days(0 To 7) As String
days(0) = ","
days(1) = "SU"
days(2) = "M"
days(3) = "TU"
days(4) = "W"
days(5) = "TH"
days(6) = "F"
days(7) = "SA"
Dim i, j, k, l, m, o, p, q As Integer
i = 1
j = 1
k = 1
l = 1
m = 1
o = 1
p = 1
q = 1
Do While i <= 7
If NumOfCommas = 0 Then
'One day input check
If i = 1 Then
Do While j <= 7
If UCase(exDays) = days(j) Then
found = True
Exit Do
End If
j = j + 1
Loop
End If
End If
'Two day input check
j = 1
If NumOfCommas = 1 Then
If found = False And i = 2 Then
Do While j <= 7
Do While k <= 7
If UCase(exDays) = days(j) + days(0) + days(k) Then
found = True
Exit Do
End If
k = k + 1
Loop
If found = False Then
j = j + 1
k = j
Else
Exit Do
End If
Loop
End If
End If
'Three day input check
So the string value entered can be "SU,M,F" or "SU,F" or any other combination but whatever items are included must be in the correct order.
The following code is a bit more compact. It uses the Split() function to break out the components, and uses a Dictionary object to hold the index values of each valid component
Option Compare Database
Option Explicit
Public Function IsValidExDays(exDays As String) As Boolean
Dim rtn As Boolean
Dim valueArray() As String, valueItem As Variant
Dim maxValue As Integer
Dim dict As Object ' Scripting.Dictionary
rtn = True
Set dict = CreateObject("Scripting.Dictionary")
dict.Add "SU", 1
dict.Add "M", 2
dict.Add "TU", 3
dict.Add "W", 4
dict.Add "TH", 5
dict.Add "F", 6
dict.Add "SA", 7
maxValue = 0
valueArray = Split(exDays, ",")
For Each valueItem In valueArray
If dict.Exists(valueItem) Then
If dict(valueItem) > maxValue Then
maxValue = dict(valueItem)
Else
rtn = False
Exit For
End If
Else
rtn = False
Exit For
End If
Next
Set dict = Nothing
IsValidExDays = rtn
End Function
Since you are in Access, why don't you make an entry form with checkboxes that will always return the parameters in the order you expect?

Editing data grabbed from a recordset

Is it possible to edit data that is grabbed from a recordset? In my case, I am trying to add quantities together so that I can get a total. So an example of what I am trying to do would be:
<%
set rs = server.CreateObject("ADODB.recordset")
totalqty = 0
do NOT while rs.EOF
totalqty = totalqty + rs("QTY")
loop
>%
Whenever I tried to do something like this, I would always get an 'Type MisMatch' Error and I'm not sure how to resolve this problem.
As always, any and all help would be appreciated.
Try to "cast" the value in the recordset like so:
CDbl( rs.fields("QTY").value )
This will cast the value to a double. If the value is null you will get en error so you have to check that first...
Or you can write a function to always get the correct type:
public function parse(value, alternative)
dim val
val = trim(value & "")
parse = alternative
if val = "" then exit function
on error resume next
select case varType(parse)
case 2, 3 'integer, long
parse = cLng(val)
case 4, 5 'single, double
parse = cdbl(val)
case 6 'currency
parse = ccur(val)
case 7 'date
parse = cDate(val)
case 11 'bool
parse = cBool(val)
case 8 'string
parse = value & ""
case else
on error goto 0
lib.throwError("type not supported. val:" & value & " alt:" & alternative)
end select
on error goto 0
end function
dim val : val = rs("QTY")
val = parse(val, 0)
' now val is always an integer (either the value from db or 0)
ulluoink's solution will work, but this is simpler...
function ToDbl(vIn, nDefault)
'Convert a variant to an integer using default where necessary
if isnull(vIn) then
ToDbl = nDefault
else
if IsNumeric(CStr(vIn)) Then
ToDbl = CDbl(vIn)
else
ToDbl = nDefault
end if
end if
end function
Then just call:
totalqty = totalqty + ToDbl(rs("QTY"), 0)