result.scalar() # first col of first row (warns if additional rows remain?)
result.scalars() # iterator of first col of each row
+ result.scalars(1) # iterator of second col of each row
+ result.scalars('a') # iterator of the "a" col of each row
result.scalars().all() # same, as a list
result.columns('a', 'b').<anything> # limit column tuples
# if the result is an ORM result, you could do:
result.columns(User, Address) # assuming these are available entities
+ # or to get just User as a list
+ result.scalars(User).all()
# index access and slices ?
result[0].all() # same as result.scalars().all()
By using attributes instead of strings above, the :meth:`.Query.join` method
no longer needs the almost never-used option of ``from_joinpoint``.
+Other ORM Query patterns changed
+=================================
+
+This section will collect various :class:`.Query` patterns and how they work
+in terms of :func:`.future.select`.
+
+.. _migration_20_query_distinct:
+
+Using DISTINCT with additional columns, but only select the entity
+-------------------------------------------------------------------
+
+:class:`.Query` will automatically add columns in the ORDER BY when
+distinct is used. The following query will select from all User columns
+as well as "address.email_address" but only return User objects::
+
+ result = session.query(User).join(User.addresses).\
+ distinct().order_by(Address.email_address).all()
+
+Relational databases won't allow you to ORDER BY "address.email_address" if
+it isn't also in the columns clause. But the above query only wants "User"
+objects back. In 2.0, this very unusual use case is performed explicitly,
+and the limiting of the entities/columns to ``User`` is done on the result::
+
+
+ from sqlalchemy.future import select
+
+ stmt = select(User, Address.email_address).join(User.addresses).\
+ distinct().order_by(Address.email_address)
+
+ result = session.execute(stmt).scalars(User).all()
+
Transparent Statement Compilation Caching replaces "Baked" queries, works in Core
==================================================================================
--- /dev/null
+.. change::
+ :tags: orm, bug
+ :tickets: 5134
+
+ Deprecated logic in :meth:`.Query.distinct` that automatically adds
+ columns in the ORDER BY clause to the columns clause; this will be removed
+ in 2.0.
+
+ .. seealso::
+
+ :ref:`migration_20_query_distinct`
.. note::
- The :meth:`.distinct` call includes logic that will automatically
- add columns from the ORDER BY of the query to the columns
- clause of the SELECT statement, to satisfy the common need
- of the database backend that ORDER BY columns be part of the
- SELECT list when DISTINCT is used. These columns *are not*
- added to the list of columns actually fetched by the
- :class:`.Query`, however, so would not affect results.
- The columns are passed through when using the
- :attr:`.Query.statement` accessor, however.
+ The ORM-level :meth:`.distinct` call includes logic that will
+ automatically add columns from the ORDER BY of the query to the
+ columns clause of the SELECT statement, to satisfy the common need
+ of the database backend that ORDER BY columns be part of the SELECT
+ list when DISTINCT is used. These columns *are not* added to the
+ list of columns actually fetched by the :class:`.Query`, however,
+ so would not affect results. The columns are passed through when
+ using the :attr:`.Query.statement` accessor, however.
+
+ .. deprecated:: 2.0 This logic is deprecated and will be removed
+ in SQLAlchemy 2.0. See :ref:`migration_20_query_distinct`
+ for a description of this use case in 2.0.
:param \*expr: optional column expressions. When present,
the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>)``
context.order_by = None
if self._distinct is True and context.order_by:
- context.primary_columns += (
- sql_util.expand_column_list_from_order_by
- )(context.primary_columns, context.order_by)
+ to_add = sql_util.expand_column_list_from_order_by(
+ context.primary_columns, context.order_by
+ )
+ if to_add:
+ util.warn_deprecated_20(
+ "ORDER BY columns added implicitly due to "
+ "DISTINCT is deprecated and will be removed in "
+ "SQLAlchemy 2.0. SELECT statements with DISTINCT "
+ "should be written to explicitly include the appropriate "
+ "columns in the columns clause"
+ )
+ context.primary_columns += to_add
context.froms += tuple(context.eager_joins.values())
statement = sql.select(
if q._limit is None and q._offset is None:
q._order_by = None
+ if q._distinct is True and q._order_by:
+ # the logic to automatically add the order by columns to the query
+ # when distinct is True is deprecated in the query
+ to_add = sql_util.expand_column_list_from_order_by(
+ target_cols, q._order_by
+ )
+ if to_add:
+ q._set_entities(target_cols + to_add)
+
# the original query now becomes a subquery
# which we'll join onto.
def _failure_message(self, expected_params):
return (
- "Testing for compiled statement %r partial params %s, "
- "received %%(received_statement)r with params "
+ "Testing for compiled statement\n%r partial params %s, "
+ "received\n%%(received_statement)r with params "
"%%(received_parameters)r"
% (
self.statement.replace("%", "%%"),
self.parameters = _distill_params(multiparams, params)
self.statements = []
+ def __repr__(self):
+ return str(self.statements)
+
class SQLCursorExecuteObserved(
collections.namedtuple(
elif rule.errormessage:
assert False, rule.errormessage
if observed:
- assert False, "Additional SQL statements remain"
+ assert False, "Additional SQL statements remain:\n%s" % observed
elif not rule.is_consumed:
rule.no_more_statements()
import sqlalchemy as sa
from sqlalchemy import and_
+from sqlalchemy import desc
from sqlalchemy import event
from sqlalchemy import func
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import testing
from sqlalchemy import text
+from sqlalchemy import true
from sqlalchemy.ext.declarative import comparable_using
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import aliased
def test_eagerloading(self):
with testing.expect_deprecated_20(".*joinedload"):
eagerload("foo")
+
+
+class DistinctOrderByImplicitTest(QueryTest, AssertsCompiledSQL):
+ __dialect__ = "default"
+
+ def test_columns_augmented_roundtrip_one(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+ q = (
+ sess.query(User)
+ .join("addresses")
+ .distinct()
+ .order_by(desc(Address.email_address))
+ )
+ with testing.expect_deprecated(
+ "ORDER BY columns added implicitly due to "
+ ):
+ eq_([User(id=7), User(id=9), User(id=8)], q.all())
+
+ def test_columns_augmented_roundtrip_three(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+
+ q = (
+ sess.query(User.id, User.name.label("foo"), Address.id)
+ .join(Address, true())
+ .filter(User.name == "jack")
+ .filter(User.id + Address.user_id > 0)
+ .distinct()
+ .order_by(User.id, User.name, Address.email_address)
+ )
+
+ # even though columns are added, they aren't in the result
+ with testing.expect_deprecated(
+ "ORDER BY columns added implicitly due to "
+ ):
+ eq_(
+ q.all(),
+ [
+ (7, "jack", 3),
+ (7, "jack", 4),
+ (7, "jack", 2),
+ (7, "jack", 5),
+ (7, "jack", 1),
+ ],
+ )
+ for row in q:
+ eq_(row._mapping.keys(), ["id", "foo", "id"])
+
+ def test_columns_augmented_sql_one(self):
+ User, Address = self.classes.User, self.classes.Address
+
+ sess = create_session()
+
+ q = (
+ sess.query(User.id, User.name.label("foo"), Address.id)
+ .distinct()
+ .order_by(User.id, User.name, Address.email_address)
+ )
+
+ # Address.email_address is added because of DISTINCT,
+ # however User.id, User.name are not b.c. they're already there,
+ # even though User.name is labeled
+ with testing.expect_deprecated(
+ "ORDER BY columns added implicitly due to "
+ ):
+ self.assert_compile(
+ q,
+ "SELECT DISTINCT users.id AS users_id, users.name AS foo, "
+ "addresses.id AS addresses_id, addresses.email_address AS "
+ "addresses_email_address FROM users, addresses "
+ "ORDER BY users.id, users.name, addresses.email_address",
+ )
sess = create_session()
q = (
- sess.query(User)
+ sess.query(User, Address.email_address)
.join("addresses")
.distinct()
.order_by(desc(Address.email_address))
+ .from_self(User)
)
eq_([User(id=7), User(id=9), User(id=8)], q.all())
sess = create_session()
q = (
- sess.query(User.id, User.name.label("foo"), Address.id)
+ sess.query(
+ User.id,
+ User.name.label("foo"),
+ Address.id,
+ Address.email_address,
+ )
.join(Address, true())
.filter(User.name == "jack")
.filter(User.id + Address.user_id > 0)
.distinct()
.order_by(User.id, User.name, Address.email_address)
+ .from_self(User.id, User.name.label("foo"), Address.id)
)
# even though columns are added, they aren't in the result
sess = create_session()
q = (
- sess.query(User.id, User.name.label("foo"), Address.id)
+ sess.query(
+ User.id,
+ User.name.label("foo"),
+ Address.id,
+ Address.email_address,
+ )
.distinct()
.order_by(User.id, User.name, Address.email_address)
+ .from_self(User.id, User.name.label("foo"), Address.id)
)
# Address.email_address is added because of DISTINCT,
# even though User.name is labeled
self.assert_compile(
q,
+ "SELECT anon_1.users_id AS anon_1_users_id, anon_1.foo AS foo, "
+ "anon_1.addresses_id AS anon_1_addresses_id "
+ "FROM ("
"SELECT DISTINCT users.id AS users_id, users.name AS foo, "
- "addresses.id AS addresses_id, "
- "addresses.email_address AS addresses_email_address FROM users, "
- "addresses ORDER BY users.id, users.name, addresses.email_address",
+ "addresses.id AS addresses_id, addresses.email_address AS "
+ "addresses_email_address FROM users, addresses ORDER BY "
+ "users.id, users.name, addresses.email_address"
+ ") AS anon_1",
)
def test_columns_augmented_sql_two(self):