how to calculate median in Access query using function in VBa - vba

I'm using ms Access query to calculate the MEdian AGe of Patients grouped by Clinic name using Query.
since Access doesn't have build-in Median function. I have to create it using VBA,
I tried many ready functions from web.. but none worked properly. any suggestions for working codes around? could u plz help me to get the median! thank u in advance.

This is a good function also pretty well commented:
Public Function acbDMedian( _
ByVal strField As String, ByVal strDomain As String, _
Optional ByVal strCriteria As String) As Variant
' Purpose:
' To calculate the median value
' for a field in a table or query.
' In:
' strField: The field
' strDomain: The table or query
' strCriteria: An optional WHERE clause to
' apply to the table or query
' Out:
' Return value: The median, if successful;
' otherwise, an error value
Dim db As DAO.Database
Dim rstDomain As DAO.Recordset
Dim strSQL As String
Dim varMedian As Variant
Dim intFieldType As Integer
Dim intRecords As Integer
Const acbcErrAppTypeError = 3169
On Error GoTo HandleErr
Set db = CurrentDb( )
' Initialize the return value.
varMedian = Null
' Build a SQL string for the recordset.
strSQL = "SELECT " & strField
strSQL = strSQL & " FROM " & strDomain
' Use a WHERE clause only if one is passed in.
If Len(strCriteria) > 0 Then
strSQL = strSQL & " WHERE " & strCriteria
End If
strSQL = strSQL & " ORDER BY " & strField
Set rstDomain = db.OpenRecordset(strSQL, dbOpenSnapshot)
' Check the data type of the median field.
intFieldType = rstDomain.Fields(strField).Type
Select Case intFieldType
Case dbByte, dbInteger, dbLong, dbCurrency, dbSingle, dbDouble, dbDate
' Numeric field.
If Not rstDomain.EOF Then
rstDomain.MoveLast
intRecords = rstDomain.RecordCount
' Start from the first record.
rstDomain.MoveFirst
If (intRecords Mod 2) = 0 Then
' Even number of records. No middle record, so move
' to the record right before the middle.
rstDomain.Move ((intRecords \ 2) - 1)
varMedian = rstDomain.Fields(strField)
' Now move to the next record, the one right after
' the middle.
rstDomain.MoveNext
' Average the two values.
varMedian = (varMedian + rstDomain.Fields(strField)) / 2
' Make sure you return a date, even when averaging
' two dates.
If intFieldType = dbDate And Not IsNull(varMedian) Then
varMedian = CDate(varMedian)
End If
Else
' Odd number of records. Move to the middle record
' and return its value.
rstDomain.Move ((intRecords \ 2))
varMedian = rstDomain.Fields(strField)
End If
Else
' No records; return Null.
varMedian = Null
End If
Case Else
' Nonnumeric field; raise an app error.
Err.Raise acbcErrAppTypeError
End Select
acbDMedian = varMedian
ExitHere:
On Error Resume Next
rstDomain.Close
Set rstDomain = Nothing
Exit Function
HandleErr:
' Return an error value.
acbDMedian = CVErr(Err)
Resume ExitHere
End Function
Source: http://etutorials.org/Microsoft+Products/access/Chapter+6.+Data/Recipe+6.4+Find+the+Median+Value+for+a+Field/

Related

Modify multiple records via listbox based on combobox in Access

I have a form that has a multi-selection listbox based on a query of items that have a specific field that is blank (UserID). I would like to select a UserID from a combobox and click a button to have all selected records modified to have that UserID. (I would also like to update the DateAssigned field to whatever the current date is). CaseID is the unique value in the table.
Sample Form Picture
(The combo box has two columns, the second one has the actual ID I'd want to use).
I've looked at multiple different posts on here like this but I haven't been able to get it to work. Here's an example of a code I found and tried, altered slightly.
Private Sub AssignButton_Click()
Dim lCnt As Long
Dim lID As Long
Dim sSQL_Update As String
Dim sText_1 As String
Dim bSuccess As Boolean
sText_1 = Me.ComboBox
With Me.ToAssignList
For lCnt = 1 To .ListCount
If .Selected(lCnt) Then
lID = .Column(4, lCnt - 1)
'Example update for 1 column
sSQL_Update = "UPDATE MainData SET UserID = '" & sText_1 & "' WHERE CaseID = " & lID & ";"
bSuccess = Update_Statement(sSQL_Update)
End If
Next
End With
End Sub
Public Function Update_Statement(sUpdate_Stmt) As Boolean
Dim db As Database
Set db = CurrentDb
db.Execute (sUpdate_Stmt)
End Function
When I try running this nothing happens.
Thank you!
Hy, Some remarks and tweaks to your code :
First enable the direct screen :
This will enable you to use the debug function.
Is the field : UserID in the table Maindata a text field ? If so, fine, otherwise you should alter the string to :
"UPDATE MainData SET UserID = " & Cint(sText_1) & " WHERE CaseID = " & lID & ";"
Then it would be better to create different functions in a module for what you are trying to do. But ok, this is not your main concern right now...
Try :
Private Sub AssignButton_Click()
Dim lCnt As Long
Dim lID As Long
Dim sSQL_Update As String
Dim sText_1 As String
Dim bSuccess As Boolean
sText_1 = Me.ComboBox
With Me.ToAssignList
For lCnt = 1 To .ListCount
If .Selected(lCnt) Then
lID = .Column(4, lCnt - 1)
'Example update for 1 column
sSQL_Update = "UPDATE MainData SET UserID = '" & sText_1 & "' WHERE CaseID = " & lID & ";"
'
'Check your sql statement and add it to stack overflow if it still is not working
'
Debug.Print sSQL_Update
'
bSuccess = Update_Statement(sSQL_Update)
End If
Next
End With
End Sub
There is should be a logical check in the code bellow to see if the update is done, anyhow, the code bellow should work and return a true value if the update doesn't produce an error... it's not good code but ok.
Public Function Update_Statement(sUpdate_Stmt) As Boolean
Dim Currentdb As Database
Set Currentdb = Currentdb
Call Currentdb.Execute(sUpdate_Stmt, dbSeeChanges)
Update_Statement = True
End Function
Good luck !

How to check efficient whether a recordset value isnull before passing it to a called function?

I have a sub which creates a recordset. A function is called with values from the recordset. The goal is to use multiple values from the recordset, however, there is a possibility that a recordset value is null, then the function call will result in an error: "Invalid use of Null". To handle this error, each time the recordset value is checked for null values, if it is null, it will be replaced with an empty string. However, the way I have programmed this feels very inefficient, even more when later on more than ten parameters should be checked. Is there a way to do this more efficiently?
I have skipped the last part off the code as this is not necessary to understand my question. I've replaced it with ......... If needed, I will edit and provide full code.
Sub CallFunctionWithArray()
Dim conn As ADODB.Connection
Dim rst As ADODB.Recordset
Dim arrValues(1 To 3) As Variant
Set conn = New ADODB.Connection
conn.Open "provider=Microsoft.JET.OLEDB.4.0;Data Source=" & CurrentProject.Path & "\Northwind.mdb"
Set rst = New ADODB.Recordset
rst.Open "SELECT * FROM CustomersCopy", conn, adOpenForwardOnly, adLockReadOnly, adCmdText
If Not (rst.EOF And rst.BOF) Then
rst.MoveFirst
Do Until rst.EOF = True
If IsNull(rst![CompanyName]) Then
arrValues(1) = ""
Else
arrValues(1) = rst![CompanyName]
End If
If IsNull(rst![DateTest]) Then
arrValues(2) = ""
Else
arrValues(2) = rst![DateTest]
End If
If IsNull(rst![INTTest]) Then
arrValues(3) = ""
Else
arrValues(3) = rst![INTTest]
End If
Call ReturnValuesOfArray(arrValues(1), arrValues(2), arrValues(3))
.........
End Sub
Function ReturnValuesOfArray(ByVal ValueOne As String, ByVal ValueTwo As String, ByVal ValueThree As String)
Debug.Print "Waarde variabele 1: " & ValueOne
Debug.Print "Waarde variabele 2: " & ValueTwo
Debug.Print "Waarde variabele 3: " & ValueThree
End Function
There is no problem with the code, it does what it's supposed to do. However, I will be passing many more parameters to the function when this is going to be really used.
You could loop through the fields of your Recordset instead of hard coding for every field. Using your code as a starting point, it could look something like this:
Private Sub Test()
Dim rst As ADODB.Recordset
Dim i As Integer
If Not (rst.EOF And rst.BOF) Then
rst.MoveFirst
Do Until rst.EOF = True
For i = 0 To rst.Fields.Count - 1
If IsNull(rst.Fields(i).Value) Then
arrValues(i) = ""
Else
arrValues(i) = rst.Fields(i).Value
End If
Next
Loop
End If
End Sub
Incorporating the ideas presented by #HansUp and #Mathieu Guindon, the code is even shorter:
Private Sub Test()
Dim rst As ADODB.Recordset
Dim i As Integer
Do Until rst.EOF
For i = 0 To rst.Fields.Count - 1
arrValues(i + 1) = Nz(rst.Fields(i).Value, "")
Next
Loop
End Sub
The rest of your code can be simplified, too, while allowing for any number of parameters:
Function ReturnValuesOfArray(ByVal Values As Variant)
Dim i As Integer
For i = LBound(Values) To UBound(Values)
Debug.Print "Waarde variabele " & i & ": " & Values(i)
Next
End Function
The Nz Function does what I think you want.
arrValues(1) = Nz(rst![CompanyName], "")
arrValues(2) = Nz(rst![DateTest], "")
arrValues(3) = Nz(rst![INTTest], "")

MS Access capture certain group of text, append, and loop onto next section in a long text field

I have a long text field (called "reporttext") that someone is importing a bunch of text that needs to be separated and appended into another table. For each case, there's a "[]" character that is supposed to separate each case. I want my code to look for the first [] and second [], append the text to another table and then loop. So the next case would be the text between the second [] and third [].
Here's my string
Reporttext: [] ksfjjls [] 42244 [] ####
I would want this to append to a new table called "notes" where it would be like this:
Reporttext
ksfjjls
42244
####
I used a macro to count the number of [] in the text file to know how many times to run the loop, but this, along with the rest of my code just isn't happening. I know my code is wrong, but I know with a few tweaks it'll get there. Any help is appreciated.
lengthofnote = Len([reporttext])
start = InStr([reporttext], "[]")
startplus3 = [start] + 3
'find number of cases
firstcase = 1
numcases = StringCountOccurrences([reporttext], "[]")
Dim LCounter As Integer
For LCounter = [firstcase] To [numcases]
revisedreporttext = Mid([reporttext], [startplus3], [lengthofnote])
secondposition = InStr([revisedreporttext], "[]")
nextreporttext = Mid([reporttext], [startplus3], [secondposition])
Add_reporttext = "INSERT INTO notes(reporttext) values ('" & nextreporttext & "');"
DoCmd.RunSQL Add_reporttext
firstcase = firstcase + 1
startplus3 = secondposition
secondposition = secondposition + 4
Next LCounter
#Zev Spitz is correct in that you could use Split() to accomplish this. You could use something like this
Option Compare Database
Option Explicit
Sub SplitLongTextField()
Dim rs As Recordset
Dim reportTextArr
Dim qString As String
Dim i As Long
qString = "SELECT [reporttext] FROM [Table1]" '<- replace [Table1] with the name of your table with the Long Text field
Set rs = CurrentDb.OpenRecordset(qString)
If Not rs.EOF Then
reportTextArr = Split(rs.Fields("reporttext"), "[]")
End If
For i = LBound(reportTextArr) To UBound(reportTextArr)
If Not reportTextArr(i) = "" Then
DoCmd.RunSQL "INSERT INTO notes(reporttext) VALUES('" & reportTextArr(i) & "');"
End If
Next i
rs.Close
End Sub
If you needed to do this for multiple records from your initial table then you could loop through the entire table and loop the operation like
Option Compare Database
Option Explicit
Sub SplitLongTextField()
Dim rs As Recordset
Dim reportTextArr
Dim qString As String
Dim i As Long
qString = "SELECT [reporttext] FROM [Table1]" '<- replace [Table1] with the name of your table with the Long Text field
Set rs = CurrentDb.OpenRecordset(qString)
Do Until rs.EOF
reportTextArr = Split(rs.Fields("reporttext"), "[]")
For i = LBound(reportTextArr) To UBound(reportTextArr)
If Not reportTextArr(i) = "" Then
DoCmd.RunSQL "INSERT INTO notes(reporttext) VALUES('" & reportTextArr(i) & "');"
End If
Next i
rs.MoveNext
Loop
rs.Close
End Sub
Assuming the string always starts with [] and preference is to return a single string, consider:
Replace(Mid(reporttext, 4), "[] ", vbCrLf)

GoToRecord works fine, but simpliest way to return value for that record

Part of the issue is opening the table for the record set and having to set focus to the subform. I have been unsuccessful in sorting the table through vba. The goal is to find the MEDIAN value of a table, hence the sorting.
Private Sub cboUser_AfterUpdate()
Dim sourceReset As String
Dim dbMedian As DAO.Database
Dim rsMedian As DAO.Recordset
sourceReset = sbf.SourceObject '<--Is Table.TEMPtable btw.
Me.sbf.SourceObject = ""
Forms!frm.Requery
Forms!frm.Refresh
'Create new TEMPtable
DoCmd.SetWarnings False
DoCmd.OpenQuery "qryTEMPtable" '<--Is sorted here as desired
DoCmd.SetWarnings True
Set dbMedian = CurrentDb()
Set rsMedian = dbMedian.OpenRecordset("TEMPtable") '<--Gets unsorted here
sbf.SourceObject = sourceReset
Me.OrderBy = "NetWrkDays ASC" '<--Re-sorting, but on subform, which.. is
fine if I can return the column value later.
Forms!frm.Refresh
Me.[sbf].SetFocus
Records= DCount("[ColA]", "TEMPtable")
'Even number of records
If Records - 2 * Int(Records / 2) = 0 Then
MEDrcd = Records / 2
DoCmd.GoToRecord , , acGoTo, MEDrcd '<-Can see value in debug, how to
assign it to a useful variable???
''''Me.CurrentRecord ("NetWrkDays")
''''Me.RecordSource ("NetWrkDays")
Me.txtMED = rsMedian("NetWrkDays")
End If
'Odd number of records
If Records - 2 * Int(Records / 2) = 1 Then
MEDrcd1 = (Records - 1) / 2
MEDrcd2 = (Records + 1) / 2
DoCmd.GoToRecord acDataForm, "TempTable", acGoTo, MEDrcd1
MED1 = rsMedian("NetWrkDays")
DoCmd.GoToRecord acDataForm, "TempTable", acGoTo, MEDrcd2
MED2 = rsMedian("NetWrkDays")
Me.txtMED = (MED1 + MED2) / 2
End If
I guess I see no point in trying to use DoCmd.GoToRecord if you cant return the value at that point.
What is the best/correct method for returning a value after moving to a record.
As the subform and table are the same, I just ran with setting focus to the subform as I said was having issue sorting the table in vba. Though then me using rsMedian makes no sense, as the table rs never moves...but I cant retrieve a value for moving though the subform using GoToRecord.
I am going in circles here and i hope is not to garbled to understand. Thank you.
Found this method online. Is working great in case anyone else finds themselves in a similar situation.
Private Sub cboUser_AfterUpdate()
Dim sourceReset As String, sqlMED As String, sTable As String, sField As String
Dim j As Integer, varVal As Single
Dim rs As DAO.Recordset
sourceReset = sbf.SourceObject
Me.sbf.SourceObject = ""
Forms!frmSTATS.Requery
Forms!frmSTATS.Refresh
'Create new TEMPtable table
DoCmd.SetWarnings False
DoCmd.OpenQuery "qryTEMPtable"
DoCmd.SetWarnings True
'Get MEDIAN Data/Value
sTable = "TEMPtable"
sField = "NetWrkDays"
sqlMED = "SELECT " & sField & " from " & sTable & " WHERE " & sField & ">0 Order by " & sField & ";"
Set rs = CurrentDb.OpenRecordset(sqlMED)
rs.MoveLast
j = rs.RecordCount
rs.Move -Int(j / 2)
If j Mod 2 = 1 Then 'odd number of elements
getMedian = rs(sField)
Else 'even number of elements
varVal = rs(sField)
rs.MoveNext
varVal = varVal + rs(sField)
getMedian = varVal / 2
End If
Me.txtAnswer = getMedian
rs.Close
sbf.SourceObject = sourceReset
Me.OrderBy = "NetWrkDays ASC"
Forms!frmSTATS.Refresh
End Sub

VBA function to a field name

I have the same copy of a function in many forms that does exactly the same job. What changes in them is a field name. So the reason I keep it local is I don't know how I would refer to a particular field by name in a referenced form. This is the function:
Private Function getNewXNo(ByRef theForm As Form, ByVal strCode As String) As String
Dim rs As DAO.Recordset
Dim maxNo As Long
Dim aNo As Long
Set rs = theForm.RecordsetClone
maxNo = 0
If rs.RecordCount <> 0 Then
rs.MoveFirst
Do While Not rs.EOF
aNo = CLng(Right(Nz(rs!applicationNo, strCode & "0000"), 4))
If aNo > maxNo Then
maxNo = aNo
End If
rs.MoveNext
Loop
End If
getNewXNo = strCode & Format(maxNo + 1, "
Set rs = Nothing
End Function
There are a lot of instances when I have to generate new codes, such as BB001, BB002, APP001, APP002, etc. The function reads all existing codes from a field in the referenced form and based on that creates a new one. Is there any way I can send a field name to a global function, such as
aNo = CLng(Right(Nz(rs!varThatContainsAFieldName, strCode & "0000"), 4))
Thanks
You can access a field in a recordset like this:
rs("fieldname")
So you could make the field name a parameter for your function.