Ticket #1435: aggr_functions-r3897.diff

File aggr_functions-r3897.diff, 13.6 KB (added by wiz, 18 years ago)

updated patch for r3897 of trunk

  • django/db/models/manager.py

     
    5151    def all(self):
    5252        return self.get_query_set()
    5353
    54     def count(self):
    55         return self.get_query_set().count()
     54    def count(self,fieldname="*"):
     55        """Returns the number of rows in the default case. Else returns the number of non-null entries in the column corresponding to the fieldname.
     56        """
     57        return self.get_query_set().count(fieldname)
    5658
    5759    def dates(self, *args, **kwargs):
    5860        return self.get_query_set().dates(*args, **kwargs)
     
    99101    def values(self, *args, **kwargs):
    100102        return self.get_query_set().values(*args, **kwargs)
    101103
     104    # Aggregate functions (column-oriented)
     105
     106    def get_aggregate(self,functype,column):
     107        return self.get_query_set().get_aggregate(functype,column)
     108
     109    def get_aggregates(self,functypes,column):
     110        return self.get_query_set().get_aggregates(functypes,column)
     111
     112    def sum(self,fieldname):
     113        return self.get_query_set().sum(fieldname)
     114
     115    def min(self,fieldname):
     116        return self.get_query_set().min(fieldname)
     117
     118    def max(self,fieldname):
     119        return self.get_query_set().max(fieldname)
     120
     121    def avg(self,fieldname):
     122        return self.get_query_set().avg(fieldname)
     123
     124    def stddev(self,fieldname):
     125        return self.get_query_set().stddev(fieldname)
     126
     127    def median(self,fieldname):
     128        return self.get_query_set().median(fieldname)
     129
    102130class ManagerDescriptor(object):
    103131    # This class ensures managers aren't accessible via model instances.
    104132    # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
  • django/db/models/query.py

     
    11from django.db import backend, connection, transaction
    22from django.db.models.fields import DateField, FieldDoesNotExist
     3from django.db.models.fields import DateField, IntegerField, FloatField, FieldDoesNotExist
    34from django.db.models import signals
    45from django.dispatch import dispatcher
    56from django.utils.datastructures import SortedDict
     
    157158        combined._filters = self._filters | other._filters
    158159        return combined
    159160
     161    ##############################################
     162    # HELPER METHODS THAT EXAMINE FIELD METADATA #
     163    ##############################################
     164
     165    def is_number(self, fieldname):
     166        "Returns flag, True for integer. False for float. Non-float and non-integer raises either a FieldDoesNotExist or TypeError exception."
     167        field = self.model._meta.get_field(fieldname)
     168        # Let the FieldDoesNotExist exception propogate
     169        if isinstance(field, IntegerField):
     170            return True
     171        if isinstance(field, FloatField):
     172            return False
     173        raise TypeError, "Field %s for Model %s is not an IntegerField or FloatField" % (fieldname, self.model._meta.object_name)
     174
     175    def is_number_or_date(self, fieldname):
     176        "Returns 0 for int; 1 for float; 2 for date. Raises either a FieldDoesNotExist or TypeError exception if not an Integer, Float or Date."
     177        field = self.model._meta.get_field(fieldname)
     178        # Let the FieldDoesNotExist exception propogate
     179        if isinstance(field, IntegerField):
     180            return 0
     181        if isinstance(field, FloatField):
     182            return 1
     183        if isinstance(field, DateField):
     184            return 2
     185        raise TypeError, "Field %s for Model %s is not an IntegerField, FloatField or DateField" % (fieldname, self.model._meta.object_name)
     186
    160187    ####################################
    161188    # METHODS THAT DO DATABASE QUERIES #
    162189    ####################################
     
    185212                    setattr(obj, k[0], row[index_end+i])
    186213                yield obj
    187214
    188     def count(self):
    189         "Performs a SELECT COUNT() and returns the number of records as an integer."
     215    def count(self,fieldname="*"):
     216        "Performs a SELECT COUNT(coulmn) and returns the number of records as an integer."
    190217        counter = self._clone()
    191218        counter._order_by = ()
    192219        counter._offset = None
     
    194221        counter._select_related = False
    195222        select, sql, params = counter._get_sql_clause()
    196223        cursor = connection.cursor()
     224        if fieldname == '*':
     225            column = '*'
     226        else:
     227            column = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
     228                                backend.quote_name(fieldname))
    197229        if self._distinct:
    198             id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
    199                     backend.quote_name(self.model._meta.pk.column))
    200             cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params)
     230            if fieldname == '*':
     231                column = "%s.%s" % (backend.quote_name(self.model._meta.db_table),
     232                                    backend.quote_name(self.model._meta.pk.column))
     233            cursor.execute("SELECT COUNT(DISTINCT(%s))" % column + sql, params)
    201234        else:
    202             cursor.execute("SELECT COUNT(*)" + sql, params)
     235            cursor.execute("SELECT COUNT(%s)" % (column) + sql, params)
    203236        return cursor.fetchone()[0]
    204237
     238    def get_aggregate(self,type,column):
     239        "Performs the specified aggregate function on the named column."
     240        agg = self._clone()
     241        agg._order_by = ()
     242        agg._offset = None
     243        agg._limit = None
     244        agg._select_related = False
     245        select, sql, params = agg._get_sql_clause()
     246        cursor = connection.cursor()
     247        sel = "SELECT %s(%s)" % (type, column)
     248        cursor.execute(sel + sql, params)
     249        return cursor.fetchone()[0]
     250
     251    def get_aggregates(self,types,column):
     252        "Performs the specified aggregate functions on the named column."
     253        agg = self._clone()
     254        agg._order_by = ()
     255        agg._offset = None
     256        agg._limit = None
     257        agg._select_related = False
     258        select, sql, params = agg._get_sql_clause()
     259        cursor = connection.cursor()
     260        sel = []
     261        sel.append( "SELECT" )
     262        for type in types:
     263            sel.append ( "%s(%s)," % (type, column))
     264        select = " ".join(sel)[:-1]
     265        cursor.execute(select + sql, params)
     266        return cursor.fetchone()
     267
     268    def sum(self, fieldname):
     269        "Performs a SELECT SUM() on the specified column."
     270        isInt = self.is_number(fieldname)
     271        column = self.model._meta.get_field(fieldname).column
     272        result = self.get_aggregate("SUM",column)
     273        if isInt:
     274            return int(result)
     275        return result
     276
     277    def avg(self, fieldname):
     278        "Performs a SELECT AVG() on the specified column."
     279        self.is_number(fieldname)
     280        column = self.model._meta.get_field(fieldname).column
     281        return self.get_aggregate("AVG",column)
     282
     283    def stddev(self, fieldname):
     284        "Performs a SELECT STDDEV() on the specified column."
     285        self.is_number(fieldname)
     286        column = self.model._meta.get_field(fieldname).column
     287        return self.get_aggregate("STDDEV",column)
     288
     289    def min(self, fieldname):
     290        "Performs a SELECT MIN() on the specified column."
     291        self.is_number_or_date(fieldname)
     292        column = self.model._meta.get_field(fieldname).column
     293        return self.get_aggregate("MIN",column)
     294
     295    def max(self, fieldname):
     296        "Performs a SELECT MAX() on the specified column."
     297        self.is_number_or_date(fieldname)
     298        column = self.model._meta.get_field(fieldname).column
     299        return self.get_aggregate("MAX",column)
     300
     301    def median(self, fieldname):
     302        "Returns the median value for the specified column."
     303        coltype = self.is_number_or_date(fieldname)
     304        column = self.model._meta.get_field(fieldname).column
     305        fetcher = self._clone()
     306        fetcher._order_by = (column,)
     307        fetcher._offset = None
     308        fetcher._limit = None
     309        fetcher._select_related = False
     310        select, sql, params = fetcher._get_sql_clause()
     311        sel = "SELECT %s" % (column)
     312        cursor = connection.cursor()
     313        cursor.execute(sel + sql, params)
     314        rows = cursor.fetchall()
     315        midvalue = len(rows) / 2
     316        if coltype == 2:
     317            # returning a date
     318            return str(rows[midvalue][0])
     319        else:
     320            return rows[midvalue][0]
     321
    205322    def get(self, *args, **kwargs):
    206323        "Performs the SELECT and returns a single object matching the given keyword arguments."
    207324        clone = self.filter(*args, **kwargs)
  • docs/db-api.txt

     
    844844
    845845Note ``latest()`` exists purely for convenience and readability.
    846846
     847Aggregate Functions
     848-------------------
     849
     850Aggregate functions perform calculations on columns. Typically
     851they return a single value. They are in two groups: high_level
     852and low_level.
     853
     854High Level Functions
     855~~~~~~~~~~~~~~~~~~~~
     856
     857The high_level functions are sum(), min(), max(), avg(), stddev()
     858and median(). Each takes a fieldname as an argument. The type of
     859the field is checked for correctness as only certain datatypes are
     860allowed for each of the high level functions.
     861
     862sum(fieldname)
     863~~~~~~~~~~~~~~
     864
     865Returns the sum of the named field. The field must be an
     866IntegerField or a FloatField. The returned value corresponds
     867with the type of the column.
     868
     869min(fieldname), max(fieldname)
     870~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     871
     872Returns the minimum or maximum value of the named field. The field
     873must be an IntegerField, FloatField or DateField. The returned value
     874corresponds with the type of the field. (This is a string
     875representation if the field is a DateField.)
     876
     877avg(fieldname)
     878~~~~~~~~~~~~~~
     879
     880Returns the average of the named field. The field must be an
     881IntegerField or a FloatField. The returned value is a Float.
     882
     883stddev(fieldname)
     884~~~~~~~~~~~~~~~~~
     885
     886Returns the standard deviation of the named field. The field must be an
     887IntegerField or a FloatField. The returned value is a Float.
     888(Not supported on sqlite3. You get an OperationError exception.)
     889
     890median(fieldname)
     891~~~~~~~~~~~~~~~~~
     892
     893Returns the median value of the named field. The field
     894must be an IntegerField, FloatField or DateField. The returned
     895value corresponds with the type of the field. (This is a string
     896representation if the column is a DateField.) Unlike the other
     897functions in this group, this function does not use the DB
     898supplied capabilities. It fetches all of the values of the field
     899ordered by that field and returns the middle value. (If there
     900are an even number of values, the second of the two middle
     901values is returned.)
     902
     903Low Level Functions
     904~~~~~~~~~~~~~~~~~~~
     905
     906There are two low level functions: get_aggregate() and
     907get_aggregates(). They do minimal checking and allow for
     908powerful queries that potentially return multiple values
     909and/or combine multiple column arithmetically.
     910
     911The low_level functions take columnnames instead of fieldnames.
     912You must do your own conversion from fieldname to columnname
     913if you are taking advantage of the fieldname mapping. (By
     914default fieldnames and columnnames match each other and so
     915most users will not have to worry about this distinction.)
     916
     917get_aggregate(type,columnname)
     918~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     919
     920This function supplies direct support for all database-supplied
     921aggregate functions. The type parameter is the name of an aggregate
     922function such as 'SUM', 'VARIANCE' or so forth limited only by
     923what set of functions your particular database supports. The return
     924value uses whatever type your database connonically returns. (Most
     925databases return the same type as the named column, although this
     926is not the case for some functions such as "avg" or "stddev" which
     927always returns a Float. Also note that sqlite3 always returns a Float
     928for all aggregate function.)
     929
     930Note that the columnname is not explicitly checked for type and
     931so it is possible to combine columns arithmetically (with care!)
     932as follows:
     933
     934Inventory.objects.get_aggregate('AVG','quantity*price')
     935
     936This returns the average value of the 'quantity' column multiplied
     937by the 'price' column.
     938
     939Meals.objects.get_aggregate('MAX','price+tax+tip')
     940
     941This returns the highest priced meal which is calculated by the
     942database by adding the 'price', the 'tax' and the 'tip' columns.
     943
     944(As a repeat warning: Don't forget to get the columnname from your
     945fieldname if you are using fieldname mapping.)
     946
     947get_aggregates(types,columnname)
     948~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     949
     950This function allows a single SQL operation to perform multiple
     951aggregate functions. The types field is an iterable list of
     952aggregate function names. The columnname is handled in the same
     953manner as with the get_aggregate() function. For example:
     954
     955Inventory.objects.get_aggregates(['AVG','MIN','MAX'],'quantity')
     956
     957The results are returned in an array.
     958
     959Usage
     960~~~~~
     961
     962Typical use targets all of the rows in the targeted table.
     963For example:
     964
     965Articles.objects.sum('wordcount')
     966
     967However it is possible to combine the aggregate functions with
     968judicious filtering. For example:
     969
     970Poll.objects.filter(question__contains='football').min('pub_date')
     971
     972Exceptions
     973~~~~~~~~~~
     974
     975The most common exceptions encountered when using aggregate functions are:
     976
     977FieldDoesNotExist - the columnname is not found.
     978
     979TypeError - the named column uses an unsupported type.
     980
     981OperationError - the functype is not supported by the database.
     982
     983
    847984Field lookups
    848985-------------
    849986
Back to Top