@@ -306,7 +306,7 @@ def join(self, other, semantic_check=True, left=False, allow_nullable_pk=False):
306306 :param allow_nullable_pk: If True, bypass the left join constraint that requires
307307 self to determine other. When bypassed, the result PK is the union of both
308308 operands' PKs, and PK attributes from the right operand could be NULL.
309- Used internally by aggregation with keep_all_rows=True .
309+ Used internally by aggregation when exclude_nonmatching=False .
310310 :return: The joined QueryExpression
311311
312312 a * b is short for a.join(b)
@@ -538,21 +538,33 @@ def proj(self, *attributes, **named_attributes):
538538 )
539539 return result
540540
541- def aggr (self , group , * attributes , keep_all_rows = False , ** named_attributes ):
541+ def aggr (self , group , * attributes , exclude_nonmatching = False , ** named_attributes ):
542542 """
543- Aggregation of the type U('attr1','attr2').aggr(group, computation="QueryExpression")
544- has the primary key ('attr1','attr2') and performs aggregation computations for all matching elements of `group`.
543+ Aggregation/grouping operation, similar to proj but with computations over a grouped relation.
545544
546- :param group: The query expression to be aggregated.
547- :param keep_all_rows: True=keep all the rows from self. False=keep only rows that match entries in group.
545+ By default, keeps all rows from self (like proj). Use exclude_nonmatching=True to
546+ keep only rows that have matches in group.
547+
548+ :param group: The query expression to be aggregated.
549+ :param exclude_nonmatching: If True, exclude rows from self that have no matching
550+ entries in group (INNER JOIN). Default False keeps all rows (LEFT JOIN).
548551 :param named_attributes: computations of the form new_attribute="sql expression on attributes of group"
549552 :return: The derived query expression
553+
554+ Example::
555+
556+ # Count sessions per subject (keeps all subjects, even those with 0 sessions)
557+ Subject.aggr(Session, n="count(*)")
558+
559+ # Count sessions per subject (only subjects with at least one session)
560+ Subject.aggr(Session, n="count(*)", exclude_nonmatching=True)
550561 """
551562 if Ellipsis in attributes :
552563 # expand ellipsis to include only attributes from the left table
553564 attributes = set (attributes )
554565 attributes .discard (Ellipsis )
555566 attributes .update (self .heading .secondary_attributes )
567+ keep_all_rows = not exclude_nonmatching
556568 return Aggregation .create (self , group = group , keep_all_rows = keep_all_rows ).proj (* attributes , ** named_attributes )
557569
558570 aggregate = aggr # alias for aggr
@@ -1170,12 +1182,14 @@ def aggr(self, group, **named_attributes):
11701182 Aggregation of the type U('attr1','attr2').aggr(group, computation="QueryExpression")
11711183 has the primary key ('attr1','attr2') and performs aggregation computations for all matching elements of `group`.
11721184
1185+ Note: exclude_nonmatching is always True for dj.U (cannot keep all rows from infinite set).
1186+
11731187 :param group: The query expression to be aggregated.
11741188 :param named_attributes: computations of the form new_attribute="sql expression on attributes of group"
11751189 :return: The derived query expression
11761190 """
1177- if named_attributes .get ( "keep_all_rows " , False ) :
1178- raise DataJointError ("Cannot set keep_all_rows=True when aggregating on a universal set." )
1191+ if named_attributes .pop ( "exclude_nonmatching " , True ) is False :
1192+ raise DataJointError ("Cannot set exclude_nonmatching=False when aggregating on a universal set." )
11791193
11801194 if inspect .isclass (group ) and issubclass (group , QueryExpression ):
11811195 group = group ()
0 commit comments