]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Changed the underlying approach to query.count().
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Mar 2011 00:17:45 +0000 (19:17 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 10 Mar 2011 00:17:45 +0000 (19:17 -0500)
query.count() is now in all cases exactly:

query.
    from_self(func.count(literal_column('1'))).
    scalar()

That is, "select count(1) from (<full query>)".
This produces a subquery in all cases, but
vastly simplifies all the guessing count()
tried to do previously, which would still
fail in many scenarios particularly when
joined table inheritance and other joins
were involved.  If the subquery produced
for an otherwise very simple count is really
an issue, use query(func.count()) as an
optimization.  [ticket:2093]

CHANGES
lib/sqlalchemy/orm/query.py
test/orm/inheritance/test_query.py

diff --git a/CHANGES b/CHANGES
index 02dc1cbac2024bd6be86e7145aa3533d6caa0663..093c5f8fe549e8f8fbbd181bdb2408191ebc356c 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -6,6 +6,24 @@ CHANGES
 0.7.0b3
 =======
 - orm
+  - Changed the underlying approach to query.count().
+    query.count() is now in all cases exactly:
+
+        query.
+            from_self(func.count(literal_column('1'))).
+            scalar()
+
+    That is, "select count(1) from (<full query>)".
+    This produces a subquery in all cases, but 
+    vastly simplifies all the guessing count()
+    tried to do previously, which would still
+    fail in many scenarios particularly when
+    joined table inheritance and other joins
+    were involved.  If the subquery produced
+    for an otherwise very simple count is really 
+    an issue, use query(func.count()) as an 
+    optimization.  [ticket:2093]
+
   - some changes to the identity map regarding
     rare weakref callbacks during iterations.
     The mutex has been removed as it apparently 
index 43699b4d9e7d9af2ca6f0233bae628fc96b3d0bf..6f79d7c9c6ff23de3034bdcfb22e56f1f82f7d65 100644 (file)
@@ -435,7 +435,7 @@ class Query(object):
             this is passed through to :meth:`.FromClause.alias`.
             If ``None``, a name will be deterministically generated
             at compile time.
-        
+
 
         """
         return self.enable_eagerloads(False).statement.alias(name=name)
@@ -1080,14 +1080,14 @@ class Query(object):
 
             SELECT * FROM (SELECT * FROM X UNION SELECT * FROM y UNION 
                             SELECT * FROM Z)
-        
+
         Note that many database backends do not allow ORDER BY to
         be rendered on a query called within UNION, EXCEPT, etc.
         To disable all ORDER BY clauses including those configured
         on mappers, issue ``query.order_by(None)`` - the resulting
         :class:`.Query` object will not render ORDER BY within 
         its SELECT statement.
-        
+
         """
 
 
@@ -1612,7 +1612,7 @@ class Query(object):
     def distinct(self, *criterion):
         """Apply a ``DISTINCT`` to the query and return the newly resulting
         ``Query``.
-        
+
         :param \*expr: optional column expressions.  When present,
          the Postgresql dialect will render a ``DISTINCT ON (<expressions>>)``
          construct.
@@ -2035,73 +2035,42 @@ class Query(object):
 
     def count(self):
         """Return a count of rows this Query would return.
+        
+        This generates the SQL for this Query as follows::
+        
+            SELECT count(1) AS count_1 FROM (
+                SELECT <rest of query follows...>
+            ) AS anon_1
 
-        For simple entity queries, count() issues
-        a SELECT COUNT, and will specifically count the primary
-        key column of the first entity only.  If the query uses 
-        LIMIT, OFFSET, or DISTINCT, count() will wrap the statement 
-        generated by this Query in a subquery, from which a SELECT COUNT
-        is issued, so that the contract of "how many rows
-        would be returned?" is honored.
-
-        For queries that request specific columns or expressions, 
-        count() again makes no assumptions about those expressions
-        and will wrap everything in a subquery.  Therefore,
-        ``Query.count()`` is usually not what you want in this case.
-        To count specific columns, often in conjunction with 
-        GROUP BY, use ``func.count()`` as an individual column expression
-        instead of ``Query.count()``.  See the ORM tutorial
-        for an example.
-
+        Note the above scheme is newly refined in 0.7 
+        (as of 0.7b3).
+        
+        For fine grained control over specific columns 
+        to count, to skip the usage of a subquery or
+        otherwise control of the FROM clause,
+        or to use other aggregate functions,
+        use :attr:`.func` expressions in conjunction
+        with :meth:`~.Session.query`, i.e.::
+        
+            from sqlalchemy import func
+            
+            # count User records, without
+            # using a subquery.
+            session.query(func.count(User.id))
+                        
+            # return count of user "id" grouped
+            # by "name"
+            session.query(func.count(User.id)).\\
+                    group_by(User.name)
+
+            from sqlalchemy import distinct
+            
+            # count distinct "name" values
+            session.query(func.count(distinct(User.name)))
+            
         """
-        should_nest = [self._should_nest_selectable]
-        def ent_cols(ent):
-            if isinstance(ent, _MapperEntity):
-                return ent.mapper.primary_key
-            else:
-                should_nest[0] = True
-                return [ent.column]
-
-        return self._col_aggregate(sql.literal_column('1'), sql.func.count,
-            nested_cols=chain(*[ent_cols(ent) for ent in self._entities]),
-            should_nest = should_nest[0]
-        )
-
-    def _col_aggregate(self, col, func, nested_cols=None, should_nest=False):
-        context = QueryContext(self)
-
-        for entity in self._entities:
-            entity.setup_context(self, context)
-
-        if context.from_clause:
-            from_obj = list(context.from_clause)
-        else:
-            from_obj = context.froms
-
-        if self._enable_single_crit:
-            self._adjust_for_single_inheritance(context)
-
-        whereclause  = context.whereclause
-
-        if should_nest:
-            if not nested_cols:
-                nested_cols = [col]
-            else:
-                nested_cols = list(nested_cols)
-            s = sql.select(nested_cols, whereclause, 
-                        from_obj=from_obj, use_labels=True,
-                        **self._select_args)
-            s = s.alias()
-            s = sql.select(
-                [func(s.corresponding_column(col) or col)]).select_from(s)
-        else:
-            s = sql.select([func(col)], whereclause, from_obj=from_obj,
-            **self._select_args)
-
-        if self._autoflush and not self._populate_existing:
-            self.session._autoflush()
-        return self.session.scalar(s, params=self._params,
-            mapper=self._mapper_zero())
+        col = sql.func.count(sql.literal_column('1'))
+        return self.from_self(col).scalar()
 
     def delete(self, synchronize_session='evaluate'):
         """Perform a bulk delete query.
index b1769294526c2f501bd8f90c723c6bb12b786156..1fbf7c62268bde417d384f7d9ae14ec4e9d7891b 100644 (file)
@@ -625,6 +625,25 @@ def _produce_test(select_type):
             eq_(sess.query(Company).join(Company.employees.of_type(Engineer)).filter(Engineer.primary_language=='java').all(), [c1])
             eq_(sess.query(Company).join(Company.employees.of_type(Engineer), 'machines').filter(Machine.name.ilike("%thinkpad%")).all(), [c1])
 
+        def test_join_to_subclass_count(self):
+            sess = create_session()
+
+            eq_(sess.query(Company, Engineer).
+                    join(Company.employees.of_type(Engineer)).
+                    filter(Engineer.primary_language=='java').count(), 
+                1)
+
+            # test [ticket:2093]
+            eq_(sess.query(Company.company_id, Engineer).
+                    join(Company.employees.of_type(Engineer)).
+                    filter(Engineer.primary_language=='java').count(), 
+                1)
+
+            eq_(sess.query(Company).
+                    join(Company.employees.of_type(Engineer)).
+                    filter(Engineer.primary_language=='java').count(), 
+                1)
+
         def test_join_through_polymorphic(self):
 
             sess = create_session()