~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to doc/_sources/orm/loading.txt

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2011-08-01 23:18:16 UTC
  • mfrom: (1.4.15 upstream) (16.1.14 experimental)
  • Revision ID: james.westby@ubuntu.com-20110801231816-6lx797pi3q1fpqst
Tags: 0.7.2-1
* New upstream release
* Bump minimum required python-mako version to 0.4.1 (closes: 635898)

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
 
47
47
.. sourcecode:: python+sql
48
48
 
49
 
    {sql}>>> jack = session.query(User).options(joinedload('addresses')).filter_by(name='jack').all() #doctest: +NORMALIZE_WHITESPACE
 
49
    {sql}>>> jack = session.query(User).\
 
50
    ... options(joinedload('addresses')).\
 
51
    ... filter_by(name='jack').all() #doctest: +NORMALIZE_WHITESPACE
50
52
    SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
51
53
    addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
52
54
    users.fullname AS users_fullname, users.password AS users_password
62
64
 
63
65
.. sourcecode:: python+sql
64
66
 
65
 
    {sql}>>>jack = session.query(User).options(subqueryload('addresses')).filter_by(name='jack').all() 
 
67
    {sql}>>> jack = session.query(User).\
 
68
    ... options(subqueryload('addresses')).\
 
69
    ... filter_by(name='jack').all() 
66
70
    SELECT users.id AS users_id, users.name AS users_name, users.fullname AS users_fullname, 
67
71
    users.password AS users_password 
68
72
    FROM users 
122
126
    session.query(Parent).options(joinedload('foo.bar.bat')).all()
123
127
 
124
128
When using dot-separated names with :func:`~sqlalchemy.orm.joinedload` or
125
 
:func:`~sqlalchemy.orm.subqueryload`, option applies **only** to the actual
 
129
:func:`~sqlalchemy.orm.subqueryload`, the option applies **only** to the actual
126
130
attribute named, and **not** its ancestors. For example, suppose a mapping
127
131
from ``A`` to ``B`` to ``C``, where the relationships, named ``atob`` and
128
132
``btoc``, are both lazy-loading. A statement like the following:
131
135
 
132
136
    session.query(A).options(joinedload('atob.btoc')).all()
133
137
 
134
 
will load only ``A`` objects to start.  When the ``atob`` attribute on each ``A`` is accessed, the returned ``B`` objects will *eagerly* load their ``C`` objects.
 
138
will load only ``A`` objects to start. When the ``atob`` attribute on each
 
139
``A`` is accessed, the returned ``B`` objects will *eagerly* load their ``C``
 
140
objects.
135
141
 
136
 
Therefore, to modify the eager load to load both ``atob`` as well as ``btoc``, place joinedloads for both:
 
142
Therefore, to modify the eager load to load both ``atob`` as well as ``btoc``,
 
143
place joinedloads for both:
137
144
 
138
145
.. sourcecode:: python+sql
139
146
 
140
147
    session.query(A).options(joinedload('atob'), joinedload('atob.btoc')).all()
141
148
 
142
 
or more simply just use :func:`~sqlalchemy.orm.joinedload_all` or :func:`~sqlalchemy.orm.subqueryload_all`:
 
149
or more simply just use :func:`~sqlalchemy.orm.joinedload_all` or
 
150
:func:`~sqlalchemy.orm.subqueryload_all`:
143
151
 
144
152
.. sourcecode:: python+sql
145
153
 
146
154
    session.query(A).options(joinedload_all('atob.btoc')).all()
147
155
 
148
 
There are two other loader strategies available, **dynamic loading** and **no loading**; these are described in :ref:`largecollections`.
 
156
There are two other loader strategies available, **dynamic loading** and **no
 
157
loading**; these are described in :ref:`largecollections`.
 
158
 
 
159
.. _zen_of_eager_loading:
149
160
 
150
161
The Zen of Eager Loading
151
162
-------------------------
160
171
In theory (and pretty much in practice), nothing you can do to the :class:`.Query` would make it load
161
172
a different set of primary or related objects based on a change in loader strategy.
162
173
 
163
 
The way eagerloading does this, and in particular how :func:`joinedload`
164
 
works, is that it creates an anonymous alias of all the joins it adds to your
165
 
query, so that they can't be referenced by other parts of the query. If the
166
 
query contains a DISTINCT, or a limit or offset, the statement is first
167
 
wrapped inside a subquery, and joins are applied to that. As the user, you
168
 
don't have access to these aliases or subqueries, and you cannot affect what
169
 
data they will load at query time - a typical beginner misunderstanding is
170
 
that adding a :meth:`.Query.order_by`, naming the joined relationship, would
171
 
change the order of the collection, or that the entries in the collection as
172
 
it is loaded could be affected by :meth:`.Query.filter`. Not the case ! If
173
 
you'd like to join from one table to another, filtering or ordering on the
174
 
joined result, you'd use :meth:`.Query.join`. If you then wanted that joined
175
 
result to populate itself into a related collection, this is also available,
176
 
via :func:`.contains_eager` option - see :ref:`contains_eager`.
 
174
How :func:`joinedload` in particular achieves this result of not impacting
 
175
entity rows returned in any way is that it creates an anonymous alias of the joins it adds to your
 
176
query, so that they can't be referenced by other parts of the query.   For example,
 
177
the query below uses :func:`.joinedload` to create a LEFT OUTER JOIN from ``users``
 
178
to ``addresses``, however the ``ORDER BY`` added against ``Address.email_address``
 
179
is not valid - the ``Address`` entity is not named in the query:
 
180
 
 
181
.. sourcecode:: python+sql
 
182
 
 
183
    >>> jack = session.query(User).\
 
184
    ... options(joinedload(User.addresses)).\
 
185
    ... filter(User.name=='jack').\
 
186
    ... order_by(Address.email_address).all() 
 
187
    {opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
 
188
    addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
 
189
    users.fullname AS users_fullname, users.password AS users_password
 
190
    FROM users LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
 
191
    WHERE users.name = ? ORDER BY addresses.email_address   <-- this part is wrong !
 
192
    ['jack']
 
193
 
 
194
Above, ``ORDER BY addresses.email_address`` is not valid since ``addresses`` is not in the 
 
195
FROM list.   The correct way to load the ``User`` records and order by email
 
196
address is to use :meth:`.Query.join`:
 
197
 
 
198
.. sourcecode:: python+sql
 
199
 
 
200
    >>> jack = session.query(User).\
 
201
    ... join(User.addresses).\
 
202
    ... filter(User.name=='jack').\
 
203
    ... order_by(Address.email_address).all() 
 
204
    {opensql}
 
205
    SELECT users.id AS users_id, users.name AS users_name,
 
206
    users.fullname AS users_fullname, users.password AS users_password
 
207
    FROM users JOIN addresses ON users.id = addresses.user_id
 
208
    WHERE users.name = ? ORDER BY addresses.email_address
 
209
    ['jack']
 
210
 
 
211
The statement above is of course not the same as the previous one, in that the columns from ``addresses``
 
212
are not included in the result at all.   We can add :func:`.joinedload` back in, so that
 
213
there are two joins - one is that which we are ordering on, the other is used anonymously to 
 
214
load the contents of the ``User.addresses`` collection:
 
215
 
 
216
.. sourcecode:: python+sql
 
217
 
 
218
    >>> jack = session.query(User).\
 
219
    ... join(User.addresses).\
 
220
    ... options(joinedload(User.addresses)).\
 
221
    ... filter(User.name=='jack').\
 
222
    ... order_by(Address.email_address).all() 
 
223
    {opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
 
224
    addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
 
225
    users.fullname AS users_fullname, users.password AS users_password
 
226
    FROM users JOIN addresses ON users.id = addresses.user_id
 
227
    LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
 
228
    WHERE users.name = ? ORDER BY addresses.email_address
 
229
    ['jack']
 
230
 
 
231
What we see above is that our usage of :meth:`.Query.join` is to supply JOIN clauses we'd like
 
232
to use in subsequent query criterion, whereas our usage of :func:`.joinedload` only concerns
 
233
itself with the loading of the ``User.addresses`` collection, for each ``User`` in the result.
 
234
In this case, the two joins most probably appear redundant - which they are.  If we
 
235
wanted to use just one JOIN for collection loading as well as ordering, we use the 
 
236
:func:`.contains_eager` option, described in :ref:`contains_eager` below.   But 
 
237
to see why :func:`joinedload` does what it does, consider if we were **filtering** on a
 
238
particular ``Address``:
 
239
 
 
240
.. sourcecode:: python+sql
 
241
 
 
242
    >>> jack = session.query(User).\
 
243
    ... join(User.addresses).\
 
244
    ... options(joinedload(User.addresses)).\
 
245
    ... filter(User.name=='jack').\
 
246
    ... filter(Address.email_address=='someaddress@foo.com').\
 
247
    ... all() 
 
248
    {opensql}SELECT addresses_1.id AS addresses_1_id, addresses_1.email_address AS addresses_1_email_address,
 
249
    addresses_1.user_id AS addresses_1_user_id, users.id AS users_id, users.name AS users_name,
 
250
    users.fullname AS users_fullname, users.password AS users_password
 
251
    FROM users JOIN addresses ON users.id = addresses.user_id
 
252
    LEFT OUTER JOIN addresses AS addresses_1 ON users.id = addresses_1.user_id
 
253
    WHERE users.name = ? AND addresses.email_address = ?
 
254
    ['jack', 'someaddress@foo.com']
 
255
 
 
256
Above, we can see that the two JOINs have very different roles.  One will match exactly
 
257
one row, that of the join of ``User`` and ``Address`` where ``Address.email_address=='someaddress@foo.com'``.
 
258
The other LEFT OUTER JOIN will match *all* ``Address`` rows related to ``User``,
 
259
and is only used to populate the ``User.addresses`` collection, for those ``User`` objects
 
260
that are returned.
 
261
 
 
262
By changing the usage of :func:`.joinedload` to another style of loading, we can change
 
263
how the collection is loaded completely independently of SQL used to retrieve
 
264
the actual ``User`` rows we want.  Below we change :func:`.joinedload` into
 
265
:func:`.subqueryload`:
 
266
 
 
267
.. sourcecode:: python+sql
 
268
 
 
269
    >>> jack = session.query(User).\
 
270
    ... join(User.addresses).\
 
271
    ... options(subqueryload(User.addresses)).\
 
272
    ... filter(User.name=='jack').\
 
273
    ... filter(Address.email_address=='someaddress@foo.com').\
 
274
    ... all() 
 
275
    {opensql}SELECT users.id AS users_id, users.name AS users_name,
 
276
    users.fullname AS users_fullname, users.password AS users_password
 
277
    FROM users JOIN addresses ON users.id = addresses.user_id
 
278
    WHERE users.name = ? AND addresses.email_address = ?
 
279
    ['jack', 'someaddress@foo.com']
 
280
 
 
281
    # ... subqueryload() emits a SELECT in order 
 
282
    # to load all address records ...
 
283
 
 
284
When using joined eager loading, if the
 
285
query contains a modifier that impacts the rows returned
 
286
externally to the joins, such as when using DISTINCT, LIMIT, OFFSET
 
287
or equivalent, the completed statement is first
 
288
wrapped inside a subquery, and the joins used specifically for joined eager
 
289
loading are applied to the subquery.   SQLAlchemy's 
 
290
joined eager loading goes the extra mile, and then ten miles further, to 
 
291
absolutely ensure that it does not affect the end result of the query, only
 
292
the way collections and related objects are loaded, no matter what the format of the query is.
177
293
 
178
294
What Kind of Loading to Use ?
179
295
-----------------------------
214
330
 * When using the default lazy loading, a load of 100 objects will like in the case of the collection
215
331
   emit as many as 101 SQL statements.  However - there is a significant exception to this, in that
216
332
   if the many-to-one reference is a simple foreign key reference to the target's primary key, each
217
 
   reference will be checked first in the current identity map using ``query.get()``.  So here, 
 
333
   reference will be checked first in the current identity map using :meth:`.Query.get`.  So here, 
218
334
   if the collection of objects references a relatively small set of target objects, or the full set
219
335
   of possible target objects have already been loaded into the session and are strongly referenced,
220
336
   using the default of `lazy='select'` is by far the most efficient way to go.
232
348
   joined loading, however, except perhaps that subquery loading can use an INNER JOIN in all cases
233
349
   whereas joined loading requires that the foreign key is NOT NULL.
234
350
 
 
351
.. _joinedload_and_join:
 
352
 
235
353
.. _contains_eager:
236
354
 
237
355
Routing Explicit Joins/Statements into Eagerly Loaded Collections
238
356
------------------------------------------------------------------
239
357
 
240
358
The behavior of :func:`~sqlalchemy.orm.joinedload()` is such that joins are
241
 
created automatically, the results of which are routed into collections and
 
359
created automatically, using anonymous aliases as targets, the results of which 
 
360
are routed into collections and
242
361
scalar references on loaded objects. It is often the case that a query already
243
362
includes the necessary joins which represent a particular collection or scalar
244
363
reference, and the joins added by the joinedload feature are redundant - yet
281
400
 
282
401
    # construct a Query object which expects the "addresses" results
283
402
    query = session.query(User).\
284
 
        outerjoin((adalias, User.addresses)).\
 
403
        outerjoin(adalias, User.addresses).\
285
404
        options(contains_eager(User.addresses, alias=adalias))
286
405
 
287
406
    # get results normally