Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chris Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Aggregate Functions 2

Status
Not open for further replies.

PaulCoop

Programmer
Apr 3, 2001
31
GB
Can you, and if you can, how do you write new aggregate functions?

If you can there are the following functions that I wish to write:

Concat([field], delimiter_string)
Mode([field])
Median([field])

Paul Cooper
 
Hi Paul!

I think you would need to pass the table name also, here are some ideas:

Public Function DConcat(MyField As String, MyTable As String, MyCriteria As String, Delim As String) As String

Dim sql As String
Dim rst As DAO.Recordset
Dim intCounter As Long
Dim ReturnString As String

sql = "Select " & MyField & " From " & MyTable
If MyCriteria <> &quot;&quot; Then
sql = sql & &quot; Where &quot; & MyCriteria
End If

Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)
If rst.EOF = True And rst.BOF = True Then
DConcat = &quot;&quot;
Else
rst.MoveLast
rst.MoveFirst
ReturnString = &quot;&quot;
For intCounter = 0 To rst.RecordCount
If intCounter <> rst.RecordCount Then
ReturnString = ReturnString & rst.Fields(MyField) & Delim
Else
DConcat = ReturnString & rst.Fields(MyField)
Next intCounter
End If

End Function

Public Function Mode(MyField As String, MyTable As String, MyCriteria As String) As Variant

Dim sql As String
Dim rst As DAO.Recordset
Dim Value() As Variant
Dim ValueCount() As Integer
Dim intRecordNumber As Integer
Dim intCounter As Integer
Dim bolFound As Boolean
Dim intReturnValue As Integer

sql = &quot;Select &quot; & MyField & &quot; From &quot; & MyTable
If MyCriteria <> &quot;&quot; Then
sql = sql & &quot; Where &quot; & MyCriteria
End If

Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)
If rst.EOF = True And rst.BOF = True Then
Mode = &quot;&quot;
Else
rst.MoveFirst
intRecordNumber = 1
ReDim Value(1 to 1)
ReDim ValueCount(1 to 1)
Do Until rst.EOF = True
If intRecordNumber = 1 Then
Value(1) = rst.Fields(MyField)
ValueCount(1) = 1
intRecordNumber = 2
Else
For intCounter = 1 To intRecordNumber - 1
If Value(intCounter) = rst.Fields(MyField) Then
ValueCount(intCounter) = ValueCount(intCounter) + 1
bolFound = True
Exit For
End If
Next intCounter
If bolFound = True Then
bolFound = False
Else
ReDim Preserve Value(1 to intRecordNumber)
ReDim Preserve ValueCount(1 to intRecordNumber)
Value(intRecordNumber) = rst.Fields(MyField)
ValueCount(intRecordNumber) = 1
intRecordNumber = intRecordNumber + 1
End If
End If
rst.MoveNext
Loop
intRecordNumber = intRecordNumber - 1
End If

intReturnValue = 1
If intRecordNumber = 1 Then
Mode = Value(1)
Else
For intCounter = 2 to intRecordNumber
If ValueCount(intCounter) > ValueCount(intReturnValue) Then
intReturnValue = intCounter
Next intCounter
Mode = Value(intReturnValue)
End If

End Function

Public Function Median(MyField As String, MyTable As String, MyCriteria As String) As Variant

Dim sql As String
Dim rst As DAO.Recordset
Dim intMiddle As Integer

sql = &quot;Select &quot; & MyField & &quot; From &quot; & MyTable
If MyCriteria <> &quot;&quot; Then
sql = sql & &quot; Where &quot; & MyCriteria
End If
sql = sql & &quot; Order By &quot; & MyField

Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)
If rst.EOF = True And rst.BOF = True Then
Median = &quot;&quot;
Else
rst.MoveLast
intMiddle = rst.RecordCount\2
Median = rst.Fields(MyField)
End If

End Function

I imagine improvements can be made on these because I haven't tested any of them. Also I forgot to set rst = Nothing in any of them. At least it should give you a place to start.

hth
Jeff Bridgham
bridgham@purdue.edu
 
Paul,
If I understood your question, you wanted to write one that 'links' to the sql engine to run the same way as Sum,First, etc? The answer to that would be no, the sql engine is compiled and set in stone, so jebrys would be the path to take.

Even 'hand-written' functions that would mimmick Dlookup, for example, usually won't run as fast because these internal functions are compiled in the dll and will naturally have less overhead than a p-code function written by a user, unless you put such a handwritten in your own dll and referenced it.
--Jim
 
Hi!

Thanks Jim. I missed the SQL connection in the original post. But, as you point out, Paul can use these as public functions and call them from his SQL code. One must use what is available!
Jeff Bridgham
bridgham@purdue.edu
 
Hi!

Found an oops in one function and a better way in another. First the oops:

In the Median function the Else statement should be

If rst.EOF = True And rst.BOF = True Then
Median = &quot;&quot;
Else
rst.MoveLast
rst.MoveFirst
For intMiddle = 1 To rst.RecordCount\2
rst.MoveNext
Next intMiddle
Median = rst.Fields(MyField)
End If

And now the better way in the Mode Function:

sql = &quot;Select & MyField & &quot;, Count(&quot; & MyField & &quot;) As FieldCount From &quot; & MyTable & &quot; Group By &quot; & MyField
* check for criteria and open recordset*

If rst.EOF = True And rst.BOF = True Then
Mode = &quot;&quot;
Else
rst.MoveFirst
intCount = rst!FieldCount
varField = rst.Fields(MyField)
rst.MoveNext
Do Until rst.EOF = True
If rst!FieldCount > intCount Then
intCount = rst!FieldCount
varField = rst.Fields(MyField)
End if
rst.MoveNext
Loop
Mode = varField
End If

I think those are better.

Jeff Bridgham
bridgham@purdue.edu
 
Thanks for the code Jeff it will save me the effort of starting from stratch.

Jim you were right in recognising that I was meaning SQL aggregates (maybe I should of been a tad clearer, Jeff). It was really wishful thinking that made me ask the question in the first place as I have done similar stuff before by looping thorough a Recordset in code. It is a pity that Microsoft hasn't spotted the usefulness of such functions. (Unless any of these have been introduced in AccessXP of which I know nothing.)

Paul Cooper
 
Hmmmmmmmmm,

The functions all exist in the excel library, which may be added to Ms. Access (Menu: Tools==>References). Rather than re-inventing (including all that messy debugging), it is just a matter of propper use.

MichaelRed
m.red@att.net

There is never time to do it right but there is always time to do it over
 
Michael,
Are you sure those functions then become indigenous to the JET sql engine--where they can be used in an aggregate sql? I'm not seeing that. I think they're just standalone functions, but correct me if I'm wrong...
--Jim
 
? indigenious? - I don't think so -but they CAN be used where VBA functions are used. In some instances, the db engine does not recognize even VBA functions, so there are no 'guarntees', but I have used many of the Excel functions in Ms. Access and in VB, with some uses in both 'languages' including db/tables. Where it gets 'sticky' is where the Excel functions expect a range (e.g. A1:A21) reference. Mostly, this is just an array, but on some dunctions it apprars to either want the array passes by ref or I'm not understanding the call properly. As far as I know, the statstical package all use arg lists, so should be amenable to use by adding the reference. The only problem I am aware of w/ this approach is in distribuiting an app where the licensing issue(s) may be obscure and the ver/location of the libraries can be questionable as well.

MichaelRed
m.red@att.net

There is never time to do it right but there is always time to do it over
 
Yes I understand that. What Paul was looking for was a concat function that was (indigenous was perhaps not the best term) part of the core sql engine, in the same way that Min,Max, stdDev are. ie, the Concat would take 1 argument (and possibly a delimeter), and when run in an aggregate sql would concat all values of the single column specified that fell into the group-by set, putting the result in the single aggregate row.
I guess there are lots of wish-lists for functions to be run in aggregate like that, but for now it looks like a procedural approach is the way to go.
--Jim
 
jerby DID state that his functions were both untested and possibly subject to 'improvement':

Concat and Mode are (at least) subject to different approaches which result in fewer lines of code. Median fail to account for the 50% of instances where the recordset has an even # of records.

In refering to the Excel functions reference manual (an OLD copy), I did note the specific functions were limited to a specific number of arguments, however each also accepted an array -and it is not clear that the array cannot have more than the # args). So there are at least two reasons for ME to offer the alternatives.

Code:
Public Function basConcat(MyField As String, _
                          MyTable As String, _
                          Optional MyRel As String = &quot;&quot;, _
                          Optional MyCriteria As String = &quot;&quot;, _
                          Optional Delim As String = &quot;&quot;) As String

    Dim sql As String
    Dim rst As DAO.Recordset
    Dim RtnStr As String

    sql = &quot;Select &quot; & MyField & &quot; From &quot; & MyTable
    If (MyCriteria <> &quot;&quot;) Then
       sql = sql & &quot; Where &quot; & &quot; &quot; & MyField & &quot; &quot; & MyRel & &quot; &quot; & Chr(34) & MyCriteria & Chr(34)
    End If

    Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)
    If (rst.EOF = True And rst.BOF = True) Then
       basConcat = &quot;&quot;
    Else
       RtnStr = &quot;&quot;
       While Not rst.EOF
            RtnStr = RtnStr & rst.Fields(MyField) & Delim
            rst.MoveNext
        Wend
    End If

   basConcat = Left(RtnStr, Len(RtnStr) - Len(Delim))

End Function
Public Function basMode(MyField As String, _
                        MyTable As String, _
                        MyCriteria As String) As Variant

    Dim sql As String
    Dim rst As DAO.Recordset
    sql = &quot;Select &quot; & MyField & &quot;&quot;

    sql = sql & &quot;, Count(&quot; & MyField
    sql = sql & &quot;) As FieldCount &quot;
    sql = sql & &quot;From &quot; & MyTable
    If (MyCriteria <> &quot;&quot;) Then
        sql = sql & &quot; Where &quot; & &quot; &quot; & MyField & &quot; &quot; & MyCriteria & &quot; &quot;
    End If

    sql = sql & &quot; Group By &quot; & MyField
    sql = sql & &quot; Order By Count(&quot; & MyField & &quot;) Desc;&quot;


    Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)

    If rst.EOF = True And rst.BOF = True Then
       basMode = &quot;&quot;
    Else
       rst.MoveFirst
       basMode = rst(MyField)
    End If

End Function
Public Function basMedian(MyField As String, _
                          MyTable As String, _
                          MyCriteria As String) As Double

    Dim sql As String
    Dim rst As DAO.Recordset
    Dim intMiddle As Integer
    Dim NumToCalc As Integer
    Dim valMedian As Double

    sql = &quot;Select &quot; & MyField & &quot; From &quot; & MyTable
    If MyCriteria <> &quot;&quot; Then
        sql = sql & &quot; Where &quot; & MyCriteria
    End If
    sql = sql & &quot; Order By &quot; & MyField

    Set rst = CurrentDb.OpenRecordset(sql, dbOpenDynaset)
    If (rst.EOF = True And rst.BOF = True) Then
        basMedian = 0
     Else
        rst.MoveLast
        rst.MoveFirst

        For intMiddle = 1 To rst.RecordCount \ 2
           rst.MoveNext
        Next intMiddle

        NumToCalc = (rst.RecordCount \ 2) Mod 2
        For intMiddle = 1 To NumToCalc
            valMedian = rst.Fields(MyField) + basvalMedianMedian
        Next intMiddle
        basMedian = valMedian / (NumToCalc - 1)

    End If

End Function
MichaelRed
m.red@att.net

There is never time to do it right but there is always time to do it over
 
Hi!

Good code MichaelRed!!

I knew there had to be improvements but I couldn't find the time to test. I hope Paul did some testing before just using my code!

Jeff Bridgham
bridgham@purdue.edu
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top