From: Mike Bayer Date: Thu, 10 Mar 2011 00:17:45 +0000 (-0500) Subject: - Changed the underlying approach to query.count(). X-Git-Tag: rel_0_7b3~39 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e7be5a5729cabc6133b4fc83c06f6c5277a7af19;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 ()". 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] --- diff --git a/CHANGES b/CHANGES index 02dc1cbac2..093c5f8fe5 100644 --- 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 ()". + 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 diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 43699b4d9e..6f79d7c9c6 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -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 (>)`` 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 + ) 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. diff --git a/test/orm/inheritance/test_query.py b/test/orm/inheritance/test_query.py index b176929452..1fbf7c6226 100644 --- a/test/orm/inheritance/test_query.py +++ b/test/orm/inheritance/test_query.py @@ -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()