Ticket #3566: aggregates.patch
File aggregates.patch, 64.5 KB (added by , 16 years ago) |
---|
-
django/db/aggregates.py
1 """ 2 Classes to represent the default aggregate functions 3 """ 4 5 from django.db.models.sql.constants import LOOKUP_SEP 6 from django.core.exceptions import FieldError 7 8 def interpolate(templateStr, **kws): 9 from string import Template 10 return Template(templateStr).substitute(kws) 11 12 class Aggregate(object): 13 """ 14 Default Aggregate. 15 func 16 """ 17 def __init__(self, lookup): 18 self.func = self.__class__.__name__.upper() 19 self.lookup = lookup 20 self.field_name = self.lookup.split(LOOKUP_SEP)[-1] 21 self.aliased_name = '%s__%s' % (self.lookup, 22 self.__class__.__name__.lower()) 23 self.sql_template = '${func}(${field})' 24 25 def relabel_aliases(self, change_map): 26 if self.col_alias in change_map: 27 self.col_alias = change_map[self.col_alias] 28 29 def as_fold(self, quote_func=None): 30 if self.lookup != self.field_name: 31 raise FieldError('Joins are not allowed here.') 32 #check to raise other exceptions 33 return '%s(%s)' % (self.func, self.lookup) 34 35 def as_sql(self, quote_func=None): 36 if not quote_func: 37 quote_func = lambda x: x 38 return interpolate(self.sql_template, 39 func=self.func.upper(), 40 field='.'.join([quote_func(self.col_alias), 41 quote_func(self.column)])) 42 43 class Max(Aggregate): 44 pass 45 46 class Min(Aggregate): 47 pass 48 49 class Avg(Aggregate): 50 pass 51 52 class Sum(Aggregate): 53 pass 54 55 class Count(Aggregate): 56 def __init__(self, lookup, distinct=False): 57 if distinct: 58 distinct = 'DISTINCT ' 59 else: 60 distinct = '' 61 super(Count, self).__init__(lookup) 62 self.sql_template = '${func}(%s${field})' % distinct 63 64 65 -
django/db/models/sql/query.py
15 15 from django.db import connection 16 16 from django.db.models import signals 17 17 from django.db.models.fields import FieldDoesNotExist 18 from django.db.models.query_utils import select_related_descend 18 from django.db.models.query_utils import select_related_descend, _value_or_object 19 19 from django.db.models.sql.where import WhereNode, EverythingNode, AND, OR 20 20 from django.db.models.sql.datastructures import Count 21 21 from django.core.exceptions import FieldError … … 57 57 self.start_meta = None 58 58 self.select_fields = [] 59 59 self.related_select_fields = [] 60 self.allow_nulls = False 60 61 self.dupe_avoidance = {} 61 62 self.used_aliases = set() 62 63 self.filter_is_sticky = False … … 167 168 obj.standard_ordering = self.standard_ordering 168 169 obj.ordering_aliases = [] 169 170 obj.start_meta = self.start_meta 171 obj.allow_nulls = self.allow_nulls 170 172 obj.select_fields = self.select_fields[:] 171 173 obj.related_select_fields = self.related_select_fields[:] 172 174 obj.dupe_avoidance = self.dupe_avoidance.copy() … … 217 219 row = self.resolve_columns(row, fields) 218 220 yield row 219 221 222 def get_aggregation(self): 223 """ 224 Returns the dictionary with the values of the existing aggregations. 225 """ 226 if not self.select: 227 return {} 228 229 #If there is a group by clause aggregating does not add useful 230 #information but retrieves only the first row. Aggregating 231 #over the subquery instead. 232 if self.group_by: 233 from subqueries import AggregateQuery 234 obj = self.clone() 235 external = [] 236 select = [i for i in enumerate(obj.select)] 237 deleted = 0 238 for (i, field) in select: 239 if hasattr(field, 'reduce') and field.reduce: 240 external.append(field) 241 del obj.select[i-deleted] 242 deleted += 1 243 query = AggregateQuery(self.model, self.connection) 244 query.add_select(external) 245 query.add_subquery(obj) 246 247 data = [_value_or_object(x) for x in query.execute_sql(SINGLE)] 248 result = dict(zip([i.aliased_name for i in query.select], data)) 249 return result 250 251 self.select = self.get_aggregate_list() 252 self.extra_select = {} 253 254 data = [_value_or_object(x) for x in self.execute_sql(SINGLE)] 255 result = dict(zip([i.aliased_name for i in self.select], data)) 256 257 return result 258 220 259 def get_count(self): 221 260 """ 222 261 Performs a COUNT() query using the current filter constraints. … … 289 328 result.append(' AND '.join(self.extra_where)) 290 329 291 330 if self.group_by: 292 grouping = self.get_grouping() 293 result.append('GROUP BY %s' % ', '.join(grouping)) 331 result.append('GROUP BY %s' % ', '.join(self.get_grouping())) 294 332 333 having = [] 334 having_params = [] 295 335 if self.having: 296 having, h_params = self.get_having() 297 result.append('HAVING %s' % ', '.join(having)) 298 params.extend(h_params) 336 qn = self.quote_name_unless_alias 337 for (aggregate, lookup_type, value) in self.having: 338 if lookup_type == 'in': 339 having.append('%s IN (%s)' % (aggregate.as_sql(quote_func=qn), 340 ', '.join(['%s'] * len(value)))) 341 having_params.extend(value) 342 elif lookup_type == 'range': 343 having.append('%s BETWEEN %%s and %%s' % 344 aggregate.as_sql(quote_func=qn)) 345 having_params.extend([value[0], value[1]]) 346 elif lookup_type == 'isnull': 347 having.append('%s IS %sNULL' % (aggregate.as_sql(quote_func=qn), 348 (not value and "NOT " or ''))) 349 else: 350 if lookup_type not in connection.operators: 351 raise TypeError('Invalid lookup_type: %r' % lookup_type) 352 353 having.append('%s %s' % (aggregate.as_sql(quote_func=qn), 354 connection.operators[lookup_type])) 355 having_params.append(value) 356 357 having_clause = 'HAVING ' + ' AND '.join(having) 358 result.append(having_clause) 299 359 300 360 if ordering: 301 361 result.append('ORDER BY %s' % ', '.join(ordering)) … … 311 371 result.append('OFFSET %d' % self.low_mark) 312 372 313 373 params.extend(self.extra_params) 374 params.extend(having_params) 314 375 return ' '.join(result), tuple(params) 315 376 316 377 def combine(self, rhs, connector): … … 413 474 self.join((None, self.model._meta.db_table, None, None)) 414 475 if self.select_related and not self.related_select_cols: 415 476 self.fill_related_selections() 477 if self.allow_nulls: 478 self.promote_all() 416 479 417 480 def get_columns(self, with_aliases=False): 418 481 """ … … 436 499 for col in self.select: 437 500 if isinstance(col, (list, tuple)): 438 501 r = '%s.%s' % (qn(col[0]), qn(col[1])) 439 if with_aliases and col[1] in col_aliases: 440 c_alias = 'Col%d' % len(col_aliases) 441 result.append('%s AS %s' % (r, c_alias)) 442 aliases.add(c_alias) 443 col_aliases.add(c_alias) 502 if with_aliases: 503 if col[1] in col_aliases: 504 c_alias = 'Col%d' % len(col_aliases) 505 result.append('%s AS %s' % (r, c_alias)) 506 aliases.add(c_alias) 507 col_aliases.add(c_alias) 508 else: 509 result.append('%s AS %s' % (r, col[1])) 510 aliases.add(r) 511 col_aliases.add(col[1]) 444 512 else: 445 513 result.append(r) 446 514 aliases.add(r) 447 515 col_aliases.add(col[1]) 448 516 else: 449 result.append(col.as_sql(quote_func=qn)) 517 if hasattr(col, 'aliased_name'): 518 result.append('%s AS %s' % (col.as_sql(quote_func=qn), 519 col.aliased_name)) 520 else: 521 result.append(col.as_sql(quote_func=qn)) 522 450 523 if hasattr(col, 'alias'): 451 524 aliases.add(col.alias) 452 525 col_aliases.add(col.alias) 526 453 527 elif self.default_cols: 454 528 cols, new_aliases = self.get_default_columns(with_aliases, 455 529 col_aliases) … … 621 695 asc, desc = ORDER_DIR['ASC'] 622 696 else: 623 697 asc, desc = ORDER_DIR['DESC'] 698 624 699 for field in ordering: 700 found = False 701 for aggregate in self.get_aggregate_list(): 702 if aggregate.aliased_name in field: 703 if field[0] == '-': 704 order = desc 705 else: 706 order = asc 707 result.append('%s %s' % (aggregate.as_sql(), order)) 708 found = True 709 if found: 710 continue 625 711 if field == '?': 626 712 result.append(self.connection.ops.random_function_sql()) 627 713 continue … … 747 833 """ Decreases the reference count for this alias. """ 748 834 self.alias_refcount[alias] -= 1 749 835 836 def promote_all(self): 837 """ Promotes every alias """ 838 for alias in self.alias_map: 839 self.promote_alias(alias, unconditional=True) 840 750 841 def promote_alias(self, alias, unconditional=False): 751 842 """ 752 843 Promotes the join type of an alias to an outer join if it's possible … … 884 975 alias = self.join((None, self.model._meta.db_table, None, None)) 885 976 return alias 886 977 978 def is_aggregate(self, obj): 979 from django.db.aggregates import Aggregate 980 return isinstance(obj, Aggregate) 981 982 def get_aggregate_list(self, attribute=None): 983 from django.db.aggregates import Aggregate 984 if not attribute: 985 return [x for x in self.select if isinstance(x, Aggregate)] 986 else: 987 return [getattr(x, attribute) for x in self.select 988 if isinstance(x, Aggregate)] 989 887 990 def count_active_tables(self): 888 991 """ 889 992 Returns the number of tables in this query with a non-zero reference … … 1061 1164 self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, 1062 1165 used, next, restricted, new_nullable, dupe_set, avoid) 1063 1166 1167 def add_aggregate(self, aggregate_expr, model): 1168 """ 1169 Adds a single aggregate expression to the Query 1170 """ 1171 opts = model._meta 1172 1173 #Do not waste time in checking the joins if it's an aggregate 1174 #on an annotation 1175 if (self.group_by and aggregate_expr.reduce): 1176 self.select.append(aggregate_expr) 1177 return 1178 1179 field_list = aggregate_expr.lookup.split(LOOKUP_SEP) 1180 1181 if (len(field_list) > 1 or 1182 field_list[0] not in [i.name for i in opts.fields]): 1183 1184 field, target, opts, join_list, last, _ = self.setup_joins( 1185 field_list, opts, self.get_initial_alias(), False) 1186 1187 self.allow_nulls = True 1188 aggregate_expr.column = target.column 1189 1190 field_name = field_list.pop() 1191 aggregate_expr.col_alias = join_list[-1] 1192 else: 1193 field_name = field_list[0] 1194 aggregate_expr.col_alias = opts.db_table 1195 1196 fields = dict([(field.name, field) for field in opts.fields]) 1197 aggregate_expr.column = fields[field_name].column 1198 self.select.append(aggregate_expr) 1199 1064 1200 def add_filter(self, filter_expr, connector=AND, negate=False, trim=False, 1065 1201 can_reuse=None, process_extras=True): 1066 1202 """ … … 1116 1252 alias = self.get_initial_alias() 1117 1253 allow_many = trim or not negate 1118 1254 1255 for i in self.get_aggregate_list(): 1256 if i.aliased_name == parts[0] : 1257 self.having.append((i, lookup_type, value)) 1258 return 1259 1119 1260 try: 1120 1261 field, target, opts, join_list, last, extra_filters = self.setup_joins( 1121 1262 parts, opts, alias, True, allow_many, can_reuse=can_reuse, … … 1514 1655 """ 1515 1656 return not (self.low_mark or self.high_mark) 1516 1657 1517 def add_fields(self, field_names, allow_m2m=True ):1658 def add_fields(self, field_names, allow_m2m=True, rebuild=False): 1518 1659 """ 1519 1660 Adds the given (model) fields to the select set. The field names are 1520 1661 added in the order specified. 1662 1663 If rebuild is True, the field list is rebuilded from scratch 1664 keeping only the aggregate objects. 1521 1665 """ 1522 1666 alias = self.get_initial_alias() 1523 1667 opts = self.get_meta() 1668 1669 aggregates = [] 1670 if rebuild: 1671 aggregates = self.get_aggregate_list() 1672 self.select = [] 1673 1524 1674 try: 1525 1675 for name in field_names: 1526 1676 field, target, u2, joins, u3, u4 = self.setup_joins( … … 1545 1695 names.sort() 1546 1696 raise FieldError("Cannot resolve keyword %r into field. " 1547 1697 "Choices are: %s" % (name, ", ".join(names))) 1698 self.select.extend(aggregates) 1548 1699 1549 1700 def add_ordering(self, *ordering): 1550 1701 """ … … 1576 1727 if force_empty: 1577 1728 self.default_ordering = False 1578 1729 1730 def set_group_by(self): 1731 if self.connection.features.allows_group_by_pk: 1732 if len(self.select) == len(self.model._meta.fields): 1733 #there might be problems with the aliases here. check. 1734 self.group_by.append('.'.join([self.model._meta.db_table, 1735 self.model._meta.pk.column])) 1736 return 1737 1738 for sel in self.select: 1739 if not self.is_aggregate(sel): 1740 self.group_by.append(sel) 1741 1579 1742 def add_count_column(self): 1580 1743 """ 1581 1744 Converts the query to do count(...) or count(distinct(pk)) in order to -
django/db/models/sql/subqueries.py
409 409 410 410 def get_ordering(self): 411 411 return () 412 413 class AggregateQuery(Query): 414 """ 415 An AggregateQuery takes another query as a parameter to the FROM 416 clause and only selects the elements in the provided list. 417 """ 418 #CK Clean this 419 def add_select(self, select): 420 self.select = select 421 422 def add_subquery(self, query): 423 self.subquery, self.sub_params = query.as_sql(with_col_aliases=True) 424 425 def as_sql(self, quote_func=None): 426 """ 427 Creates the SQL for this query. Returns the SQL string and list of 428 parameters. 429 """ 430 sql = ('SELECT %s FROM (%s) AS subquery' % 431 (', '.join([i.as_fold() for i in self.select]), self.subquery)) 432 params = self.sub_params 433 return (sql, params) 434 -
django/db/models/manager.py
101 101 def filter(self, *args, **kwargs): 102 102 return self.get_query_set().filter(*args, **kwargs) 103 103 104 def aggregate(self, *args, **kwargs): 105 return self.get_query_set().aggregate(*args, **kwargs) 106 107 def annotate(self, *args, **kwargs): 108 return self.get_query_set().annotate(*args, **kwargs) 109 104 110 def complex_filter(self, *args, **kwargs): 105 111 return self.get_query_set().complex_filter(*args, **kwargs) 106 112 -
django/db/models/query.py
4 4 from sets import Set as set # Python 2.3 fallback 5 5 6 6 from django.db import connection, transaction, IntegrityError 7 from django.db.aggregates import Aggregate 7 8 from django.db.models.fields import DateField 8 from django.db.models.query_utils import Q, select_related_descend 9 from django.db.models.query_utils import Q, select_related_descend, _value_or_object 9 10 from django.db.models import signals, sql 10 11 from django.utils.datastructures import SortedDict 11 12 … … 266 267 max_depth = self.query.max_depth 267 268 extra_select = self.query.extra_select.keys() 268 269 index_start = len(extra_select) 269 for row in self.query.results_iter(): 270 271 for row in self.query.results_iter(): 270 272 if fill_cache: 271 obj, _ = get_cached_row(self.model, row, index_start,272 max_depth, requested=requested)273 obj, aggregate_start = get_cached_row(self.model, row, 274 index_start, max_depth, requested=requested) 273 275 else: 274 obj = self.model(*row[index_start:]) 276 aggregate_start = index_start + len(self.model._meta.fields) 277 #ommit aggregates in object creation 278 obj = self.model(*row[index_start:aggregate_start]) 279 275 280 for i, k in enumerate(extra_select): 276 281 setattr(obj, k, row[i]) 282 283 data_length = len(row) 284 if aggregate_start < data_length: 285 #the aggregate values retreived from the backend 286 aggregate_values = [_value_or_object(row[i]) 287 for i in range(aggregate_start, data_length)] 288 289 select = self.query.extra_select.keys() + self.query.select 290 #Add the attributes to the model 291 new_values = dict(zip( 292 [select[i].aliased_name 293 for i in range(aggregate_start, len(select))], 294 aggregate_values)) 295 296 obj.__dict__.update(new_values) 297 277 298 yield obj 278 299 300 def aggregate(self, *args, **kwargs): 301 """ 302 Returns a dictionary containing the calculations (aggregation) 303 over the current queryset 304 305 If args is present the expression is passed as a kwarg ussing 306 the Aggregate object's default alias. 307 """ 308 for arg in args: 309 kwargs[arg.aliased_name] = arg 310 311 for (alias, aggregate_expr) in kwargs.items(): 312 aggregate_expr.aliased_name = alias 313 aggregate_expr.reduce = True 314 self.query.add_aggregate(aggregate_expr, self.model) 315 316 return self.query.get_aggregation() 317 279 318 def count(self): 280 319 """ 281 320 Performs a SELECT COUNT() and returns the number of records as an … … 547 586 """ 548 587 self.query.select_related = other.query.select_related 549 588 589 def annotate(self, *args, **kwargs): 590 self.return_groups = kwargs.get('grouped_objects') 591 try: 592 del kwargs['grouped_objects'] 593 except: 594 pass 595 596 for arg in args: 597 kwargs[arg.aliased_name] = arg 598 599 opts = self.model._meta 600 obj = self._clone(return_groups=self.return_groups) 601 602 if isinstance(obj, ValuesQuerySet): 603 obj.query.set_group_by() 604 #obj.query.group_by.extend(obj.query.select[:]) 605 606 if not obj.query.group_by: 607 field_names = [f.attname for f in opts.fields] 608 obj.query.add_fields(field_names, False) 609 obj.query.set_group_by() 610 611 for (alias, aggregate_expr) in kwargs.items(): 612 aggregate_expr.aliased_name = alias 613 aggregate_expr.reduce = False 614 obj.query.add_aggregate(aggregate_expr, self.model) 615 616 return obj 617 550 618 def order_by(self, *field_names): 551 619 """ 552 620 Returns a new QuerySet instance with the ordering changed. … … 635 703 """ 636 704 pass 637 705 638 639 706 class ValuesQuerySet(QuerySet): 640 707 def __init__(self, *args, **kwargs): 641 708 super(ValuesQuerySet, self).__init__(*args, **kwargs) … … 650 717 len(self.field_names) != len(self.model._meta.fields)): 651 718 self.query.trim_extra_select(self.extra_names) 652 719 names = self.query.extra_select.keys() + self.field_names 720 names.extend([x.aliased_name for x in self.query.select 721 if isinstance(x, Aggregate)]) 722 aggregate_start = len(self._fields) or len(self.model._meta.fields) 723 653 724 for row in self.query.results_iter(): 654 yield dict(zip(names, row)) 725 normalized_row = list(row) 726 for i in range(aggregate_start, len(normalized_row)): 727 normalized_row[i] = _value_or_object(normalized_row[i]) 655 728 729 num_fields = len(self.model._meta.fields) 730 has_grouping = (len(row) > aggregate_start and 731 len(self.field_names) < num_fields and 732 len(self.query.group_by) < num_fields) 733 734 #Grouped objects QuerySet 735 if (hasattr(self, 'return_groups') and self.return_groups): 736 restrictions = dict(zip(names, normalized_row[:aggregate_start])) 737 group_query = self.model.objects.filter(**restrictions) 738 yield (dict(zip(names, normalized_row)), group_query) 739 else: 740 yield dict(zip(names, normalized_row)) 741 656 742 def _setup_query(self): 657 743 """ 658 744 Constructs the field_names list that the values query will be … … 660 746 661 747 Called by the _clone() method after initializing the rest of the 662 748 instance. 663 """ 749 """ 664 750 self.extra_names = [] 665 751 if self._fields: 666 752 if not self.query.extra_select: … … 676 762 # Default to all fields. 677 763 field_names = [f.attname for f in self.model._meta.fields] 678 764 679 self.query.add_fields(field_names, False )765 self.query.add_fields(field_names, False, rebuild=True) 680 766 self.query.default_cols = False 681 767 self.field_names = field_names 682 768 -
django/db/models/query_utils.py
65 65 return False 66 66 return True 67 67 68 def _value_or_object(obj): 69 try: 70 return float(obj) 71 except: 72 return obj -
django/db/backends/mysql/base.py
110 110 class DatabaseFeatures(BaseDatabaseFeatures): 111 111 empty_fetchmany_value = () 112 112 update_can_self_select = False 113 allows_group_by_pk = True 113 114 related_fields_match_type = True 114 115 115 116 class DatabaseOperations(BaseDatabaseOperations): -
django/db/backends/__init__.py
62 62 return util.CursorDebugWrapper(cursor, self) 63 63 64 64 class BaseDatabaseFeatures(object): 65 allows_group_by_pk = False 65 66 # True if django.db.backend.utils.typecast_timestamp is used on values 66 67 # returned from dates() calls. 67 68 needs_datetime_string_cast = True -
tests/modeltests/aggregation/fixtures/initial_data.json
1 [ 2 { 3 "pk": 1, 4 "model": "aggregation.publisher", 5 "fields": { 6 "name": "Apress ", 7 "num_awards": 3 8 } 9 }, 10 { 11 "pk": 2, 12 "model": "aggregation.publisher", 13 "fields": { 14 "name": "Sams", 15 "num_awards": 1 16 } 17 }, 18 { 19 "pk": 3, 20 "model": "aggregation.publisher", 21 "fields": { 22 "name": "Prentice Hall", 23 "num_awards": 7 24 } 25 }, 26 { 27 "pk": 4, 28 "model": "aggregation.publisher", 29 "fields": { 30 "name": "Morgan Kaufmann", 31 "num_awards": 9 32 } 33 }, 34 { 35 "pk": 1, 36 "model": "aggregation.book", 37 "fields": { 38 "publisher": 1, 39 "isbn": "159059725", 40 "name": "The Definitive Guide to Django: Web Development Done Right", 41 "price": 30.0, 42 "authors": [ 43 1, 44 2 45 ], 46 "pages": 447 47 } 48 }, 49 { 50 "pk": 2, 51 "model": "aggregation.book", 52 "fields": { 53 "publisher": 2, 54 "isbn": "067232959", 55 "name": "Sams Teach Yourself Django in 24 Hours", 56 "price": 23.09, 57 "authors": [ 58 3 59 ], 60 "pages": 528 61 } 62 }, 63 { 64 "pk": 3, 65 "model": "aggregation.book", 66 "fields": { 67 "publisher": 1, 68 "isbn": "159059996", 69 "name": "Practical Django Projects", 70 "price": 29.69, 71 "authors": [ 72 4 73 ], 74 "pages": 300 75 } 76 }, 77 { 78 "pk": 4, 79 "model": "aggregation.book", 80 "fields": { 81 "publisher": 3, 82 "isbn": "013235613", 83 "name": "Python Web Development with Django", 84 "price": 29.69, 85 "authors": [ 86 5, 87 6, 88 7 89 ], 90 "pages": 350 91 } 92 }, 93 { 94 "pk": 5, 95 "model": "aggregation.book", 96 "fields": { 97 "publisher": 3, 98 "isbn": "013790395", 99 "name": "Artificial Intelligence: A Modern Approach", 100 "price": 82.8, 101 "authors": [ 102 8, 103 9 104 ], 105 "pages": 1132 106 } 107 }, 108 { 109 "pk": 6, 110 "model": "aggregation.book", 111 "fields": { 112 "publisher": 4, 113 "isbn": "155860191", 114 "name": "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp", 115 "price": 75.0, 116 "authors": [ 117 8 118 ], 119 "pages": 946 120 } 121 }, 122 { 123 "pk": 1, 124 "model": "aggregation.store", 125 "fields": { 126 "books": [ 127 1, 128 2, 129 3, 130 4, 131 5, 132 6 133 ], 134 "name": "Amazon.com" 135 } 136 }, 137 { 138 "pk": 2, 139 "model": "aggregation.store", 140 "fields": { 141 "books": [ 142 1, 143 3, 144 5, 145 6 146 ], 147 "name": "Books.com" 148 } 149 }, 150 { 151 "pk": 3, 152 "model": "aggregation.store", 153 "fields": { 154 "books": [ 155 3, 156 4, 157 6 158 ], 159 "name": "Mamma and Pappa's Books" 160 } 161 }, 162 { 163 "pk": 1, 164 "model": "aggregation.author", 165 "fields": { 166 "age": 34, 167 "friends": [ 168 2, 169 4 170 ], 171 "name": "Adrian Holovaty" 172 } 173 }, 174 { 175 "pk": 2, 176 "model": "aggregation.author", 177 "fields": { 178 "age": 35, 179 "friends": [ 180 1, 181 7 182 ], 183 "name": "Jacob Kaplan-Moss" 184 } 185 }, 186 { 187 "pk": 3, 188 "model": "aggregation.author", 189 "fields": { 190 "age": 45, 191 "friends": [], 192 "name": "Brad Dayley" 193 } 194 }, 195 { 196 "pk": 4, 197 "model": "aggregation.author", 198 "fields": { 199 "age": 29, 200 "friends": [ 201 1 202 ], 203 "name": "James Bennett" 204 } 205 }, 206 { 207 "pk": 5, 208 "model": "aggregation.author", 209 "fields": { 210 "age": 37, 211 "friends": [ 212 6, 213 7 214 ], 215 "name": "Jeffrey Forcier " 216 } 217 }, 218 { 219 "pk": 6, 220 "model": "aggregation.author", 221 "fields": { 222 "age": 29, 223 "friends": [ 224 5, 225 7 226 ], 227 "name": "Paul Bissex" 228 } 229 }, 230 { 231 "pk": 7, 232 "model": "aggregation.author", 233 "fields": { 234 "age": 25, 235 "friends": [ 236 2, 237 5, 238 6 239 ], 240 "name": "Wesley J. Chun" 241 } 242 }, 243 { 244 "pk": 8, 245 "model": "aggregation.author", 246 "fields": { 247 "age": 57, 248 "friends": [ 249 9 250 ], 251 "name": "Peter Norvig" 252 } 253 }, 254 { 255 "pk": 9, 256 "model": "aggregation.author", 257 "fields": { 258 "age": 46, 259 "friends": [ 260 8 261 ], 262 "name": "Stuart Russell" 263 } 264 } 265 ] -
tests/modeltests/aggregation/models.py
1 # coding: utf-8 2 from django.db import models 3 4 class Author(models.Model): 5 name = models.CharField(max_length=100) 6 age = models.IntegerField() 7 friends = models.ManyToManyField('self', blank=True) 8 9 def __unicode__(self): 10 return self.name 11 12 class Publisher(models.Model): 13 name = models.CharField(max_length=300) 14 num_awards = models.IntegerField() 15 16 def __unicode__(self): 17 return self.name 18 19 class Book(models.Model): 20 isbn = models.CharField(max_length=9) 21 name = models.CharField(max_length=300) 22 pages = models.IntegerField() 23 price = models.FloatField() 24 authors = models.ManyToManyField(Author) 25 publisher = models.ForeignKey(Publisher) 26 27 def __unicode__(self): 28 return self.name 29 30 class Store(models.Model): 31 name = models.CharField(max_length=300) 32 books = models.ManyToManyField(Book) 33 34 def __unicode__(self): 35 return self.name 36 37 class Entries(models.Model): 38 EntryID = models.AutoField(primary_key=True, db_column='Entry ID') 39 Entry = models.CharField(unique=True, max_length=50) 40 Exclude = models.BooleanField() 41 42 class Clues(models.Model): 43 ID = models.AutoField(primary_key=True) 44 EntryID = models.ForeignKey(Entries, verbose_name='Entry', db_column = 'Entry ID') 45 Clue = models.CharField(max_length=150) 46 47 # Tests on 'aggergate' 48 # Different backends and numbers. 49 __test__ = {'API_TESTS': """ 50 >>> from django.core import management 51 52 # Reset the database representation of this app. 53 # This will return the database to a clean initial state. 54 >>> management.call_command('flush', verbosity=0, interactive=False) 55 56 # Empty Call 57 >>> Author.objects.all().aggregate() 58 {} 59 60 >>> from django.db.aggregates import Avg, Sum, Count, Max, Min 61 62 # Note that rounding of floating points is being used for the tests to 63 # pass for all backends 64 65 # Single model aggregation 66 # 67 68 # Simple 69 # Average Author age 70 >>> Author.objects.all().aggregate(Avg('age')) 71 {'age__avg': 37.4...} 72 73 # Multiple 74 # Average and Sum of Author's age 75 >>> Author.objects.all().aggregate(Sum('age'), Avg('age')) 76 {'age__sum': 337.0, 'age__avg': 37.4...} 77 78 # After aplying other modifiers 79 # Sum of the age of those older than 29 years old 80 >>> Author.objects.all().filter(age__gt=29).aggregate(Sum('age')) 81 {'age__sum': 254.0} 82 83 # Depth-1 Joins 84 # 85 86 # On Relationships with self 87 # Average age of those with friends (not exactelly. 88 # That would be: Author.objects.all().exclude(friends=None).aggregate(Avg('age'))) 89 >>> Author.objects.all().aggregate(Avg('friends__age')) 90 {'friends__age__avg': 34.07...} 91 92 # On ManyToMany Relationships 93 # 94 95 # Forward 96 # Average age of the Authors of Books that cost less than 50 USD 97 >>> Book.objects.all().filter(price__lt=50).aggregate(Avg('authors__age')) 98 {'authors__age__avg': 33.42...} 99 100 101 # Backward 102 # Average price of the Books whose Author's name contains the letter 'a' 103 >>> Author.objects.all().filter(name__contains='a').aggregate(Avg('book__price')) 104 {'book__price__avg': 37.54...} 105 106 # On OneToMany Relationships 107 # 108 109 # Forward 110 # Sum of the number of awards of each Book's Publisher 111 >>> Book.objects.all().aggregate(Sum('publisher__num_awards')) 112 {'publisher__num_awards__sum': 30.0} 113 114 # Backward 115 # Sum of the price of every Book that has a Publisher 116 >>> Publisher.objects.all().aggregate(Sum('book__price')) 117 {'book__price__sum': 270.269...} 118 119 # Multiple Joins 120 # 121 122 #Forward 123 >>> Store.objects.all().aggregate(Max('books__authors__age')) 124 {'books__authors__age__max': 57.0} 125 126 #Backward 127 >>> Author.objects.all().aggregate(Min('book__publisher__num_awards')) 128 {'book__publisher__num_awards__min': 1.0} 129 130 # You can also use aliases. 131 # 132 133 # Average amazon.com Book price 134 >>> Store.objects.filter(name='Amazon.com').aggregate(amazon_mean=Avg('books__price')) 135 {'amazon_mean': 45.04...} 136 137 # Tests on annotate() 138 # 139 140 # An empty annotate call does nothing but return the same QuerySet 141 >>> Book.objects.all().annotate().order_by('pk') 142 [<Book: The Definitive Guide to Django: Web Development Done Right>, <Book: Sams Teach Yourself Django in 24 Hours>, <Book: Practical Django Projects>, <Book: Python Web Development with Django>, <Book: Artificial Intelligence: A Modern Approach>, <Book: Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp>] 143 144 #Annotate inserts the alias into the model object with the aggregated result 145 >>> books = Book.objects.all().annotate(mean_age=Avg('authors__age')) 146 >>> books.get(pk=1).name 147 u'The Definitive Guide to Django: Web Development Done Right' 148 149 >>> books.get(pk=1).mean_age 150 34.5 151 152 #Calls to values() are not commutative over annotate(). 153 154 #Calling values on a queryset that has annotations returns the output 155 #as a dictionary 156 >>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values() 157 [{'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price': 30.0, 'id': 1, 'publisher_id': 1, 'pages': 447, 'mean_age': 34.5}] 158 159 #Calling it with paramters reduces the output but does not remove the 160 #annotation. 161 >>> Book.objects.filter(pk=1).annotate(mean_age=Avg('authors__age')).values('name') 162 [{'name': u'The Definitive Guide to Django: Web Development Done Right', 'mean_age': 34.5}] 163 164 #An empty values() call before annotating has the same effect as an 165 #empty values() call after annotating 166 >>> Book.objects.filter(pk=1).values().annotate(mean_age=Avg('authors__age')) 167 [{'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price': 30.0, 'id': 1, 'publisher_id': 1, 'pages': 447, 'mean_age': 34.5}] 168 169 #Calling annotate() on a ValuesQuerySet annotates over the groups of 170 #fields to be selected by the ValuesQuerySet. 171 172 #Note that an extra parameter is added to each dictionary. This 173 #parameter is a queryset representing the objects that have been 174 #grouped to generate the annotation 175 176 >>> Book.objects.all().values('price').annotate(number=Count('authors__id'), mean_age=Avg('authors__age')).order_by('price') 177 [{'price': 23.09, 'number': 1.0, 'mean_age': 45.0}, {'price': 29.690000000000001, 'number': 4.0, 'mean_age': 30.0}, {'price': 30.0, 'number': 2.0, 'mean_age': 34.5}, {'price': 75.0, 'number': 1.0, 'mean_age': 57.0}, {'price': 82.799999999999997, 'number': 2.0, 'mean_age': 51.5}] 178 179 180 #Notice that the output includes all Authors but the value of the aggregation 181 #is 0 for those that have no friends. 182 #(consider having a neutral ('zero') element for each operation) 183 >>> authors = Author.objects.all().annotate(Avg('friends__age')).order_by('id') 184 >>> len(authors) 185 9 186 >>> for i in authors: 187 ... print i.name, i.friends__age__avg 188 ... 189 Adrian Holovaty 32.0 190 Jacob Kaplan-Moss 29.5 191 Brad Dayley None 192 James Bennett 34.0 193 Jeffrey Forcier 27.0 194 Paul Bissex 31.0 195 Wesley J. Chun 33.66... 196 Peter Norvig 46.0 197 Stuart Russell 57.0 198 199 #The Count aggregation function allows an extra parameter: distinct. 200 # 201 >>> Book.objects.all().aggregate(Count('price')) 202 {'price__count': 6.0} 203 204 >>> Book.objects.all().aggregate(Count('price', distinct=True)) 205 {'price__count': 5.0} 206 207 #Retreiving the grouped objects 208 209 210 #When using Count you can also ommit the primary key and refer only to 211 #the related field name if you want to count all the related objects 212 #and not a specific column 213 >>> explicit = list(Author.objects.annotate(Count('book__id'))) 214 >>> implicit = list(Author.objects.annotate(Count('book'))) 215 >>> explicit == implicit 216 True 217 218 ## 219 # Ordering is allowed on aggregates 220 >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest') 221 [{'price': 30.0, 'oldest': 35.0}, {'price': 29.6..., 'oldest': 37.0}, {'price': 23.09, 'oldest': 45.0}, {'price': 75.0, 'oldest': 57.0}, {'price': 82.7..., 'oldest': 57.0}] 222 223 >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('-oldest') 224 [{'price': 75.0, 'oldest': 57.0}, {'price': 82.7..., 'oldest': 57.0}, {'price': 23.09, 'oldest': 45.0}, {'price': 29.6..., 'oldest': 37.0}, {'price': 30.0, 'oldest': 35.0}] 225 226 >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('-oldest', 'price') 227 [{'price': 75.0, 'oldest': 57.0}, {'price': 82.7..., 'oldest': 57.0}, {'price': 23.09, 'oldest': 45.0}, {'price': 29.6..., 'oldest': 37.0}, {'price': 30.0, 'oldest': 35.0}] 228 229 >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('-oldest', '-price') 230 [{'price': 82.7..., 'oldest': 57.0}, {'price': 75.0, 'oldest': 57.0}, {'price': 23.09, 'oldest': 45.0}, {'price': 29.6..., 'oldest': 37.0}, {'price': 30.0, 'oldest': 35.0}] 231 232 # It is possible to aggregate over anotated values 233 # 234 >>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Avg('num_authors')) 235 {'num_authors__avg': 1.66...} 236 237 # You can filter the results based on the aggregation alias. 238 # 239 240 #Lets add a publisher to test the different possibilities for filtering 241 >>> p = Publisher(name='Expensive Publisher', num_awards=0) 242 >>> p.save() 243 >>> Book(name='ExpensiveBook1', pages=1, isbn='111', price=1000, publisher=p).save() 244 >>> Book(name='ExpensiveBook2', pages=1, isbn='222', price=1000, publisher=p).save() 245 >>> Book(name='ExpensiveBook3', pages=1, isbn='333', price=35, publisher=p).save() 246 247 #Consider the following queries: 248 249 #Publishers that have: 250 251 #(i) more than one book 252 >>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk') 253 [<Publisher: Apress >, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 254 255 #(ii) a book that cost less than 40 256 >>> Publisher.objects.filter(book__price__lt=40).order_by('pk') 257 [<Publisher: Apress >, <Publisher: Apress >, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 258 259 #(iii) more than one book and (at least) a book that cost less than 40 260 >>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1, book__price__lt=40).order_by('pk') 261 [<Publisher: Apress >, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 262 263 #(iv) more than one book that costs less than 40 264 >>> Publisher.objects.filter(book__price__lt=40).annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk') 265 [<Publisher: Apress >] 266 267 # Now a bit of testing on the different lookup types 268 # 269 270 >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__range=[1, 3]).order_by('pk') 271 [<Publisher: Apress >, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Morgan Kaufmann>, <Publisher: Expensive Publisher>] 272 273 >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__range=[1, 2]).order_by('pk') 274 [<Publisher: Apress >, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Morgan Kaufmann>] 275 276 >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__in=[1, 3]).order_by('pk') 277 [<Publisher: Sams>, <Publisher: Morgan Kaufmann>, <Publisher: Expensive Publisher>] 278 279 >>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__isnull=True) 280 [] 281 282 >>> p.delete() 283 284 # Community tests 285 # 286 287 #Thanks to Russell for the following set 288 # 289 290 #Does Author X have any friends? (or better, how many friends does author X have) 291 >> Author.objects.filter(pk=1).aggregate(Count('friends__id')) 292 {'friends__id__count': 2.0} 293 294 #Give me a list of all Books with more than 1 authors 295 >>> Book.objects.all().annotate(num_authors=Count('authors__name')).filter(num_authors__ge=2).order_by('pk') 296 [<Book: The Definitive Guide to Django: Web Development Done Right>, <Book: Artificial Intelligence: A Modern Approach>] 297 298 #Give me a list of all Authors that have no friends 299 >>> Author.objects.all().annotate(num_friends=Count('friends__id', distinct=True)).filter(num_friends=0).order_by('pk') 300 [<Author: Brad Dayley>] 301 302 #Give me a list of all publishers that have published more than 1 books 303 >>> Publisher.objects.all().annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk') 304 [<Publisher: Apress >, <Publisher: Prentice Hall>] 305 306 #Give me a list of all publishers that have published more than 1 books that cost less than 30 307 #>>> Publisher.objects.all().filter(book__price__lt=40).annotate(num_books=Count('book__id')).filter(num_books__gt=1) 308 [<Publisher: Apress >] 309 310 #Give me a list of all Books that were written by X and one other author. 311 >>> Book.objects.all().annotate(num_authors=Count('authors__id')).filter(authors__name__contains='Norvig', num_authors__gt=1) 312 [<Book: Artificial Intelligence: A Modern Approach>] 313 314 #Give me the average price of all Books that were written by X and one other author. 315 #(Aggregate over objects discovered using membership of the m2m set) 316 317 #Adding an existing author to another book to test it the right way 318 >>> a = Author.objects.get(name__contains='Norvig') 319 >>> b = Book.objects.get(name__contains='Done Right') 320 >>> b.authors.add(a) 321 >>> b.save() 322 323 #This should do it 324 >>> Book.objects.all().annotate(num_authors=Count('authors__id')).filter(authors__name__contains='Norvig', num_authors__gt=1).aggregate(Avg('price')) 325 {'price__avg': 56.39...} 326 >>> b.authors.remove(a) 327 328 # 329 # --- Just one of the hard ones left --- 330 # 331 332 #Give me a list of all Authors that have published a book with at least one other person 333 #(Filters over a count generated on a related object) 334 # 335 # Cheating: [a for a in Author.objects.all().annotate(num_coleagues=Count('book__authors__id'), num_books=Count('book__id', distinct=True)) if a.num_coleagues - a.num_books > 0] 336 # F-Syntax is required. Will be fixed after F objects are available 337 338 339 #Thanks to Karen for the following set 340 # Tests on fields with different names and spaces. (but they work =) ) 341 342 >>> Clues.objects.values('EntryID__Entry').annotate(Appearances=Count('EntryID'), Distinct_Clues=Count('Clue', distinct=True)) 343 [] 344 345 """} -
tests/regressiontests/aggregation_regress/fixtures/initial_data.json
1 [ 2 { 3 "pk": 1, 4 "model": "aggregation_regress.publisher", 5 "fields": { 6 "name": "Apress ", 7 "num_awards": 3 8 } 9 }, 10 { 11 "pk": 2, 12 "model": "aggregation_regress.publisher", 13 "fields": { 14 "name": "Sams", 15 "num_awards": 1 16 } 17 }, 18 { 19 "pk": 3, 20 "model": "aggregation_regress.publisher", 21 "fields": { 22 "name": "Prentice Hall", 23 "num_awards": 7 24 } 25 }, 26 { 27 "pk": 4, 28 "model": "aggregation_regress.publisher", 29 "fields": { 30 "name": "Morgan Kaufmann", 31 "num_awards": 9 32 } 33 }, 34 { 35 "pk": 1, 36 "model": "aggregation_regress.book", 37 "fields": { 38 "publisher": 1, 39 "isbn": "159059725", 40 "name": "The Definitive Guide to Django: Web Development Done Right", 41 "price": 30.0, 42 "authors": [ 43 1, 44 2 45 ], 46 "pages": 447 47 } 48 }, 49 { 50 "pk": 2, 51 "model": "aggregation_regress.book", 52 "fields": { 53 "publisher": 2, 54 "isbn": "067232959", 55 "name": "Sams Teach Yourself Django in 24 Hours", 56 "price": 23.09, 57 "authors": [ 58 3 59 ], 60 "pages": 528 61 } 62 }, 63 { 64 "pk": 3, 65 "model": "aggregation_regress.book", 66 "fields": { 67 "publisher": 1, 68 "isbn": "159059996", 69 "name": "Practical Django Projects", 70 "price": 29.69, 71 "authors": [ 72 4 73 ], 74 "pages": 300 75 } 76 }, 77 { 78 "pk": 4, 79 "model": "aggregation_regress.book", 80 "fields": { 81 "publisher": 3, 82 "isbn": "013235613", 83 "name": "Python Web Development with Django", 84 "price": 29.69, 85 "authors": [ 86 5, 87 6, 88 7 89 ], 90 "pages": 350 91 } 92 }, 93 { 94 "pk": 5, 95 "model": "aggregation_regress.book", 96 "fields": { 97 "publisher": 3, 98 "isbn": "013790395", 99 "name": "Artificial Intelligence: A Modern Approach", 100 "price": 82.8, 101 "authors": [ 102 8, 103 9 104 ], 105 "pages": 1132 106 } 107 }, 108 { 109 "pk": 6, 110 "model": "aggregation_regress.book", 111 "fields": { 112 "publisher": 4, 113 "isbn": "155860191", 114 "name": "Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp", 115 "price": 75.0, 116 "authors": [ 117 8 118 ], 119 "pages": 946 120 } 121 }, 122 { 123 "pk": 1, 124 "model": "aggregation_regress.store", 125 "fields": { 126 "books": [ 127 1, 128 2, 129 3, 130 4, 131 5, 132 6 133 ], 134 "name": "Amazon.com" 135 } 136 }, 137 { 138 "pk": 2, 139 "model": "aggregation_regress.store", 140 "fields": { 141 "books": [ 142 1, 143 3, 144 5, 145 6 146 ], 147 "name": "Books.com" 148 } 149 }, 150 { 151 "pk": 3, 152 "model": "aggregation_regress.store", 153 "fields": { 154 "books": [ 155 3, 156 4, 157 6 158 ], 159 "name": "Mamma and Pappa's Books" 160 } 161 }, 162 { 163 "pk": 1, 164 "model": "aggregation_regress.author", 165 "fields": { 166 "age": 34, 167 "friends": [ 168 2, 169 4 170 ], 171 "name": "Adrian Holovaty" 172 } 173 }, 174 { 175 "pk": 2, 176 "model": "aggregation_regress.author", 177 "fields": { 178 "age": 35, 179 "friends": [ 180 1, 181 7 182 ], 183 "name": "Jacob Kaplan-Moss" 184 } 185 }, 186 { 187 "pk": 3, 188 "model": "aggregation_regress.author", 189 "fields": { 190 "age": 45, 191 "friends": [], 192 "name": "Brad Dayley" 193 } 194 }, 195 { 196 "pk": 4, 197 "model": "aggregation_regress.author", 198 "fields": { 199 "age": 29, 200 "friends": [ 201 1 202 ], 203 "name": "James Bennett" 204 } 205 }, 206 { 207 "pk": 5, 208 "model": "aggregation_regress.author", 209 "fields": { 210 "age": 37, 211 "friends": [ 212 6, 213 7 214 ], 215 "name": "Jeffrey Forcier " 216 } 217 }, 218 { 219 "pk": 6, 220 "model": "aggregation_regress.author", 221 "fields": { 222 "age": 29, 223 "friends": [ 224 5, 225 7 226 ], 227 "name": "Paul Bissex" 228 } 229 }, 230 { 231 "pk": 7, 232 "model": "aggregation_regress.author", 233 "fields": { 234 "age": 25, 235 "friends": [ 236 2, 237 5, 238 6 239 ], 240 "name": "Wesley J. Chun" 241 } 242 }, 243 { 244 "pk": 8, 245 "model": "aggregation_regress.author", 246 "fields": { 247 "age": 57, 248 "friends": [ 249 9 250 ], 251 "name": "Peter Norvig" 252 } 253 }, 254 { 255 "pk": 9, 256 "model": "aggregation_regress.author", 257 "fields": { 258 "age": 46, 259 "friends": [ 260 8 261 ], 262 "name": "Stuart Russell" 263 } 264 } 265 ] -
tests/regressiontests/aggregation_regress/models.py
1 # coding: utf-8 2 from django.db import models 3 4 class Author(models.Model): 5 name = models.CharField(max_length=100) 6 age = models.IntegerField() 7 friends = models.ManyToManyField('self', blank=True) 8 9 def __unicode__(self): 10 return self.name 11 12 class Admin: 13 pass 14 15 class Publisher(models.Model): 16 name = models.CharField(max_length=300) 17 num_awards = models.IntegerField() 18 19 def __unicode__(self): 20 return self.name 21 22 class Admin: 23 pass 24 25 class Book(models.Model): 26 isbn = models.CharField(max_length=9) 27 name = models.CharField(max_length=300) 28 pages = models.IntegerField() 29 price = models.FloatField() 30 authors = models.ManyToManyField(Author) 31 publisher = models.ForeignKey(Publisher) 32 33 def __unicode__(self): 34 return self.name 35 36 class Admin: 37 pass 38 39 class Store(models.Model): 40 name = models.CharField(max_length=300) 41 books = models.ManyToManyField(Book) 42 43 def __unicode__(self): 44 return self.name 45 46 class Admin: 47 pass 48 49 #Extra does not play well with values. Modify the tests if/when this is fixed. 50 __test__ = {'API_TESTS': """ 51 >>> from django.core import management 52 >>> from django.db.models import get_app 53 54 # Reset the database representation of this app. 55 # This will return the database to a clean initial state. 56 >>> management.call_command('flush', verbosity=0, interactive=False) 57 58 >>> from django.db.aggregates import Avg, Sum, Count, Max, Min 59 60 >>> Book.objects.all().aggregate(Sum('pages'), Avg('pages')) 61 {'pages__sum': 3703.0, 'pages__avg': 617.1...} 62 63 >>> Book.objects.all().values().aggregate(Sum('pages'), Avg('pages')) 64 {'pages__sum': 3703.0, 'pages__avg': 617.1...} 65 66 >>> Book.objects.all().extra(select={'price_per_page' : 'price / pages'}).aggregate(Sum('pages')) 67 {'pages__sum': 3703.0} 68 69 >>> Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1).__dict__ 70 {'mean_auth_age': 34.5, 'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price_per_page': 0.067..., 'price': 30.0, 'id': 1, 'publisher_id': 1, 'pages': 447} 71 72 >>> Book.objects.all().extra(select={'price_per_page' : 'price / pages'}).annotate(mean_auth_age=Avg('authors__age')).get(pk=1).__dict__ 73 {'mean_auth_age': 34.5, 'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price_per_page': 0.067..., 'price': 30.0, 'id': 1, 'publisher_id': 1, 'pages': 447} 74 75 >>> Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values().get(pk=1) 76 {'mean_auth_age': 34.5, 'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price_per_page': 0.067..., 'price': 30.0, 'id': 1, 'publisher_id': 1.0, 'pages': 447} 77 78 >>> Book.objects.all().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).values('name').get(pk=1) 79 {'mean_auth_age': 34.5, 'name': u'The Definitive Guide to Django: Web Development Done Right'} 80 81 >>> Book.objects.all().values().annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1) 82 {'mean_auth_age': 34.5, 'isbn': u'159059725', 'name': u'The Definitive Guide to Django: Web Development Done Right', 'price_per_page': 0.067..., 'price': 30.0, 'id': 1, 'publisher_id': 1.0, 'pages': 447} 83 84 >>> Book.objects.all().values('name').annotate(mean_auth_age=Avg('authors__age')).extra(select={'price_per_page' : 'price / pages'}).get(pk=1) 85 {'mean_auth_age': 34.5, 'name': u'The Definitive Guide to Django: Web Development Done Right'} 86 87 #Check that all of the objects are getting counted (allow_nulls) and that values respects the amount of objects 88 >>> len(Author.objects.all().annotate(Avg('friends__age')).values()) 89 9 90 91 #Check that consecutive calls to annotate dont break group by 92 >>> Book.objects.values('price').annotate(oldest=Max('authors__age')).order_by('oldest').annotate(Max('publisher__num_awards')) 93 [{'price': 30.0, 'oldest': 35.0, 'publisher__num_awards__max': 3.0}, {'price': 29.69..., 'oldest': 37.0, 'publisher__num_awards__max': 7.0}, {'price': 23.09, 'oldest': 45.0, 'publisher__num_awards__max': 1.0}, {'price': 75.0, 'oldest': 57.0, 'publisher__num_awards__max': 9.0}, {'price': 82.7..., 'oldest': 57.0, 'publisher__num_awards__max': 7.0}] 94 95 #Checks fixed bug with multiple aggregate objects in the aggregate call 96 >>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('price'), Sum('num_authors')) 97 {'num_authors__sum': 10.0, 'price__max': 82.7...} 98 99 """ 100 } -
tests/regressiontests/queries/models.py
582 582 # An empty values() call includes all aliases, including those from an extra() 583 583 >>> dicts = qs.values().order_by('id') 584 584 >>> [sorted(d.items()) for d in dicts] 585 [[('author_id', 2 ), ('good', 0), ('id', 1), ('rank', 2)], [('author_id', 3), ('good', 0), ('id', 2), ('rank', 1)], [('author_id', 1), ('good', 1), ('id', 3), ('rank', 3)]]585 [[('author_id', 2.0), ('good', 0), ('id', 1), ('rank', 2)], [('author_id', 3.0), ('good', 0), ('id', 2), ('rank', 1)], [('author_id', 1.0), ('good', 1), ('id', 3), ('rank', 3)]] 586 586 587 587 Bugs #2874, #3002 588 588 >>> qs = Item.objects.select_related().order_by('note__note', 'name') … … 953 953 >>> len([x[2] for x in q.alias_map.values() if x[2] == q.LOUTER and q.alias_refcount[x[1]]]) 954 954 1 955 955 956 A check to ensure we don't break the internal query construction of GROUP BY957 and HAVING. These aren't supported in the public API, but the Query class knows958 about them and shouldn't do bad things.959 >>> qs = Tag.objects.values_list('parent_id', flat=True).order_by()960 >>> qs.query.group_by = ['parent_id']961 >>> qs.query.having = ['count(parent_id) > 1']962 >>> expected = [t3.parent_id, t4.parent_id]963 >>> expected.sort()964 >>> result = list(qs)965 >>> result.sort()966 >>> expected == result967 True968 969 956 """} 970 957 971 958 # In Python 2.3 and the Python 2.6 beta releases, exceptions raised in __len__ -
AUTHORS
30 30 AgarFu <heaven@croasanaso.sytes.net> 31 31 Dagur Páll Ammendrup <dagurp@gmail.com> 32 32 Collin Anderson <cmawebsite@gmail.com> 33 Nicolas Lara <nicolaslara@gmail.com> 33 34 Jeff Anderson <jefferya@programmerq.net> 34 35 Andreas 35 36 andy@jadedplanet.net -
docs/aggregation.txt
1 ============= 2 Aggregation 3 ============= 4 5 **New in Django development version** 6 7 Aggergation works on top of an existing QuerySet allowing you to do 8 calculations over sets of objects at the database level. 9 10 The calculations to be retrieved are expressed using Aggregate 11 objects. 12 13 Aggregate objects 14 ================= 15 16 Aggregate objects define the correspondance between the lookup being 17 done in ORM syntax and the query that is executed in the backend. 18 19 All Aggregate objects take the field to be aggregated upon as a string 20 and an optional alias to represent the calculation in the result. 21 22 Field representations are done in the same way as in field 23 lookups. For example:: 24 25 Max(total_price='price') 26 27 would represent the maximum price of the selected objects in the model 28 aggregated upon and it would be refered to as "total_price" while:: 29 30 Avg(mean_age='friends__age') 31 32 would represent the maximum of the related field for all the objects 33 related to the objects in the model aggregated upon, likewise refered 34 to as "mean_age". 35 36 There are many possible ways to use aggregation so the actual results 37 of different lookups will become clearer in the documentation for 38 ``aggregate()`` and ``annotate()``. 39 40 If the alias is not present a default alias is defined according to 41 each Aggregate object. So:: 42 43 Min('friend__height') 44 45 would be refered as "friend__height__max". 46 47 For every aggregate object that spans multiple models, if the field of 48 the related model to be used in the aggregation is not specified, the 49 field defaults to the primary key. 50 51 Aggregate objects are located at ``django.db.aggregates``. Every 52 aggregate object is a subclass of ``Aggregate``. A empty subclass of 53 aggregate called Func could be used like this:: 54 55 Func('field') 56 57 and have the following SQL equivalent::: 58 59 SELECT FUNC(field) as field__func ... 60 61 The following Aggregate subclases are pre-defined: 62 63 Max 64 --- 65 66 Calculates the maximum on the given field. 67 68 Default alias: ``field__max`` 69 70 Min 71 --- 72 73 Calculates the minimum on the given field. 74 75 Default alias: ``field__min`` 76 77 Avg 78 --- 79 80 Calculates the average on the given field. 81 82 Default alias: ``field__avg`` 83 84 Sum 85 --- 86 87 Calculates the sumation on the given field. 88 89 Default alias: ``field__sum`` 90 91 Count 92 ----- 93 94 Counts the objects in which the field is not "Null". For counting 95 regardles of the field please refer to `count()`_. 96 97 Count takes an optional parameter: *distinct*. 98 99 Distinct, if True, reduces the output counting repetitions on a field only once. 100 101 If distinct is True 102 103 Count(field, distinct=True) 104 105 has the SQL equivalent: 106 107 COUNT(DISTINCT field) 108 109 otherwise it is: 110 111 COUNT(field) 112 113 Default alias: ``field__count`` 114 115 .. _count(): ../db-api/#count 116 117 Methods that do aggregation 118 =========================== 119 120 For this section we'll refer to the following models:: 121 122 class Author(models.Model): 123 name = models.CharField(max_length=100) 124 age = models.IntegerField() 125 friends = models.ManyToManyField('self', blank=True) 126 127 class Publisher(models.Model): 128 name = models.CharField(max_length=300) 129 num_awards = models.IntegerField() 130 131 class Book(models.Model): 132 isbn = models.CharField(max_length=9) 133 name = models.CharField(max_length=300) 134 pages = models.IntegerField() 135 price = models.FloatField() 136 authors = models.ManyToManyField(Author) 137 publisher = models.ForeignKey(Publisher) 138 139 class Store(models.Model): 140 name = models.CharField(max_length=300) 141 books = models.ManyToManyField(Book) 142 143 144 aggregate(args, kwargs) 145 ----------------------- 146 147 Returns a dictionary containing the calculations (aggregation) over 148 the current queryset. 149 150 >>> Book.objects.aggregate(Avg('price'), highest_price=Max('price')) 151 {'price__avg': 45.045000000000002, 'highest_price': 82.799999999999997} 152 153 You can also do aggregate lookups on related models. 154 155 >>> Author.objects.aggregate(Sum('book__price')) 156 {'book__price__sum': 442.44999999999999} 157 158 it is important to notice that the previous query reads "The sum of 159 the price of every book for every author". So if a book has many 160 authors its price will be added as many times as authors the book 161 has. If you would be interested, instead, in "the sum of the price for 162 all books" you would need to do a query like this:: 163 164 >>> Book.objects.aggregate(Sum('price')) 165 {'price__sum': 270.26999999999998} 166 167 .. note:: 168 169 It is importante to notice that aggregate() is a terminal 170 clause. This means that it does *not* return a queryset and no 171 other modifiers can be applied after it. 172 173 annotate(args, kwargs) 174 ---------------------- 175 176 Returns a QuerySet extended with the results of the calculations on 177 the given fields. So if you need to retrieve the "age for the oldest 178 author of each book" you could do: 179 180 >>> books = Book.objects.annotate(Max('authors__age')) 181 >>> books[0].name 182 u'Python Web Development With Django' 183 >>> books[0].authors.all() 184 [<Author: Jeffrey Forcier >, <Author: Paul Bissex>, <Author: Wesley J. Chun>] 185 >>> books[0].authors__age__max 186 37.0 187 188 And the output would be the model object extended with the aggregation 189 information. 190 191 grouping 192 ~~~~~~~~ 193 194 Sometimes you want to annotate, not on the whole set of objects but on 195 those that share the same value for some fields. To do this, you 196 appply values() before annotating. For example if you want to retrieve 197 the average author age for the books of the same price you could do:: 198 199 >>> books = Book.objects.values('price').annotate(oldest=Max('authors__age')) 200 >>> for book_group in books: 201 ... print 'price', book_group['price'], 'oldest', book_group['oldest'] 202 ... 203 price 29.69 oldest 37.0 204 price 75.0 oldest 57.0 205 price 82.8 oldest 57.0 206 price 23.09 oldest 45.0 207 price 30.0 oldest 35.0 208 209 Note that aplying values after annotate() does not have the same 210 efect. It reduces the output but no grouping is made: 211 212 >>> books = Book.objects.annotate(Max('authors__age')).values('price') #An entry for every Book 213 >>> for i in books: 214 ... print 'price', i['price'], 'max', i['authors__age__max'] 215 216 price 23.09 max 45.0 217 price 29.69 max 37.0 218 price 75.0 max 57.0 219 price 82.8 max 57.0 220 price 30.0 max 35.0 221 price 29.69 max 29.0 222 223 >>> len(Book.objects.annotate(Max('authors__age')).values('price')) #An entry for every Book 224 6 225 226 >>> len(Book.objects.values('price').annotate(Max('authors__age'))) #Books are grouped by price 227 5 228 229 grouped_objects 230 ~~~~~~~~~~~~~~~ 231 232 Also, after doing an annotation, one might need to recover the 233 elements that were grouped to do the calculation. To do this, the 234 grouped_objects argument to annotate is provided. This argument, if 235 True, changes the output format so the result is a list of tuples 236 containing the values of the grouping and a queryset to retreive the 237 the objects that were grouped. 238 239 This changes the output in a way that for each result there's a tuple 240 containing the result of the aggregation and a queryset to retrieve 241 the objects that were grouped 242 243 >>> books = Book.objects.values('price').annotate(oldest=Max('authors__age'), grouped_objects=True) 244 >>> books[0] 245 ({'price': 29.690000000000001, 'oldest': 37.0}, 246 [<Book: Practical Django Projects>, <Book: Python Web Development with Django>]) 247 248 .. note:: 249 250 As normal querysets the queryset returned by ``grouped_objects`` is 251 lazy and will not be executed until it is evaluated. So if the 252 objects change before evaluating the queryset the aggregated result 253 might not hold. 254 255 256 filtering 257 ~~~~~~~~~ 258 259 Another thing you might need is to retreive only certain objects based 260 on the result of a calculation. To do this the filtering syntax is 261 used on the alias of the annotation. See filter(link) for more 262 information on the lookups. 263 264 There are four different types of filtering that you might be 265 interested in. Each of this have adiferent representation. 266 267 * Simple filtering on the annotations 268 269 an example of this is retreiving the "Publishers that have more than one book" 270 271 >>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk') 272 [<Publisher: Apress >, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 273 274 * Simple filtering on the whole set 275 276 This is a normal, un-related to aggregation, filter. "Publishers that have books that cost les than 40" 277 278 >>> Publisher.objects.filter(book__price__lt=40).order_by('pk') 279 [<Publisher: Apress >, <Publisher: Apress >, <Publisher: Sams>, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 280 281 * Annotationg on the whole set and filtering on annotations 282 283 "Publishers that have more than one book and (at least) a book that cost less than 40" 284 285 >>> Publisher.objects.annotate(num_books=Count('book__id')).filter(num_books__gt=1, book__price__lt=40).order_by('pk') 286 [<Publisher: Apress >, <Publisher: Prentice Hall>, <Publisher: Expensive Publisher>] 287 288 * Filtering and annotating on the whole set 289 290 "Publishers that have more than one book that costs less than 40" 291 292 >>> Publisher.objects.filter(book__price__lt=40).annotate(num_books=Count('book__id')).filter(num_books__gt=1).order_by('pk') 293 [<Publisher: Apress >] 294 295 The reason for this types of filtering to exist is because the 296 filtering results vary depending whether they are done in a join of 297 two models or the single model. When you need the filtering to be done 298 on the model and not the join of two models the filtering must be done 299 before calling the annotation. If, on the contrary, the filtering 300 should be done on the result of the joining it must be done after 301 annotating. 302 303 Aggregating on annotated values 304 ------------------------------- 305 306 It is possible to apply ``aggregate()`` on the result of an annotation 307 that does not gorup objects. Doing this will generate a subquery for 308 the annotated objects and calculate the aggregation on top of it. 309 310 This way, if you wanted to calculate the average number of authors per 311 book you could do:: 312 313 >>> Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Avg('num_authors')) 314 {'num_authors__avg': 1.66...} 315