-.. _loading_toplevel:
-
.. currentmodule:: sqlalchemy.orm
Relationship Loading Techniques
.. sourcecode:: python+sql
{sql}>>> jack.addresses
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address,
+ SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address,
addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id
[5]
{stop}[<Address(u'jack@google.com')>, <Address(u'j25@yahoo.com')>]
-The one case where SQL is not emitted is for a simple many-to-one relationship, when
+The one case where SQL is not emitted is for a simple many-to-one relationship, when
the related object can be identified by its primary key alone and that object is already
present in the current :class:`.Session`.
{sql}>>> jack = session.query(User).\
... options(subqueryload('addresses')).\
- ... filter_by(name='jack').all()
- SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname,
- users.password AS users_password
- FROM users
+ ... filter_by(name='jack').all()
+ SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM users
WHERE users.name = ?
('jack',)
- SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id, anon_1.users_id AS anon_1_users_id
- FROM (SELECT users.id AS users_id
- FROM users
- WHERE users.name = ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id
+ SELECT addresses.id AS addresses_id, addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id, anon_1.users_id AS anon_1_users_id
+ FROM (SELECT users.id AS users_id
+ FROM users
+ WHERE users.name = ?) AS anon_1 JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id, addresses.id
('jack',)
.. versionadded:: 0.7.5
Default loader strategies as a new feature.
-Each of :func:`.joinedload`, :func:`.subqueryload`, :func:`.lazyload`,
+Each of :func:`.joinedload`, :func:`.subqueryload`, :func:`.lazyload`,
and :func:`.noload` can be used to set the default style of
-:func:`.relationship` loading
+:func:`.relationship` loading
for a particular query, affecting all :func:`.relationship` -mapped
attributes not otherwise
specified in the :class:`.Query`. This feature is available by passing
Above, the ``lazyload('*')`` option will supercede the ``lazy`` setting
of all :func:`.relationship` constructs in use for that query,
-except for those which use the ``'dynamic'`` style of loading.
+except for those which use the ``'dynamic'`` style of loading.
If some relationships specify
``lazy='joined'`` or ``lazy='subquery'``, for example,
using ``default_strategy(lazy='select')`` will unilaterally
cause all those relationships to use ``'select'`` loading.
The option does not supercede loader options stated in the
-query, such as :func:`.eagerload`,
+query, such as :func:`.eagerload`,
:func:`.subqueryload`, etc. The query below will still use joined loading
for the ``widget`` relationship::
session.query(MyClass).options(
- lazyload('*'),
+ lazyload('*'),
joinedload(MyClass.widget)
)
-------------------------
The philosophy behind loader strategies is that any set of loading schemes can be
-applied to a particular query, and *the results don't change* - only the number
+applied to a particular query, and *the results don't change* - only the number
of SQL statements required to fully load related objects and collections changes. A particular
query might start out using all lazy loads. After using it in context, it might be revealed
that particular attributes or collections are always accessed, and that it would be more
>>> jack = session.query(User).\
... options(joinedload(User.addresses)).\
... filter(User.name=='jack').\
- ... order_by(Address.email_address).all()
+ ... order_by(Address.email_address).all()
{opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
users.fullname AS users_fullname, users.password AS users_password
WHERE users.name = ? ORDER BY addresses.email_address <-- this part is wrong !
['jack']
-Above, ``ORDER BY addresses.email_address`` is not valid since ``addresses`` is not in the
+Above, ``ORDER BY addresses.email_address`` is not valid since ``addresses`` is not in the
FROM list. The correct way to load the ``User`` records and order by email
address is to use :meth:`.Query.join`:
>>> jack = session.query(User).\
... join(User.addresses).\
... filter(User.name=='jack').\
- ... order_by(Address.email_address).all()
+ ... order_by(Address.email_address).all()
{opensql}
SELECT users.id AS users_id, users.name AS users_name,
users.fullname AS users_fullname, users.password AS users_password
The statement above is of course not the same as the previous one, in that the columns from ``addresses``
are not included in the result at all. We can add :func:`.joinedload` back in, so that
-there are two joins - one is that which we are ordering on, the other is used anonymously to
+there are two joins - one is that which we are ordering on, the other is used anonymously to
load the contents of the ``User.addresses`` collection:
.. sourcecode:: python+sql
... join(User.addresses).\
... options(joinedload(User.addresses)).\
... filter(User.name=='jack').\
- ... order_by(Address.email_address).all()
+ ... order_by(Address.email_address).all()
{opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
users.fullname AS users_fullname, users.password AS users_password
to use in subsequent query criterion, whereas our usage of :func:`.joinedload` only concerns
itself with the loading of the ``User.addresses`` collection, for each ``User`` in the result.
In this case, the two joins most probably appear redundant - which they are. If we
-wanted to use just one JOIN for collection loading as well as ordering, we use the
-:func:`.contains_eager` option, described in :ref:`contains_eager` below. But
+wanted to use just one JOIN for collection loading as well as ordering, we use the
+:func:`.contains_eager` option, described in :ref:`contains_eager` below. But
to see why :func:`joinedload` does what it does, consider if we were **filtering** on a
particular ``Address``:
... options(joinedload(User.addresses)).\
... filter(User.name=='jack').\
... filter(Address.email_address=='someaddress@foo.com').\
- ... all()
+ ... all()
{opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
users.fullname AS users_fullname, users.password AS users_password
... options(subqueryload(User.addresses)).\
... filter(User.name=='jack').\
... filter(Address.email_address=='someaddress@foo.com').\
- ... all()
+ ... all()
{opensql}SELECT users.id AS users_id, users.name AS users_name,
users.fullname AS users_fullname, users.password AS users_password
FROM users JOIN addresses ON users.id = addresses.user_id
WHERE users.name = ? AND addresses.email_address = ?
['jack', 'someaddress@foo.com']
- # ... subqueryload() emits a SELECT in order
+ # ... subqueryload() emits a SELECT in order
# to load all address records ...
When using joined eager loading, if the
externally to the joins, such as when using DISTINCT, LIMIT, OFFSET
or equivalent, the completed statement is first
wrapped inside a subquery, and the joins used specifically for joined eager
-loading are applied to the subquery. SQLAlchemy's
-joined eager loading goes the extra mile, and then ten miles further, to
+loading are applied to the subquery. SQLAlchemy's
+joined eager loading goes the extra mile, and then ten miles further, to
absolutely ensure that it does not affect the end result of the query, only
the way collections and related objects are loaded, no matter what the format of the query is.
simple SELECT without any joins.
* When using joined loading, the load of 100 objects and their collections will emit only one SQL
- statement. However, the
- total number of rows fetched will be equal to the sum of the size of all the collections, plus one
+ statement. However, the
+ total number of rows fetched will be equal to the sum of the size of all the collections, plus one
extra row for each parent object that has an empty collection. Each row will also contain the full
set of columns represented by the parents, repeated for each collection item - SQLAlchemy does not
- re-fetch these columns other than those of the primary key, however most DBAPIs (with some
- exceptions) will transmit the full data of each parent over the wire to the client connection in
- any case. Therefore joined eager loading only makes sense when the size of the collections are
+ re-fetch these columns other than those of the primary key, however most DBAPIs (with some
+ exceptions) will transmit the full data of each parent over the wire to the client connection in
+ any case. Therefore joined eager loading only makes sense when the size of the collections are
relatively small. The LEFT OUTER JOIN can also be performance intensive compared to an INNER join.
* When using subquery loading, the load of 100 objects will emit two SQL statements. The second
statement will fetch a total number of rows equal to the sum of the size of all collections. An
- INNER JOIN is used, and a minimum of parent columns are requested, only the primary keys. So a
+ INNER JOIN is used, and a minimum of parent columns are requested, only the primary keys. So a
subquery load makes sense when the collections are larger.
* When multiple levels of depth are used with joined or subquery loading, loading collections-within-
* When using the default lazy loading, a load of 100 objects will like in the case of the collection
emit as many as 101 SQL statements. However - there is a significant exception to this, in that
if the many-to-one reference is a simple foreign key reference to the target's primary key, each
- reference will be checked first in the current identity map using :meth:`.Query.get`. So here,
+ reference will be checked first in the current identity map using :meth:`.Query.get`. So here,
if the collection of objects references a relatively small set of target objects, or the full set
of possible target objects have already been loaded into the session and are strongly referenced,
using the default of `lazy='select'` is by far the most efficient way to go.
------------------------------------------------------------------
The behavior of :func:`~sqlalchemy.orm.joinedload()` is such that joins are
-created automatically, using anonymous aliases as targets, the results of which
+created automatically, using anonymous aliases as targets, the results of which
are routed into collections and
scalar references on loaded objects. It is often the case that a query already
includes the necessary joins which represent a particular collection or scalar
of the database and DBAPI in use. In this case the SQLite dialect will interpret instructions
to the Python built-in ``sqlite3`` module.
-The :class:`.Engine` has not actually tried to connect to the database yet; that happens
+The :class:`.Engine` has not actually tried to connect to the database yet; that happens
only the first time it is asked to perform a task against the database. We can illustrate
this by asking it to perform a simple SELECT statement:
()
{stop}1
-As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the
-SQLite database, which is then used to emit the SQL. The connection is then returned to an internal
+As the :meth:`.Engine.execute` method is called, the :class:`.Engine` establishes a connection to the
+SQLite database, which is then used to emit the SQL. The connection is then returned to an internal
connection pool where it will be reused on subsequent statement executions. While we illustrate direct usage of the
:class:`.Engine` here, this isn't typically necessary when using the ORM, where the :class:`.Engine`,
once created, is used behind the scenes by the ORM as we'll see shortly.
When using the ORM, the configurational process starts by describing the database
tables we'll be dealing with, and then by defining our own classes which will
-be mapped to those tables. In modern SQLAlchemy,
+be mapped to those tables. In modern SQLAlchemy,
these two tasks are usually performed together,
using a system known as :ref:`declarative_toplevel`, which allows us to create
classes that include directives to describe the actual database table they will
be mapped to.
-Classes mapped using the Declarative system are defined in terms of a base class which
+Classes mapped using the Declarative system are defined in terms of a base class which
maintains a catalog of classes and
tables relative to that base - this is known as the **declarative base class**. Our
application will usually have just one instance of this base in a commonly
Now that we have a "base", we can define any number of mapped classes in terms
of it. We will start with just a single table called ``users``, which will store
records for the end-users using our application.
-A new class called ``User`` will be the class to which we map this table. The
+A new class called ``User`` will be the class to which we map this table. The
imports we'll need to accomplish this include objects that represent the components
-of our table, including the :class:`.Column` class which represents a database column,
-as well as the :class:`.Integer` and :class:`.String` classes that
+of our table, including the :class:`.Column` class which represents a database column,
+as well as the :class:`.Integer` and :class:`.String` classes that
represent basic datatypes used in columns::
>>> from sqlalchemy import Column, Integer, String
are of course entirely feasible as well.
We define a constructor via ``__init__()`` and also a ``__repr__()`` method - both are optional. The
-class of course can have any number of other methods and attributes as required by the application,
+class of course can have any number of other methods and attributes as required by the application,
as it's basically just a plain Python class. Inheriting from ``Base`` is also only a requirement
-of the declarative configurational system, which itself is optional and relatively open ended; at its
-core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits
+of the declarative configurational system, which itself is optional and relatively open ended; at its
+core, the SQLAlchemy ORM only requires that a class be a so-called "new style class", that is, it inherits
from ``object`` in Python 2, in order to be mapped. All classes in Python 3 are "new style" classes.
.. topic:: The Non Opinionated Philosophy
In our ``User`` mapping example, it was required that we identify the name of the table
- in use, as well as the names and characteristics of all columns which we care about,
+ in use, as well as the names and characteristics of all columns which we care about,
including which column or columns
represent the primary key, as well as some basic information about the types in use.
SQLAlchemy never makes assumptions about these decisions - the developer must
With our ``User`` class constructed via the Declarative system, we have defined information about
our table, known as **table metadata**, as well as a user-defined class which is linked to this
-table, known as a **mapped class**. Declarative has provided for us a shorthand system for what in SQLAlchemy is
+table, known as a **mapped class**. Declarative has provided for us a shorthand system for what in SQLAlchemy is
called a "Classical Mapping", which specifies these two units separately and is discussed
-in :ref:`classical_mapping`. The table
+in :ref:`classical_mapping`. The table
is actually represented by a datastructure known as :class:`.Table`, and the mapping represented
-by a :class:`.Mapper` object generated by a function called :func:`.mapper`. Declarative performs both of
+by a :class:`.Mapper` object generated by a function called :func:`.mapper`. Declarative performs both of
these steps for us, making available the
:class:`.Table` it has created via the ``__table__`` attribute::
>>> User.__table__ # doctest: +NORMALIZE_WHITESPACE
Table('users', MetaData(None),
- Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
- Column('name', String(), table=<users>),
- Column('fullname', String(), table=<users>),
+ Column('id', Integer(), table=<users>, primary_key=True, nullable=False),
+ Column('name', String(), table=<users>),
+ Column('fullname', String(), table=<users>),
Column('password', String(), table=<users>), schema=None)
and while rarely needed, making available the :class:`.Mapper` object via the ``__mapper__`` attribute::
the :class:`.MetaData` object offers is the ability to issue CREATE TABLE statements
to the database for all tables that don't yet exist. We illustrate this
by calling the :meth:`.MetaData.create_all` method, passing in our :class:`.Engine`
-as a source of database connectivity. We will see that special commands are
+as a source of database connectivity. We will see that special commands are
first emitted to check for the presence of the ``users`` table, and following that
the actual ``CREATE TABLE`` statement:
from sqlalchemy import Sequence
Column(Integer, Sequence('user_id_seq'), primary_key=True)
- A full, foolproof :class:`~sqlalchemy.schema.Table` generated via our declarative
+ A full, foolproof :class:`~sqlalchemy.schema.Table` generated via our declarative
mapping is therefore::
class User(Base):
'None'
The ``id`` attribute, which while not defined by our ``__init__()`` method,
-exists with a value of ``None`` on our ``User`` instance due to the ``id``
+exists with a value of ``None`` on our ``User`` instance due to the ``id``
column we declared in our mapping. By
default, the ORM creates class attributes for all columns present
in the table being mapped. These class attributes exist as
-`Python descriptors <http://docs.python.org/howto/descriptor.html>`_, and
+`Python descriptors <http://docs.python.org/howto/descriptor.html>`_, and
define **instrumentation** for the mapped class. The
functionality of this instrumentation includes the ability to fire on change
events, track modifications, and to automatically load new data from the database when
The business of acquiring a :class:`.Session` has a good deal of variety based
on the variety of types of applications and frameworks out there.
Keep in mind the :class:`.Session` is just a workspace for your objects,
- local to a particular database connection - if you think of
+ local to a particular database connection - if you think of
an application thread as a guest at a dinner party, the :class:`.Session`
- is the guest's plate and the objects it holds are the food
- (and the database...the kitchen?)! Hints on
+ is the guest's plate and the objects it holds are the food
+ (and the database...the kitchen?)! Hints on
how :class:`.Session` is integrated into an application are at
:ref:`session_faq`.
BEGIN (implicit)
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
('ed', 'Ed Jones', 'edspassword')
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name = ?
>>> ed_user is our_user
True
-The ORM concept at work here is known as an `identity map <http://martinfowler.com/eaaCatalog/identityMap.html>`_
+The ORM concept at work here is known as an `identity map <http://martinfowler.com/eaaCatalog/identityMap.html>`_
and ensures that
all operations upon a particular row within a
:class:`~sqlalchemy.orm.session.Session` operate upon the same set of data.
{sql}>>> ed_user.id # doctest: +NORMALIZE_WHITESPACE
BEGIN (implicit)
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.id = ?
because a new transaction was begun after we issued :meth:`~.Session.commit`. SQLAlchemy
by default refreshes data from a previous transaction the first time it's
accessed within a new transaction, so that the most recent state is available.
-The level of reloading is configurable as is described in :ref:`session_toplevel`.
+The level of reloading is configurable as is described in :doc:`/orm/session`.
.. topic:: Session Object States
inside the :class:`.Session` without a primary key, to actually being
inserted, it moved between three out of four
available "object states" - **transient**, **pending**, and **persistent**.
- Being aware of these states and what they mean is always a good idea -
+ Being aware of these states and what they mean is always a good idea -
be sure to read :ref:`session_object_states` for a quick overview.
Rolling Back
('Edwardo', 1)
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
('fakeuser', 'Invalid', '12345')
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name IN (?, ?)
{sql}>>> ed_user.name #doctest: +NORMALIZE_WHITESPACE
BEGIN (implicit)
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.id = ?
.. sourcecode:: python+sql
{sql}>>> session.query(User).filter(User.name.in_(['ed', 'fakeuser'])).all() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name IN (?, ?)
{sql}>>> for instance in session.query(User).order_by(User.id): # doctest: +NORMALIZE_WHITESPACE
... print instance.name, instance.fullname
- SELECT users.id AS users_id,
+ SELECT users.id AS users_id,
users.name AS users_name,
- users.fullname AS users_fullname,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users ORDER BY users.id
()
{sql}>>> for name, fullname in session.query(User.name, User.fullname): # doctest: +NORMALIZE_WHITESPACE
... print name, fullname
- SELECT users.name AS users_name,
+ SELECT users.name AS users_name,
users.fullname AS users_fullname
FROM users
()
{sql}>>> for row in session.query(User, User.name).all(): #doctest: +NORMALIZE_WHITESPACE
... print row.User, row.name
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
()
{sql}>>> for row in session.query(user_alias, user_alias.name).all(): #doctest: +NORMALIZE_WHITESPACE
... print row.user_alias
- SELECT user_alias.id AS user_alias_id,
- user_alias.name AS user_alias_name,
- user_alias.fullname AS user_alias_fullname,
+ SELECT user_alias.id AS user_alias_id,
+ user_alias.name AS user_alias_name,
+ user_alias.fullname AS user_alias_fullname,
user_alias.password AS user_alias_password
FROM users AS user_alias
(){stop}
{sql}>>> for u in session.query(User).order_by(User.id)[1:3]: #doctest: +NORMALIZE_WHITESPACE
... print u
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users ORDER BY users.id
LIMIT ? OFFSET ?
... filter(User.name=='ed').\
... filter(User.fullname=='Ed Jones'): # doctest: +NORMALIZE_WHITESPACE
... print user
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name = ? AND users.fullname = ?
>>> query = session.query(User).filter(User.name.like('%ed')).order_by(User.id)
{sql}>>> query.all() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name LIKE ? ORDER BY users.id
.. sourcecode:: python+sql
{sql}>>> query.first() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name LIKE ? ORDER BY users.id
... user = query.one()
... except MultipleResultsFound, e:
... print e
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name LIKE ? ORDER BY users.id
... user = query.filter(User.id == 99).one()
... except NoResultFound, e:
... print e
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name LIKE ? AND users.id = ? ORDER BY users.id
... filter("id<224").\
... order_by("id").all(): #doctest: +NORMALIZE_WHITESPACE
... print user.name
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE id<224 ORDER BY id
{sql}>>> session.query(User).filter("id<:value and name=:name").\
... params(value=224, name='fred').order_by(User.id).one() # doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE id<? and name=? ORDER BY users.id
:class:`.Query` is constructed like the rest of SQLAlchemy, in that it tries
to always allow "falling back" to a less automated, lower level approach to things.
- Accepting strings for all SQL fragments is a big part of that, so that
+ Accepting strings for all SQL fragments is a big part of that, so that
you can bypass the need to organize SQL constructs if you know specifically
what string output you'd like.
But when using literal strings, the :class:`.Query` no longer knows anything about
- that part of the SQL construct being emitted, and has no ability to
+ that part of the SQL construct being emitted, and has no ability to
**transform** it to adapt to new contexts.
For example, suppose we selected ``User`` objects and ordered by the ``name``
>>> q = session.query(User.id, User.name)
{sql}>>> q.order_by("name").all()
- SELECT users.id AS users_id, users.name AS users_name
+ SELECT users.id AS users_id, users.name AS users_name
FROM users ORDER BY name
()
{stop}[(1, u'ed'), (4, u'fred'), (3, u'mary'), (2, u'wendy')]
Perfectly fine. But suppose, before we got a hold of the :class:`.Query`,
some sophisticated transformations were applied to it, such as below
where we use :meth:`~.Query.from_self`, a particularly advanced
- method, to retrieve pairs of user names with
+ method, to retrieve pairs of user names with
different numbers of characters::
>>> from sqlalchemy import func
... filter(User.name < ua.name).\
... filter(func.length(ua.name) != func.length(User.name))
- The :class:`.Query` now represents a select from a subquery, where
+ The :class:`.Query` now represents a select from a subquery, where
``User`` is represented twice both inside and outside of the subquery.
Telling the :class:`.Query` to order by "name" doesn't really give
- us much guarantee which "name" it's going to order on. In this
+ us much guarantee which "name" it's going to order on. In this
case it assumes "name" is against the outer "aliased" ``User`` construct:
.. sourcecode:: python+sql
{sql}>>> q.order_by("name").all() #doctest: +NORMALIZE_WHITESPACE
- SELECT anon_1.users_id AS anon_1_users_id,
- anon_1.users_name AS anon_1_users_name,
- users_1.name AS users_1_name
- FROM (SELECT users.id AS users_id, users.name AS users_name
- FROM users) AS anon_1, users AS users_1
- WHERE anon_1.users_name < users_1.name
- AND length(users_1.name) != length(anon_1.users_name)
+ SELECT anon_1.users_id AS anon_1_users_id,
+ anon_1.users_name AS anon_1_users_name,
+ users_1.name AS users_1_name
+ FROM (SELECT users.id AS users_id, users.name AS users_name
+ FROM users) AS anon_1, users AS users_1
+ WHERE anon_1.users_name < users_1.name
+ AND length(users_1.name) != length(anon_1.users_name)
ORDER BY name
()
{stop}[(1, u'ed', u'fred'), (1, u'ed', u'mary'), (1, u'ed', u'wendy'), (3, u'mary', u'wendy'), (4, u'fred', u'wendy')]
Only if we use the SQL element directly, in this case ``User.name``
- or ``ua.name``, do we give :class:`.Query` enough information to know
+ or ``ua.name``, do we give :class:`.Query` enough information to know
for sure which "name" we'd like to order on, where we can see we get different results
for each:
.. sourcecode:: python+sql
{sql}>>> q.order_by(ua.name).all() #doctest: +NORMALIZE_WHITESPACE
- SELECT anon_1.users_id AS anon_1_users_id,
- anon_1.users_name AS anon_1_users_name,
- users_1.name AS users_1_name
- FROM (SELECT users.id AS users_id, users.name AS users_name
- FROM users) AS anon_1, users AS users_1
- WHERE anon_1.users_name < users_1.name
- AND length(users_1.name) != length(anon_1.users_name)
+ SELECT anon_1.users_id AS anon_1_users_id,
+ anon_1.users_name AS anon_1_users_name,
+ users_1.name AS users_1_name
+ FROM (SELECT users.id AS users_id, users.name AS users_name
+ FROM users) AS anon_1, users AS users_1
+ WHERE anon_1.users_name < users_1.name
+ AND length(users_1.name) != length(anon_1.users_name)
ORDER BY users_1.name
()
{stop}[(1, u'ed', u'fred'), (1, u'ed', u'mary'), (1, u'ed', u'wendy'), (3, u'mary', u'wendy'), (4, u'fred', u'wendy')]
{sql}>>> q.order_by(User.name).all() #doctest: +NORMALIZE_WHITESPACE
- SELECT anon_1.users_id AS anon_1_users_id,
- anon_1.users_name AS anon_1_users_name,
- users_1.name AS users_1_name
- FROM (SELECT users.id AS users_id, users.name AS users_name
- FROM users) AS anon_1, users AS users_1
- WHERE anon_1.users_name < users_1.name
- AND length(users_1.name) != length(anon_1.users_name)
+ SELECT anon_1.users_id AS anon_1_users_id,
+ anon_1.users_name AS anon_1_users_name,
+ users_1.name AS users_1_name
+ FROM (SELECT users.id AS users_id, users.name AS users_name
+ FROM users) AS anon_1, users AS users_1
+ WHERE anon_1.users_name < users_1.name
+ AND length(users_1.name) != length(anon_1.users_name)
ORDER BY anon_1.users_name
()
{stop}[(1, u'ed', u'wendy'), (1, u'ed', u'mary'), (1, u'ed', u'fred'), (4, u'fred', u'wendy'), (3, u'mary', u'wendy')]
.. sourcecode:: python+sql
{sql}>>> session.query(User).filter(User.name.like('%ed')).count() #doctest: +NORMALIZE_WHITESPACE
- SELECT count(*) AS count_1
- FROM (SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
- users.password AS users_password
- FROM users
+ SELECT count(*) AS count_1
+ FROM (SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM users
WHERE users.name LIKE ?) AS anon_1
('%ed',)
{stop}2
The :meth:`~.Query.count()` method is used to determine
how many rows the SQL statement would return. Looking
-at the generated SQL above, SQLAlchemy always places whatever it is we are
+at the generated SQL above, SQLAlchemy always places whatever it is we are
querying into a subquery, then counts the rows from that. In some cases
this can be reduced to a simpler ``SELECT count(*) FROM table``, however
modern versions of SQLAlchemy don't try to guess when this is appropriate,
as the exact SQL can be emitted using more explicit means.
For situations where the "thing to be counted" needs
-to be indicated specifically, we can specify the "count" function
+to be indicated specifically, we can specify the "count" function
directly using the expression ``func.count()``, available from the
:attr:`~sqlalchemy.sql.expression.func` construct. Below we
use it to return the count of each distinct user name:
.. sourcecode:: python+sql
{sql}>>> session.query(func.count('*')).select_from(User).scalar()
- SELECT count(?) AS count_1
+ SELECT count(?) AS count_1
FROM users
('*',)
{stop}4
=======================
Let's consider how a second table, related to ``User``, can be mapped and
-queried. Users in our system
+queried. Users in our system
can store any number of email addresses associated with their username. This
implies a basic one to many association from the ``users`` to a new
table which stores email addresses, which we will call ``addresses``. Using
A second directive, known as :func:`.relationship`,
tells the ORM that the ``Address`` class itself should be linked
-to the ``User`` class, using the attribute ``Address.user``.
+to the ``User`` class, using the attribute ``Address.user``.
:func:`.relationship` uses the foreign key
relationships between the two tables to determine the nature of
this linkage, determining that ``Address.user`` will be **many-to-one**.
feature of the SQLAlchemy ORM. The section :ref:`relationships_backref`
discusses the "backref" feature in detail.
-Arguments to :func:`.relationship` which concern the remote class
-can be specified using strings, assuming the Declarative system is in
+Arguments to :func:`.relationship` which concern the remote class
+can be specified using strings, assuming the Declarative system is in
use. Once all mappings are complete, these strings are evaluated
-as Python expressions in order to produce the actual argument, in the
-above case the ``User`` class. The names which are allowed during
+as Python expressions in order to produce the actual argument, in the
+above case the ``User`` class. The names which are allowed during
this evaluation include, among other things, the names of all classes
which have been created in terms of the declared base. Below we illustrate creation
-of the same "addresses/user" bidirectional relationship in terms of ``User`` instead of
+of the same "addresses/user" bidirectional relationship in terms of ``User`` instead of
``Address``::
class User(Base):
.. topic:: Did you know ?
- * a FOREIGN KEY constraint in most (though not all) relational databases can
+ * a FOREIGN KEY constraint in most (though not all) relational databases can
only link to a primary key column, or a column that has a UNIQUE constraint.
* a FOREIGN KEY constraint that refers to a multiple column primary key, and itself
has multiple columns, is known as a "composite foreign key". It can also
.. sourcecode:: python+sql
>>> jack.addresses = [
- ... Address(email_address='jack@google.com'),
+ ... Address(email_address='jack@google.com'),
... Address(email_address='j25@yahoo.com')]
When using a bidirectional relationship, elements added in one direction
{sql}>>> jack = session.query(User).\
... filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
BEGIN (implicit)
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name = ?
.. sourcecode:: python+sql
{sql}>>> jack.addresses #doctest: +NORMALIZE_WHITESPACE
- SELECT addresses.id AS addresses_id,
- addresses.email_address AS
- addresses_email_address,
+ SELECT addresses.id AS addresses_id,
+ addresses.email_address AS
+ addresses_email_address,
addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id ORDER BY addresses.id
Now that we have two tables, we can show some more features of :class:`.Query`,
specifically how to create queries that deal with both tables at the same time.
-The `Wikipedia page on SQL JOIN
-<http://en.wikipedia.org/wiki/Join_%28SQL%29>`_ offers a good introduction to
+The `Wikipedia page on SQL JOIN
+<http://en.wikipedia.org/wiki/Join_%28SQL%29>`_ offers a good introduction to
join techniques, several of which we'll illustrate here.
To construct a simple implicit join between ``User`` and ``Address``,
-we can use :meth:`.Query.filter()` to equate their related columns together.
+we can use :meth:`.Query.filter()` to equate their related columns together.
Below we load the ``User`` and ``Address`` entities at once using this method:
.. sourcecode:: python+sql
... filter(Address.email_address=='jack@google.com').\
... all(): # doctest: +NORMALIZE_WHITESPACE
... print u, a
- SELECT users.id AS users_id,
- users.name AS users_name,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
users.fullname AS users_fullname,
- users.password AS users_password,
+ users.password AS users_password,
addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
+ addresses.email_address AS addresses_email_address,
addresses.user_id AS addresses_user_id
FROM users, addresses
- WHERE users.id = addresses.user_id
+ WHERE users.id = addresses.user_id
AND addresses.email_address = ?
('jack@google.com',)
{stop}<User('jack','Jack Bean', 'gjffdd')> <Address('jack@google.com')>
{sql}>>> session.query(User).join(Address).\
... filter(Address.email_address=='jack@google.com').\
... all() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users JOIN addresses ON users.id = addresses.user_id
WHERE addresses.email_address = ?
query.join(Address, User.addresses) # same, with explicit target
query.join('addresses') # same, using a string
-As you would expect, the same idea is used for "outer" joins, using the
+As you would expect, the same idea is used for "outer" joins, using the
:meth:`~.Query.outerjoin` function::
query.outerjoin(User.addresses) # LEFT OUTER JOIN
... filter(adalias1.email_address=='jack@google.com').\
... filter(adalias2.email_address=='j25@yahoo.com'):
... print username, email1, email2 # doctest: +NORMALIZE_WHITESPACE
- SELECT users.name AS users_name,
+ SELECT users.name AS users_name,
addresses_1.email_address AS addresses_1_email_address,
addresses_2.email_address AS addresses_2_email_address
- FROM users JOIN addresses AS addresses_1
+ FROM users JOIN addresses AS addresses_1
ON users.id = addresses_1.user_id
- JOIN addresses AS addresses_2
+ JOIN addresses AS addresses_2
ON users.id = addresses_2.user_id
- WHERE addresses_1.email_address = ?
+ WHERE addresses_1.email_address = ?
AND addresses_2.email_address = ?
('jack@google.com', 'j25@yahoo.com')
{stop}jack jack@google.com j25@yahoo.com
get rows back for those users who don't have any addresses, e.g.::
SELECT users.*, adr_count.address_count FROM users LEFT OUTER JOIN
- (SELECT user_id, count(*) AS address_count
+ (SELECT user_id, count(*) AS address_count
FROM addresses GROUP BY user_id) AS adr_count
ON users.id=adr_count.user_id
{sql}>>> for u, count in session.query(User, stmt.c.address_count).\
... outerjoin(stmt, User.id==stmt.c.user_id).order_by(User.id): # doctest: +NORMALIZE_WHITESPACE
... print u, count
- SELECT users.id AS users_id,
+ SELECT users.id AS users_id,
users.name AS users_name,
- users.fullname AS users_fullname,
+ users.fullname AS users_fullname,
users.password AS users_password,
anon_1.address_count AS anon_1_address_count
- FROM users LEFT OUTER JOIN
+ FROM users LEFT OUTER JOIN
(SELECT addresses.user_id AS user_id, count(?) AS address_count
- FROM addresses GROUP BY addresses.user_id) AS anon_1
+ FROM addresses GROUP BY addresses.user_id) AS anon_1
ON users.id = anon_1.user_id
ORDER BY users.id
('*',)
>>> for user, address in session.query(User, adalias).\
... join(adalias, User.addresses): # doctest: +NORMALIZE_WHITESPACE
... print user, address
- SELECT users.id AS users_id,
- users.name AS users_name,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
users.fullname AS users_fullname,
- users.password AS users_password,
+ users.password AS users_password,
anon_1.id AS anon_1_id,
- anon_1.email_address AS anon_1_email_address,
+ anon_1.email_address AS anon_1_email_address,
anon_1.user_id AS anon_1_user_id
- FROM users JOIN
- (SELECT addresses.id AS id,
- addresses.email_address AS email_address,
+ FROM users JOIN
+ (SELECT addresses.id AS id,
+ addresses.email_address AS email_address,
addresses.user_id AS user_id
FROM addresses
- WHERE addresses.email_address != ?) AS anon_1
+ WHERE addresses.email_address != ?) AS anon_1
ON users.id = anon_1.user_id
('j25@yahoo.com',)
{stop}<User('jack','Jack Bean', 'gjffdd')> <Address('jack@google.com')>
{sql}>>> session.query(Address).\
... filter(~Address.user.has(User.name=='jack')).all() # doctest: +NORMALIZE_WHITESPACE
- SELECT addresses.id AS addresses_id,
+ SELECT addresses.id AS addresses_id,
addresses.email_address AS addresses_email_address,
addresses.user_id AS addresses_user_id
FROM addresses
Recall earlier that we illustrated a **lazy loading** operation, when
we accessed the ``User.addresses`` collection of a ``User`` and SQL
-was emitted. If you want to reduce the number of queries (dramatically, in many cases),
+was emitted. If you want to reduce the number of queries (dramatically, in many cases),
we can apply an **eager load** to the query operation. SQLAlchemy
offers three types of eager loading, two of which are automatic, and a third
which involves custom criterion. All three are usually invoked via functions known
A good choice for loading a set of objects as well as their related collections
is the :func:`.orm.subqueryload` option, which emits a second SELECT statement
that fully loads the collections associated with the results just loaded.
-The name "subquery" originates from the fact that the SELECT statement
+The name "subquery" originates from the fact that the SELECT statement
constructed directly via the :class:`.Query` is re-used, embedded as a subquery
-into a SELECT against the related table. This is a little elaborate but
+into a SELECT against the related table. This is a little elaborate but
very easy to use:
.. sourcecode:: python+sql
{sql}>>> jack = session.query(User).\
... options(subqueryload(User.addresses)).\
... filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
- users.password AS users_password
- FROM users
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM users
WHERE users.name = ?
('jack',)
- SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id,
- anon_1.users_id AS anon_1_users_id
- FROM (SELECT users.id AS users_id
- FROM users WHERE users.name = ?) AS anon_1
- JOIN addresses ON anon_1.users_id = addresses.user_id
+ SELECT addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id,
+ anon_1.users_id AS anon_1_users_id
+ FROM (SELECT users.id AS users_id
+ FROM users WHERE users.name = ?) AS anon_1
+ JOIN addresses ON anon_1.users_id = addresses.user_id
ORDER BY anon_1.users_id, addresses.id
('jack',)
{stop}>>> jack
a LEFT OUTER JOIN, so that the lead object as well as the related object
or collection is loaded in one step. We illustrate loading the same
``addresses`` collection in this way - note that even though the ``User.addresses``
-collection on ``jack`` is actually populated right now, the query
+collection on ``jack`` is actually populated right now, the query
will emit the extra join regardless:
.. sourcecode:: python+sql
{sql}>>> jack = session.query(User).\
... options(joinedload(User.addresses)).\
... filter_by(name='jack').one() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
users.fullname AS users_fullname,
- users.password AS users_password,
- addresses_1.id AS addresses_1_id,
- addresses_1.email_address AS addresses_1_email_address,
+ users.password AS users_password,
+ addresses_1.id AS addresses_1_id,
+ addresses_1.email_address AS addresses_1_email_address,
addresses_1.user_id AS addresses_1_user_id
- FROM users
+ FROM users
LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
WHERE users.name = ? ORDER BY addresses_1.id
('jack',)
The join created by :func:`.joinedload` is anonymously aliased such that
it **does not affect the query results**. An :meth:`.Query.order_by`
or :meth:`.Query.filter` call **cannot** reference these aliased
- tables - so-called "user space" joins are constructed using
+ tables - so-called "user space" joins are constructed using
:meth:`.Query.join`. The rationale for this is that :func:`.joinedload` is only
applied in order to affect how related objects or collections are loaded
as an optimizing detail - it can be added or removed with no impact
- on actual results. See the section :ref:`zen_of_eager_loading` for
+ on actual results. See the section :ref:`zen_of_eager_loading` for
a detailed description of how this is used.
Explicit Join + Eagerload
... filter(User.name=='jack').\
... options(contains_eager(Address.user)).\
... all() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
- users.password AS users_password,
- addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id
- FROM addresses JOIN users ON users.id = addresses.user_id
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password,
+ addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses JOIN users ON users.id = addresses.user_id
WHERE users.name = ?
('jack',)
<User('jack','Jack Bean', 'gjffdd')>
For more information on eager loading, including how to configure various forms
-of loading by default, see the section :ref:`loading_toplevel`.
+of loading by default, see the section :doc:`/orm/loading`.
Deleting
========
(None, 2)
DELETE FROM users WHERE users.id = ?
(5,)
- SELECT count(*) AS count_1
- FROM (SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
- users.password AS users_password
- FROM users
+ SELECT count(*) AS count_1
+ FROM (SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM users
WHERE users.name = ?) AS anon_1
('jack',)
{stop}0
{sql}>>> session.query(Address).filter(
... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
... ).count() # doctest: +NORMALIZE_WHITESPACE
- SELECT count(*) AS count_1
- FROM (SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id
- FROM addresses
+ SELECT count(*) AS count_1
+ FROM (SELECT addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses
WHERE addresses.email_address IN (?, ?)) AS anon_1
('jack@google.com', 'j25@yahoo.com')
{stop}2
# load Jack by primary key
{sql}>>> jack = session.query(User).get(5) #doctest: +NORMALIZE_WHITESPACE
BEGIN (implicit)
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.id = ?
# remove one Address (lazy load fires off)
{sql}>>> del jack.addresses[1] #doctest: +NORMALIZE_WHITESPACE
- SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
+ SELECT addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id
... ).count() # doctest: +NORMALIZE_WHITESPACE
DELETE FROM addresses WHERE addresses.id = ?
(2,)
- SELECT count(*) AS count_1
- FROM (SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id
- FROM addresses
+ SELECT count(*) AS count_1
+ FROM (SELECT addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses
WHERE addresses.email_address IN (?, ?)) AS anon_1
('jack@google.com', 'j25@yahoo.com')
{stop}1
(1,)
DELETE FROM users WHERE users.id = ?
(5,)
- SELECT count(*) AS count_1
- FROM (SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
- users.password AS users_password
- FROM users
+ SELECT count(*) AS count_1
+ FROM (SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
+ users.password AS users_password
+ FROM users
WHERE users.name = ?) AS anon_1
('jack',)
{stop}0
{sql}>>> session.query(Address).filter(
... Address.email_address.in_(['jack@google.com', 'j25@yahoo.com'])
... ).count() # doctest: +NORMALIZE_WHITESPACE
- SELECT count(*) AS count_1
- FROM (SELECT addresses.id AS addresses_id,
- addresses.email_address AS addresses_email_address,
- addresses.user_id AS addresses_user_id
- FROM addresses
+ SELECT count(*) AS count_1
+ FROM (SELECT addresses.id AS addresses_id,
+ addresses.email_address AS addresses_email_address,
+ addresses.user_id AS addresses_user_id
+ FROM addresses
WHERE addresses.email_address IN (?, ?)) AS anon_1
('jack@google.com', 'j25@yahoo.com')
{stop}0
.. topic:: More on Cascades
Further detail on configuration of cascades is at :ref:`unitofwork_cascades`.
- The cascade functionality can also integrate smoothly with
+ The cascade functionality can also integrate smoothly with
the ``ON DELETE CASCADE`` functionality of the relational database.
See :ref:`passive_deletes` for details.
Above, we can see declaring a :class:`.Table` directly is a little different
than declaring a mapped class. :class:`.Table` is a constructor function, so
-each individual :class:`.Column` argument is separated by a comma. The
+each individual :class:`.Column` argument is separated by a comma. The
:class:`.Column` object is also given its name explicitly, rather than it being
taken from an assigned attribute name.
{sql}>>> wendy = session.query(User).\
... filter_by(name='wendy').\
... one() #doctest: +NORMALIZE_WHITESPACE
- SELECT users.id AS users_id,
- users.name AS users_name,
- users.fullname AS users_fullname,
+ SELECT users.id AS users_id,
+ users.name AS users_name,
+ users.fullname AS users_fullname,
users.password AS users_password
FROM users
WHERE users.name = ?
(2, "Wendy's Blog Post", 'This is a test')
INSERT INTO post_keywords (post_id, keyword_id) VALUES (?, ?)
((1, 1), (1, 2))
- SELECT posts.id AS posts_id,
- posts.user_id AS posts_user_id,
- posts.headline AS posts_headline,
+ SELECT posts.id AS posts_id,
+ posts.user_id AS posts_user_id,
+ posts.headline AS posts_headline,
posts.body AS posts_body
FROM posts
WHERE EXISTS (SELECT 1
FROM post_keywords, keywords
- WHERE posts.id = post_keywords.post_id
- AND keywords.id = post_keywords.keyword_id
+ WHERE posts.id = post_keywords.post_id
+ AND keywords.id = post_keywords.keyword_id
AND keywords.keyword = ?)
('firstpost',)
{stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
... filter(BlogPost.author==wendy).\
... filter(BlogPost.keywords.any(keyword='firstpost')).\
... all() #doctest: +NORMALIZE_WHITESPACE
- SELECT posts.id AS posts_id,
- posts.user_id AS posts_user_id,
- posts.headline AS posts_headline,
+ SELECT posts.id AS posts_id,
+ posts.user_id AS posts_user_id,
+ posts.headline AS posts_headline,
posts.body AS posts_body
FROM posts
WHERE ? = posts.user_id AND (EXISTS (SELECT 1
FROM post_keywords, keywords
- WHERE posts.id = post_keywords.post_id
- AND keywords.id = post_keywords.keyword_id
+ WHERE posts.id = post_keywords.post_id
+ AND keywords.id = post_keywords.keyword_id
AND keywords.keyword = ?))
(2, 'firstpost')
{stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
{sql}>>> wendy.posts.\
... filter(BlogPost.keywords.any(keyword='firstpost')).\
... all() #doctest: +NORMALIZE_WHITESPACE
- SELECT posts.id AS posts_id,
- posts.user_id AS posts_user_id,
- posts.headline AS posts_headline,
+ SELECT posts.id AS posts_id,
+ posts.user_id AS posts_user_id,
+ posts.headline AS posts_headline,
posts.body AS posts_body
FROM posts
WHERE ? = posts.user_id AND (EXISTS (SELECT 1
FROM post_keywords, keywords
- WHERE posts.id = post_keywords.post_id
- AND keywords.id = post_keywords.keyword_id
+ WHERE posts.id = post_keywords.post_id
+ AND keywords.id = post_keywords.keyword_id
AND keywords.keyword = ?))
(2, 'firstpost')
{stop}[BlogPost("Wendy's Blog Post", 'This is a test', <User('wendy','Wendy Williams', 'foobar')>)]
Relationship Reference: :ref:`relationship_config_toplevel`
-Session Reference: :ref:`session_toplevel`
+Session Reference: :doc:`/orm/session`