summaryrefslogtreecommitdiff
path: root/doc/build/orm/queryguide.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/build/orm/queryguide.rst')
-rw-r--r--doc/build/orm/queryguide.rst882
1 files changed, 882 insertions, 0 deletions
diff --git a/doc/build/orm/queryguide.rst b/doc/build/orm/queryguide.rst
new file mode 100644
index 000000000..5576882a6
--- /dev/null
+++ b/doc/build/orm/queryguide.rst
@@ -0,0 +1,882 @@
+.. highlight:: pycon+sql
+
+.. _queryguide_toplevel:
+
+==================
+ORM Querying Guide
+==================
+
+This section provides an overview of emitting queries with the
+SQLAlchemy ORM using :term:`2.0 style` usage.
+
+Readers of this section should be familiar with the SQLAlchemy overview
+at :ref:`unified_tutorial`, and in particular most of the content here expands
+upon the content at :ref:`tutorial_selecting_data`.
+
+
+.. Setup code, not for display
+
+ >>> from sqlalchemy import create_engine
+ >>> engine = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
+ >>> from sqlalchemy import MetaData, Table, Column, Integer, String
+ >>> metadata = MetaData()
+ >>> user_table = Table(
+ ... "user_account",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('name', String(30)),
+ ... Column('fullname', String)
+ ... )
+ >>> from sqlalchemy import ForeignKey
+ >>> address_table = Table(
+ ... "address",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('user_id', None, ForeignKey('user_account.id')),
+ ... Column('email_address', String, nullable=False)
+ ... )
+ >>> orders_table = Table(
+ ... "user_order",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('user_id', None, ForeignKey('user_account.id')),
+ ... Column('email_address', String, nullable=False)
+ ... )
+ >>> order_items_table = Table(
+ ... "order_items",
+ ... metadata,
+ ... Column("order_id", ForeignKey("user_order.id"), primary_key=True),
+ ... Column("item_id", ForeignKey("item.id"), primary_key=True)
+ ... )
+ >>> items_table = Table(
+ ... "item",
+ ... metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('name', String),
+ ... Column('description', String)
+ ... )
+ >>> metadata.create_all(engine)
+ BEGIN (implicit)
+ ...
+ >>> from sqlalchemy.orm import declarative_base
+ >>> Base = declarative_base()
+ >>> from sqlalchemy.orm import relationship
+ >>> class User(Base):
+ ... __table__ = user_table
+ ...
+ ... addresses = relationship("Address", back_populates="user")
+ ... orders = relationship("Order")
+ ...
+ ... def __repr__(self):
+ ... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
+
+ >>> class Address(Base):
+ ... __table__ = address_table
+ ...
+ ... user = relationship("User", back_populates="addresses")
+ ...
+ ... def __repr__(self):
+ ... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
+
+ >>> class Order(Base):
+ ... __table__ = orders_table
+ ... items = relationship("Item", secondary=order_items_table)
+
+ >>> class Item(Base):
+ ... __table__ = items_table
+
+ >>> conn = engine.connect()
+ >>> from sqlalchemy.orm import Session
+ >>> session = Session(conn)
+ >>> session.add_all([
+ ... User(name="spongebob", fullname="Spongebob Squarepants", addresses=[
+ ... Address(email_address="spongebob@sqlalchemy.org")
+ ... ]),
+ ... User(name="sandy", fullname="Sandy Cheeks", addresses=[
+ ... Address(email_address="sandy@sqlalchemy.org"),
+ ... Address(email_address="squirrel@squirrelpower.org")
+ ... ]),
+ ... User(name="patrick", fullname="Patrick Star", addresses=[
+ ... Address(email_address="pat999@aol.com")
+ ... ]),
+ ... User(name="squidward", fullname="Squidward Tentacles", addresses=[
+ ... Address(email_address="stentcl@sqlalchemy.org")
+ ... ]),
+ ... User(name="ehkrabs", fullname="Eugene H. Krabs"),
+ ... ])
+ >>> session.commit()
+ BEGIN ...
+ >>> conn.begin()
+ BEGIN ...
+
+
+SELECT statements
+=================
+
+SELECT statements are produced by the :func:`_sql.select` function which
+returns a :class:`_sql.Select` object::
+
+ >>> from sqlalchemy import select
+ >>> stmt = select(User).where(User.name == 'spongebob')
+
+To invoke a :class:`_sql.Select` with the ORM, it is passed to
+:meth:`_orm.Session.execute`::
+
+ {sql}>>> result = session.execute(stmt)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = ?
+ [...] ('spongebob',){stop}
+ >>> for user_obj in result.scalars():
+ ... print(f"{user_obj.name} {user_obj.fullname}")
+ spongebob Spongebob Squarepants
+
+
+
+.. _orm_queryguide_select_columns:
+
+Selecting ORM Entities and Attributes
+--------------------------------------
+
+The :func:`_sql.select` construct accepts ORM entities, including mapped
+classes as well as class-level attributes representing mapped columns, which
+are converted into ORM-annotated :class:`_sql.FromClause` and
+:class:`_sql.ColumnElement` elements at construction time.
+
+A :class:`_sql.Select` object that contains ORM-annotated entities is normally
+executed using a :class:`_orm.Session` object, and not a :class:`_future.Connection`
+object, so that ORM-related features may take effect.
+
+Below we select from the ``User`` entity, producing a :class:`_sql.Select`
+that selects from the mapped :class:`_schema.Table` to which ``User`` is mapped::
+
+ {sql}>>> result = session.execute(select(User).order_by(User.id))
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account ORDER BY user_account.id
+ [...] (){stop}
+
+When selecting from ORM entities, the entity itself is returned in the result
+as a single column value; for example above, the :class:`_engine.Result`
+returns :class:`_engine.Row` objects that have just a single column, that column
+holding onto a ``User`` object::
+
+ >>> result.fetchone()
+ (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
+
+When selecting a list of single-column ORM entities, it is typical to skip
+the generation of :class:`_engine.Row` objects and instead receive
+ORM entities directly, which is achieved using the :meth:`_engine.Result.scalars`
+method::
+
+ >>> result.scalars().all()
+ [User(id=2, name='sandy', fullname='Sandy Cheeks'),
+ User(id=3, name='patrick', fullname='Patrick Star'),
+ User(id=4, name='squidward', fullname='Squidward Tentacles'),
+ User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')]
+
+ORM Entities are named in the result row based on their class name,
+such as below where we SELECT from both ``User`` and ``Address`` at the
+same time::
+
+ >>> stmt = select(User, Address).join(User.addresses).order_by(User.id, Address.id)
+
+ {sql}>>> for row in session.execute(stmt):
+ ... print(f"{row.User.name} {row.Address.email_address}")
+ SELECT user_account.id, user_account.name, user_account.fullname,
+ address.id AS id_1, address.user_id, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ ORDER BY user_account.id, address.id
+ [...] (){stop}
+ spongebob spongebob@sqlalchemy.org
+ sandy sandy@sqlalchemy.org
+ sandy squirrel@squirrelpower.org
+ patrick pat999@aol.com
+ squidward stentcl@sqlalchemy.org
+
+
+Selecting Individual Attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The attributes on a mapped class, such as ``User.name`` and ``Address.email_address``,
+have a similar behavior as that of the entity class itself such as ``User``
+in that they are automatically converted into ORM-annotated Core objects
+when passed to :func:`_sql.select`. They may be used in the same way
+as table columns are used::
+
+ {sql}>>> result = session.execute(
+ ... select(User.name, Address.email_address).
+ ... join(User.addresses).
+ ... order_by(User.id, Address.id)
+ ... )
+ SELECT user_account.name, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ ORDER BY user_account.id, address.id
+ [...] (){stop}
+
+ORM attributes, themselves known as :class:`_orm.InstrumentedAttribute`
+objects, can be used in the same way as any :class:`_sql.ColumnElement`,
+and are delivered in result rows just the same way, such as below
+where we refer to their values by column name within each row::
+
+ >>> for row in result:
+ ... print(f"{row.name} {row.email_address}")
+ spongebob spongebob@sqlalchemy.org
+ sandy sandy@sqlalchemy.org
+ sandy squirrel@squirrelpower.org
+ patrick pat999@aol.com
+ squidward stentcl@sqlalchemy.org
+
+Grouping Selected Attributes with Bundles
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :class:`_orm.Bundle` construct is an extensible ORM-only construct that
+allows sets of column expressions to be grouped in result rows::
+
+ >>> from sqlalchemy.orm import Bundle
+ >>> stmt = select(
+ ... Bundle("user", User.name, User.fullname),
+ ... Bundle("email", Address.email_address)
+ ... ).join_from(User, Address)
+ {sql}>>> for row in session.execute(stmt):
+ ... print(f"{row.user.name} {row.email.email_address}")
+ SELECT user_account.name, user_account.fullname, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ [...] (){stop}
+ spongebob spongebob@sqlalchemy.org
+ sandy sandy@sqlalchemy.org
+ sandy squirrel@squirrelpower.org
+ patrick pat999@aol.com
+ squidward stentcl@sqlalchemy.org
+
+
+The :class:`_orm.Bundle` is potentially useful for creating lightweight
+views as well as custom column groupings such as mappings.
+
+.. seealso::
+
+ :ref:`bundles` - in the ORM loading documentation.
+
+
+Selecting ORM Aliases
+^^^^^^^^^^^^^^^^^^^^^
+
+As discussed in the tutorial at :ref:`tutorial_using_aliases`, to create a
+SQL alias of an ORM entity is achieved using the :func:`_orm.aliased`
+construct against a mapped class::
+
+ >>> from sqlalchemy.orm import aliased
+ >>> u1 = aliased(User)
+ >>> print(select(u1).order_by(u1.id))
+ {opensql}SELECT user_account_1.id, user_account_1.name, user_account_1.fullname
+ FROM user_account AS user_account_1 ORDER BY user_account_1.id
+
+As is the case when using :meth:`_schema.Table.alias`, the SQL alias
+is anonymously named. For the case of selecting the entity from a row
+with an explicit name, the :paramref:`_orm.aliased.name` parameter may be
+passed as well::
+
+ >>> from sqlalchemy.orm import aliased
+ >>> u1 = aliased(User, name="u1")
+ >>> stmt = select(u1).order_by(u1.id)
+ {sql}>>> row = session.execute(stmt).first()
+ SELECT u1.id, u1.name, u1.fullname
+ FROM user_account AS u1 ORDER BY u1.id
+ [...] (){stop}
+ >>> print(f"{row.u1.name}")
+ spongebob
+
+The :class:`_orm.aliased` construct is also central to making use of subqueries
+with the ORM; the section :ref:`orm_queryguide_subqueries` discusses this further.
+
+.. _orm_queryguide_selecting_text:
+
+Getting ORM Results from Textual and Core Statements
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ORM supports loading of entities from SELECT statements that come from other
+sources. The typical use case is that of a textual SELECT statement, which
+in SQLAlchemy is represented using the :func:`_sql.text` construct. The
+:func:`_sql.text` construct, once constructed, can be augmented with
+information
+about the ORM-mapped columns that the statement would load; this can then be
+associated with the ORM entity itself so that ORM objects can be loaded based
+on this statement.
+
+Given a textual SQL statement we'd like to load from::
+
+ >>> from sqlalchemy import text
+ >>> textual_sql = text("SELECT id, name, fullname FROM user_account ORDER BY id")
+
+We can add column information to the statement by using the
+:meth:`_sql.TextClause.columns` method; when this method is invoked, the
+:class:`_sql.TextClause` object is converted into a :class:`_sql.TextualSelect`
+object, which takes on a role that is comparable to the :class:`_sql.Select`
+construct. The :meth:`_sql.TextClause.columns` method
+is typically passed :class:`_schema.Column` objects or equivalent, and in this
+case we can make use of the ORM-mapped attributes on the ``User`` class
+directly::
+
+ >>> textual_sql = textual_sql.columns(User.id, User.name, User.fullname)
+
+We now have an ORM-configured SQL construct that as given, can load the "id",
+"name" and "fullname" columns separately. To use this SELECT statement as a
+source of complete ``User`` entities instead, we can link these columns to a
+regular ORM-enabled
+:class:`_sql.Select` construct using the :meth:`_sql.Select.from_statement`
+method::
+
+ >>> # using from_statement()
+ >>> orm_sql = select(User).from_statement(textual_sql)
+ >>> for user_obj in session.execute(orm_sql).scalars():
+ ... print(user_obj)
+ {opensql}SELECT id, name, fullname FROM user_account ORDER BY id
+ [...] (){stop}
+ User(id=1, name='spongebob', fullname='Spongebob Squarepants')
+ User(id=2, name='sandy', fullname='Sandy Cheeks')
+ User(id=3, name='patrick', fullname='Patrick Star')
+ User(id=4, name='squidward', fullname='Squidward Tentacles')
+ User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')
+
+The same :class:`_sql.TextualSelect` object can also be converted into
+a subquery using the :meth:`_sql.TextualSelect.subquery` method,
+and linked to the ``User`` entity to it using the :func:`_orm.aliased`
+construct, in a similar manner as discussed below in :ref:`orm_queryguide_subqueries`::
+
+ >>> # using aliased() to select from a subquery
+ >>> orm_subquery = aliased(User, textual_sql.subquery())
+ >>> stmt = select(orm_subquery)
+ >>> for user_obj in session.execute(stmt).scalars():
+ ... print(user_obj)
+ {opensql}SELECT anon_1.id, anon_1.name, anon_1.fullname
+ FROM (SELECT id, name, fullname FROM user_account ORDER BY id) AS anon_1
+ [...] (){stop}
+ User(id=1, name='spongebob', fullname='Spongebob Squarepants')
+ User(id=2, name='sandy', fullname='Sandy Cheeks')
+ User(id=3, name='patrick', fullname='Patrick Star')
+ User(id=4, name='squidward', fullname='Squidward Tentacles')
+ User(id=5, name='ehkrabs', fullname='Eugene H. Krabs')
+
+The difference between using the :class:`_sql.TextualSelect` directly with
+:meth:`_sql.Select.from_statement` versus making use of :func:`_sql.aliased`
+is that in the former case, no subuqery is produced in the resulting SQL.
+This can in some scenarios be advantageous from a performance or complexity
+perspective.
+
+.. _orm_queryguide_joins:
+
+Joins
+-----
+
+The :meth:`_sql.Select.join` and :meth:`_sql.Select.join_from` methods
+are used to construct SQL JOINs against a SELECT statement.
+
+This section will detail ORM use cases for these methods. For a general
+overview of their use from a Core perspective, see :ref:`tutorial_select_join`
+in the :ref:`unified_tutorial`.
+
+The usage of :meth:`_sql.Select.join` in an ORM context for :term:`2.0 style`
+queries is mostly equivalent, minus legacy use cases, to the usage of the
+:meth:`_orm.Query.join` method in :term:`1.x style` queries.
+
+Simple Relationship Joins
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Consider a mapping between two classes ``User`` and ``Address``,
+with a relationship ``User.addresses`` representing a collection
+of ``Address`` objects associated with each ``User``. The most
+common usage of :meth:`_sql.Select.join`
+is to create a JOIN along this
+relationship, using the ``User.addresses`` attribute as an indicator
+for how this should occur::
+
+ >>> stmt = select(User).join(User.addresses)
+
+Where above, the call to :meth:`_sql.Select.join` along
+``User.addresses`` will result in SQL approximately equivalent to::
+
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+In the above example we refer to ``User.addresses`` as passed to
+:meth:`_sql.Select.join` as the "on clause", that is, it indicates
+how the "ON" portion of the JOIN should be constructed.
+
+Chaining Multiple Joins
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+To construct a chain of joins, multiple :meth:`_sql.Select.join` calls may be
+used. The relationship-bound attribute implies both the left and right side of
+the join at once. Consider additional entities ``Order`` and ``Item``, where
+the ``User.orders`` relationship refers to the ``Order`` entity, and the
+``Order.items`` relationship refers to the ``Item`` entity, via an association
+table ``order_items``. Two :meth:`_sql.Select.join` calls will result in
+a JOIN first from ``User`` to ``Order``, and a second from ``Order`` to
+``Item``. However, since ``Order.items`` is a :ref:`many to many <relationships_many_to_many>`
+relationship, it results in two separate JOIN elements, for a total of three
+JOIN elements in the resulting SQL::
+
+ >>> stmt = (
+ ... select(User).
+ ... join(User.orders).
+ ... join(Order.items)
+ ... )
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN user_order ON user_account.id = user_order.user_id
+ JOIN order_items AS order_items_1 ON user_order.id = order_items_1.order_id
+ JOIN item ON item.id = order_items_1.item_id
+
+.. tip::
+
+ as seen in the above example, **the order in which each call to the join()
+ method occurs is important**. Query would not, for example, know how to
+ join correctly if we were to specify ``User``, then ``Item``, then
+ ``Order``, in our chain of joins; in such a case, depending on the
+ arguments passed, it may raise an error that it doesn't know how to join,
+ or it may produce invalid SQL in which case the database will raise an
+ error. In correct practice, the :meth:`_sql.Select.join` method is invoked
+ in such a way that lines up with how we would want the JOIN clauses in SQL
+ to be rendered, and each call should represent a clear link from what
+ precedes it.
+
+Joins to a Target Entity or Selectable
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A second form of :meth:`_sql.Select.join` allows any mapped entity or core
+selectable construct as a target. In this usage, :meth:`_sql.Select.join`
+will attempt to **infer** the ON clause for the JOIN, using the natural foreign
+key relationship between two entities::
+
+ >>> stmt = select(User).join(Address)
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+In the above calling form, :meth:`_sql.Select.join` is called upon to infer
+the "on clause" automatically. This calling form will ultimately raise
+an error if either there are no :class:`_schema.ForeignKeyConstraint` setup
+between the two mapped :class:`_schema.Table` constructs, or if there are multiple
+:class:`_schema.ForeignKeyConstraint` linakges between them such that the
+appropriate constraint to use is ambiguous.
+
+.. note:: When making use of :meth:`_sql.Select.join` or :meth:`_sql.Select.join_from`
+ without indicating an ON clause, ORM
+ configured :func:`_orm.relationship` constructs are **not taken into account**.
+ Only the configured :class:`_schema.ForeignKeyConstraint` relationships between
+ the entities at the level of the mapped :class:`_schema.Table` objects are consulted
+ when an attempt is made to infer an ON clause for the JOIN.
+
+Joins to a Target with an ON Clause
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The third calling form allows both the target entity as well
+as the ON clause to be passed explicitly. A example that includes
+a SQL expression as the ON clause is as follows::
+
+ >>> stmt = select(User).join(Address, User.id==Address.user_id)
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+The expression-based ON clause may also be the relationship-bound
+attribute; this form in fact states the target of ``Address`` twice, however
+this is accepted::
+
+ >>> stmt = select(User).join(Address, User.addresses)
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+The above syntax has more functionality if we use it in terms of aliased
+entities. The default target for ``User.addresses`` is the ``Address``
+class, however if we pass aliased forms using :func:`_orm.aliased`, the
+:func:`_orm.aliased` form will be used as the target, as in the example
+below::
+
+ >>> a1 = aliased(Address)
+ >>> a2 = aliased(Address)
+ >>> stmt = (
+ ... select(User).
+ ... join(a1, User.addresses).
+ ... join(a2, User.addresses).
+ ... where(a1.email_address == 'ed@foo.com').
+ ... where(a2.email_address == 'ed@bar.com')
+ ... )
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN address AS address_1 ON user_account.id = address_1.user_id
+ JOIN address AS address_2 ON user_account.id = address_2.user_id
+ WHERE address_1.email_address = :email_address_1
+ AND address_2.email_address = :email_address_2
+
+When using relationship-bound attributes, the target entity can also be
+substituted with an aliased entity by using the
+:meth:`_orm.PropComparator.of_type` method. The same example using
+this method would be::
+
+ >>> stmt = (
+ ... select(User).
+ ... join(User.addresses.of_type(a1)).
+ ... join(User.addresses.of_type(a2)).
+ ... where(a1.email_address == 'ed@foo.com').
+ ... where(a2.email_address == 'ed@bar.com')
+ ... )
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN address AS address_1 ON user_account.id = address_1.user_id
+ JOIN address AS address_2 ON user_account.id = address_2.user_id
+ WHERE address_1.email_address = :email_address_1
+ AND address_2.email_address = :email_address_2
+
+.. _orm_queryguide_join_on_augmented:
+
+Augmenting Built-in ON Clauses
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As a substitute for providing a full custom ON condition for an
+existing relationship, the :meth:`_orm.PropComparator.and_` function
+may be applied to a relationship attribute to augment additional
+criteria into the ON clause; the additional criteria will be combined
+with the default criteria using AND. Below, the ON criteria between
+``user_account`` and ``address`` contains two separate elements joined
+by ``AND``, the first one being the natural join along the foreign key,
+and the second being a custom limiting criteria::
+
+ >>> stmt = (
+ ... select(User).
+ ... join(User.addresses.and_(Address.email_address != 'foo@bar.com'))
+ ... )
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN address ON user_account.id = address.user_id
+ AND address.email_address != :email_address_1
+
+.. seealso::
+
+ The :meth:`_orm.PropComparator.and_` method also works with loader
+ strategies. See the section :ref:`loader_option_criteria` for an example.
+
+.. _orm_queryguide_subqueries:
+
+Joining to Subqueries
+^^^^^^^^^^^^^^^^^^^^^^^
+
+The target of a join may be any "selectable" entity which usefully includes
+subuqeries. When using the ORM, it is typical
+that these targets are stated in terms of an
+:func:`_orm.aliased` construct, but this is not strictly required particularly
+if the joined entity is not being returned in the results. For example, to join from the
+``User`` entity to the ``Address`` entity, where the ``Address`` entity
+is represented as a row limited subquery, we first construct a :class:`_sql.Subquery`
+object using :meth:`_sql.Select.subquery`, which may then be used as the
+target of the :meth:`_sql.Select.join` method::
+
+ >>> subq = (
+ ... select(Address).
+ ... where(Address.email_address == 'pat999@aol.com').
+ ... subquery()
+ ... )
+ >>> stmt = select(User).join(subq, User.id == subq.c.user_id)
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN (SELECT address.id AS id,
+ address.user_id AS user_id, address.email_address AS email_address
+ FROM address
+ WHERE address.email_address = :email_address_1) AS anon_1
+ ON user_account.id = anon_1.user_id{stop}
+
+The above SELECT statement when invoked via :meth:`_orm.Session.execute`
+will return rows that contain ``User`` entities, but not ``Address`` entities.
+In order to add ``Address`` entities to the set of entities that would be
+returned in result sets, we construct an :func:`_orm.aliased` object against
+the ``Address`` entity and the custom subquery. Note we also apply a name
+``"address"`` to the :func:`_orm.aliased` construct so that we may
+refer to it by name in the result row::
+
+
+ >>> address_subq = aliased(Address, subq, name="address")
+ >>> stmt = select(User, address_subq).join(address_subq)
+ >>> for row in session.execute(stmt):
+ ... print(f"{row.User} {row.address}")
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname,
+ anon_1.id AS id_1, anon_1.user_id, anon_1.email_address
+ FROM user_account
+ JOIN (SELECT address.id AS id,
+ address.user_id AS user_id, address.email_address AS email_address
+ FROM address
+ WHERE address.email_address = ?) AS anon_1 ON user_account.id = anon_1.user_id
+ [...] ('pat999@aol.com',){stop}
+ User(id=3, name='patrick', fullname='Patrick Star') Address(id=4, email_address='pat999@aol.com')
+
+The same subquery may be referred towards by multiple entities as well,
+for a subquery that represents more than one entity. The subquery itself
+will remain unique within the statement, while the entities that are linked
+to it using :class:`_orm.aliased` refer to distinct sets of columns::
+
+ >>> user_address_subq = (
+ ... select(User.id, User.name, Address.id, Address.email_address).
+ ... join_from(User, Address).
+ ... where(Address.email_address.in_(['pat999@aol.com', 'squirrel@squirrelpower.org'])).
+ ... subquery()
+ ... )
+ >>> user_alias = aliased(User, user_address_subq, name="user")
+ >>> address_alias = aliased(Address, user_address_subq, name="address")
+ >>> stmt = select(user_alias, address_alias).where(user_alias.name == 'sandy')
+ >>> for row in session.execute(stmt):
+ ... print(f"{row.user} {row.address}")
+ {opensql}SELECT anon_1.id, anon_1.name, anon_1.id_1, anon_1.email_address
+ FROM (SELECT user_account.id AS id, user_account.name AS name, address.id AS id_1, address.email_address AS email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ WHERE address.email_address IN (?, ?)) AS anon_1
+ WHERE anon_1.name = ?
+ [...] ('pat999@aol.com', 'squirrel@squirrelpower.org', 'sandy'){stop}
+ User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='squirrel@squirrelpower.org')
+
+
+
+Controlling what to Join From
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In cases where the left side of the current state of
+:class:`_sql.Select` is not in line with what we want to join from,
+the :meth:`_sql.Select.join_from` method may be used::
+
+ >>> stmt = select(Address).join_from(User, User.addresses).where(User.name == 'sandy')
+ >>> print(stmt)
+ SELECT address.id, address.user_id, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ WHERE user_account.name = :name_1
+
+The :meth:`_sql.Select.join_from` method accepts two or three arguments, either
+in the form ``<join from>, <onclause>``, or ``<join from>, <join to>,
+[<onclause>]``::
+
+ >>> stmt = select(Address).join_from(User, Address).where(User.name == 'sandy')
+ >>> print(stmt)
+ SELECT address.id, address.user_id, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ WHERE user_account.name = :name_1
+
+To set up the initial FROM clause for a SELECT such that :meth:`_sql.Select.join`
+can be used subsequent, the :meth:`_sql.Select.select_from` method may also
+be used::
+
+
+ >>> stmt = select(Address).select_from(User).join(User.addresses).where(User.name == 'sandy')
+ >>> print(stmt)
+ SELECT address.id, address.user_id, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+ WHERE user_account.name = :name_1
+
+
+
+
+Special Relationship Operators
+------------------------------
+
+As detailed in the :ref:`unified_tutorial` at
+:ref:`tutorial_select_relationships`, ORM attributes mapped by
+:func:`_orm.relationship` may be used in a variety of ways as SQL construction
+helpers. In addition to the above documentation on
+:ref:`orm_queryguide_joins`, relationships may produce criteria to be used in
+the WHERE clause as well. See the linked sections below.
+
+.. seealso::
+
+ Sections in the :ref:`tutorial_orm_related_objects` section of the
+ :ref:`unified_tutorial`:
+
+ * :ref:`tutorial_relationship_exists` - helpers to generate EXISTS clauses
+ using :func:`_orm.relationship`
+
+
+ * :ref:`tutorial_relationship_operators` - helpers to create comparisons in
+ terms of a :func:`_orm.relationship` in reference to a specific object
+ instance
+
+
+ORM Loader Options
+-------------------
+
+Loader options are objects that are passed to the :meth:`_sql.Select.options`
+method which affect the loading of both column and relationship-oriented
+attributes. The majority of loader options descend from the :class:`_orm.Load`
+hierarchy. For a complete overview of using loader options, see the linked
+sections below.
+
+.. seealso::
+
+ * :ref:`loading_columns` - details mapper and loading options that affect
+ how column and SQL-expression mapped attributes are loaded
+
+ * :ref:`loading_toplevel` - details relationship and loading options that
+ affect how :func:`_orm.relationship` mapped attributes are loaded
+
+
+ORM Execution Options
+---------------------
+
+Execution options are keyword arguments that are passed to an
+"execution_options" method, which take place at the level of statement
+execution. The primary "execution option" method is in Core at
+:meth:`_engine.Connection.execution_options`. In the ORM, execution options may
+also be passed to :meth:`_orm.Session.execute` using the
+:paramref:`_orm.Session.execute.execution_options` parameter. Perhaps more
+succinctly, most execution options, including those specific to the ORM, can be
+assigned to a statement directly, using the
+:meth:`_sql.Executable.execution_options` method, so that the options may be
+associated directly with the statement instead of being configured separately.
+The examples below will use this form.
+
+.. _orm_queryguide_populate_existing:
+
+Populate Existing
+^^^^^^^^^^^^^^^^^^
+
+The ``populate_existing`` execution option ensures that for all rows
+loaded, the corresponding instances in the :class:`_orm.Session` will
+be fully refreshed, erasing any existing data within the objects
+(including pending changes) and replacing with the data loaded from the
+result.
+
+Example use looks like::
+
+ >>> stmt = select(User).execution_options(populate_existing=True)
+ {sql}>>> result = session.execute(stmt)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ ...
+
+Normally, ORM objects are only loaded once, and if they are matched up
+to the primary key in a subsequent result row, the row is not applied to the
+object. This is both to preserve pending, unflushed changes on the object
+as well as to avoid the overhead and complexity of refreshing data which
+is already there. The :class:`_orm.Session` assumes a default working
+model of a highly isolated transaction, and to the degree that data is
+expected to change within the transaction outside of the local changes being
+made, those use cases would be handled using explicit steps such as this method.
+
+Another use case for ``populate_existing`` is in support of various
+attribute loading features that can change how an attribute is loaded on
+a per-query basis. Options for which this apply include:
+
+* The :func:`_orm.with_expression` option
+
+* The :meth:`_orm.PropComparator.and_` method that can modify what a loader
+ strategy loads
+
+* The :func:`_orm.contains_eager` option
+
+* The :func:`_orm.with_loader_criteria` option
+
+The ``populate_existing`` execution option is equvialent to the
+:meth:`_orm.Query.populate_existing` method in :term:`1.x style` ORM queries.
+
+.. seealso::
+
+ :ref:`faq_session_identity` - in :doc:`/faq/index`
+
+ :ref:`session_expire` - in the ORM :class:`_orm.Session`
+ documentation
+
+.. _orm_queryguide_autoflush:
+
+Autoflush
+^^^^^^^^^^
+
+This option when passed as ``False`` will cause the :class:`_orm.Session`
+to not invoke the "autoflush" step. It's equivalent to using the
+:attr:`_orm.Session.no_autoflush` context manager to disable autoflush::
+
+ >>> stmt = select(User).execution_options(autoflush=False)
+ {sql}>>> session.execute(stmt)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ ...
+
+This option will also work on ORM-enabled :class:`_sql.Update` and
+:class:`_sql.Delete` queries.
+
+The ``autoflush`` execution option is equvialent to the
+:meth:`_orm.Query.autoflush` method in :term:`1.x style` ORM queries.
+
+.. seealso::
+
+ :ref:`session_flushing`
+
+.. _orm_queryguide_yield_per:
+
+Yield Per
+^^^^^^^^^^
+
+The ``yield_per`` execution option is an integer value which will cause the
+:class:`_engine.Result` to yield only a fixed count of rows at a time. It is
+often useful to use with a result partitioning method such as
+:meth:`_engine.Result.partitions`, e.g.::
+
+ >>> stmt = select(User).execution_options(yield_per=10)
+ {sql}>>> for partition in session.execute(stmt).partitions(10):
+ ... for row in partition:
+ ... print(row)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ [...] (){stop}
+ (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
+ ...
+
+The purpose of this method is when fetching very large result sets
+(> 10K rows), to batch results in sub-collections and yield them
+out partially, so that the Python interpreter doesn't need to declare
+very large areas of memory which is both time consuming and leads
+to excessive memory use. The performance from fetching hundreds of
+thousands of rows can often double when a suitable yield-per setting
+(e.g. approximately 1000) is used, even with DBAPIs that buffer
+rows (which are most).
+
+When ``yield_per`` is used, the
+:paramref:`_engine.Connection.execution_options.stream_results` option is also
+set for the Core execution, so that a streaming / server side cursor will be
+used if the backend supports it (currently known are
+:mod:`~sqlalchemy.dialects.postgresql.psycopg2`,
+:mod:`~sqlalchemy.dialects.mysql.mysqldb` and
+:mod:`~sqlalchemy.dialects.mysql.pymysql`. Other backends will pre buffer all
+rows. The memory use of raw database rows is much less than that of an
+ORM-mapped object, but should still be taken into consideration when
+benchmarking.
+
+
+The ``yield_per`` execution option **is not compatible subqueryload eager
+loading or joinedload eager loading when using collections**. It is
+potentially compatible with "select in" eager loading, **provided the database
+driver supports multiple, independent cursors** (pysqlite and psycopg2 are
+known to work, MySQL and SQL Server ODBC drivers do not).
+
+The ``yield_per`` execution option is equvialent to the
+:meth:`_orm.Query.yield_per` method in :term:`1.x style` ORM queries.
+
+.. seealso::
+
+ :ref:`engine_stream_results`
+
+
+
+
+ORM Update / Delete with Arbitrary WHERE clause
+================================================
+
+The :meth:`_orm.Session.execute` method, in addition to handling ORM-enabled
+:class:`_sql.Select` objects, can also accommodate ORM-enabled
+:class:`_sql.Update` and :class:`_sql.Delete` objects, which UPDATE or DELETE
+any number of database rows while also being able to synchronize the state of
+matching objects locally present in the :class:`_orm.Session`. See the section
+:ref:`orm_expression_update_delete` for background on this feature.
+
+
+
+