aggregates.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. """
  2. Classes to represent the default SQL aggregate functions
  3. """
  4. class AggregateField(object):
  5. """An internal field mockup used to identify aggregates in the
  6. data-conversion parts of the database backend.
  7. """
  8. def __init__(self, internal_type):
  9. self.internal_type = internal_type
  10. def get_internal_type(self):
  11. return self.internal_type
  12. ordinal_aggregate_field = AggregateField('IntegerField')
  13. computed_aggregate_field = AggregateField('FloatField')
  14. class Aggregate(object):
  15. """
  16. Default SQL Aggregate.
  17. """
  18. is_ordinal = False
  19. is_computed = False
  20. sql_template = '%(function)s(%(field)s)'
  21. def __init__(self, col, source=None, is_summary=False, **extra):
  22. """Instantiate an SQL aggregate
  23. * col is a column reference describing the subject field
  24. of the aggregate. It can be an alias, or a tuple describing
  25. a table and column name.
  26. * source is the underlying field or aggregate definition for
  27. the column reference. If the aggregate is not an ordinal or
  28. computed type, this reference is used to determine the coerced
  29. output type of the aggregate.
  30. * extra is a dictionary of additional data to provide for the
  31. aggregate definition
  32. Also utilizes the class variables:
  33. * sql_function, the name of the SQL function that implements the
  34. aggregate.
  35. * sql_template, a template string that is used to render the
  36. aggregate into SQL.
  37. * is_ordinal, a boolean indicating if the output of this aggregate
  38. is an integer (e.g., a count)
  39. * is_computed, a boolean indicating if this output of this aggregate
  40. is a computed float (e.g., an average), regardless of the input
  41. type.
  42. """
  43. self.col = col
  44. self.source = source
  45. self.is_summary = is_summary
  46. self.extra = extra
  47. # Follow the chain of aggregate sources back until you find an
  48. # actual field, or an aggregate that forces a particular output
  49. # type. This type of this field will be used to coerce values
  50. # retrieved from the database.
  51. tmp = self
  52. while tmp and isinstance(tmp, Aggregate):
  53. if getattr(tmp, 'is_ordinal', False):
  54. tmp = ordinal_aggregate_field
  55. elif getattr(tmp, 'is_computed', False):
  56. tmp = computed_aggregate_field
  57. else:
  58. tmp = tmp.source
  59. self.field = tmp
  60. def relabel_aliases(self, change_map):
  61. if isinstance(self.col, (list, tuple)):
  62. self.col = (change_map.get(self.col[0], self.col[0]), self.col[1])
  63. def as_sql(self, quote_func=None):
  64. "Return the aggregate, rendered as SQL."
  65. if not quote_func:
  66. quote_func = lambda x: x
  67. if hasattr(self.col, 'as_sql'):
  68. field_name = self.col.as_sql(quote_func)
  69. elif isinstance(self.col, (list, tuple)):
  70. field_name = '.'.join([quote_func(c) for c in self.col])
  71. else:
  72. field_name = self.col
  73. params = {
  74. 'function': self.sql_function,
  75. 'field': field_name
  76. }
  77. params.update(self.extra)
  78. return self.sql_template % params
  79. class Avg(Aggregate):
  80. is_computed = True
  81. sql_function = 'AVG'
  82. class Count(Aggregate):
  83. is_ordinal = True
  84. sql_function = 'COUNT'
  85. sql_template = '%(function)s(%(distinct)s%(field)s)'
  86. def __init__(self, col, distinct=False, **extra):
  87. super(Count, self).__init__(col, distinct=distinct and 'DISTINCT ' or '', **extra)
  88. class Max(Aggregate):
  89. sql_function = 'MAX'
  90. class Min(Aggregate):
  91. sql_function = 'MIN'
  92. class StdDev(Aggregate):
  93. is_computed = True
  94. def __init__(self, col, sample=False, **extra):
  95. super(StdDev, self).__init__(col, **extra)
  96. self.sql_function = sample and 'STDDEV_SAMP' or 'STDDEV_POP'
  97. class Sum(Aggregate):
  98. sql_function = 'SUM'
  99. class Variance(Aggregate):
  100. is_computed = True
  101. def __init__(self, col, sample=False, **extra):
  102. super(Variance, self).__init__(col, **extra)
  103. self.sql_function = sample and 'VAR_SAMP' or 'VAR_POP'