I know you said SQL, but I knew I had seen a vba function. I found this in one of the demo databases I have, a routine called DMedian.
The code is a s follows:
Code:
'---------------------------------------------------------------------------------------
' Procedure : DMedian
' Author : mellon (found on internet)
' Date : 17/08/2015
' Purpose : from https://msdn.microsoft.com/en-us/library/dd789431%28v=office.12%29.aspx?f=255&MSPPError=-2147217396
'---------------------------------------------------------------------------------------
'
Public Function DMedian( _
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 errAppTypeError = 3169
On Error GoTo HandleErr
Set db = CurrentDb()
' Initialize return value.
varMedian = Null
' Build SQL string for recordset.
strSQL = "SELECT " & strField & " FROM " & strDomain
' Only use a WHERE clause 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
' And 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 or 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
' Non-numeric field; so raise an app error.
Err.Raise errAppTypeError
End Select
DMedian = varMedian
ExitHere:
On Error Resume Next
rstDomain.Close
Set rstDomain = Nothing
Exit Function
HandleErr:
' Return an error value.
DMedian = CVErr(Err.Number)
Resume ExitHere
End Function
I created a table based on your data. I called it Dogs.
Code:
dogtype |
Wght |
doc |
30 |
doc |
14 |
doc |
11 |
doc |
10 |
lab |
70 |
lab |
65 |
lab |
60 |
lab |
53 |
lab |
50 |
pug |
24 |
pug |
20 |
pug |
19 |
pug |
15 |
Then modified a test routine for the DMedian as below -modified for your Dogs data.
Code:
Sub testMedianWithGroup()
Dim db As DAO.Database
Dim rs As DAO.Recordset
10 On Error GoTo testMedianWithGroup_Error
20 Set db = CurrentDb
'Set rs = db.OpenRecordset("Select aName, animalID from animal")
30 Set rs = db.OpenRecordset("Select distinct dogtype from dogs")
40 Debug.Print "dogtype " & vbTab & "Median weight"
50 Do While Not rs.EOF
60 Debug.Print rs!dogtype & " " & vbTab & vbTab & DMedian("wght", "dogs", "dogtype = '" & rs!dogtype & "'")
70 rs.MoveNext
80 Loop
90 On Error GoTo 0
100 Exit Sub
testMedianWithGroup_Error:
110 MsgBox "Error " & Err.Number & " in line " & Erl & " (" & Err.Description & ") in procedure testMedianWithGroup of Module AWF_Related"
End Sub
The results are
Code:
dogtype Median weight
doc 12.5
lab 60
pug 19.5
Median: mathematics : the middle value in a series of values arranged from smallest to largest