summaryrefslogtreecommitdiff
path: root/doc/build/tutorial
diff options
context:
space:
mode:
authorFederico Caselli <cfederico87@gmail.com>2020-11-03 23:24:28 +0100
committerFederico Caselli <cfederico87@gmail.com>2020-11-13 23:03:01 +0100
commitf18316a14f3858acfd3e813753c2c69821a27d57 (patch)
tree2ab08829cb07ee6d396a794c1123496574d5dce7 /doc/build/tutorial
parent5b674ac6319a373e21dac1e4faf37c7354e61429 (diff)
downloadsqlalchemy-f18316a14f3858acfd3e813753c2c69821a27d57.tar.gz
Some small improvements on the tutorial 2.0 documents
Change-Id: I7fb37d45c29307b2213bebd0ef280d73804ac473
Diffstat (limited to 'doc/build/tutorial')
-rw-r--r--doc/build/tutorial/data.rst107
-rw-r--r--doc/build/tutorial/dbapi_transactions.rst13
-rw-r--r--doc/build/tutorial/engine.rst3
-rw-r--r--doc/build/tutorial/index.rst6
-rw-r--r--doc/build/tutorial/orm_data_manipulation.rst22
-rw-r--r--doc/build/tutorial/orm_related_objects.rst29
6 files changed, 117 insertions, 63 deletions
diff --git a/doc/build/tutorial/data.rst b/doc/build/tutorial/data.rst
index 55d65c4f4..e7136683b 100644
--- a/doc/build/tutorial/data.rst
+++ b/doc/build/tutorial/data.rst
@@ -24,13 +24,14 @@ The components of this section are as follows:
* :ref:`tutorial_core_insert` - to get some data into the database, we introduce
and demonstrate the Core :class:`_sql.Insert` construct. INSERTs from an
- ORM perspective are described later, at :ref:`tutorial_orm_data_manipulation`.
+ ORM perspective are described in the next section
+ :ref:`tutorial_orm_data_manipulation`.
* :ref:`tutorial_selecting_data` - this section will describe in detail
the :class:`_sql.Select` construct, which is the most commonly used object
in SQLAlchemy. The :class:`_sql.Select` construct emits SELECT statements
for both Core and ORM centric applications and both use cases will be
- described here. Additional ORM use cases are also noted in he later
+ described here. Additional ORM use cases are also noted in the later
section :ref:`tutorial_select_relationships` as well as the
:ref:`queryguide_toplevel`.
@@ -64,7 +65,7 @@ new data into a table.
The insert() SQL Expression Construct
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-A simple example of :class:`_sql.Insert` illustrates the target table
+A simple example of :class:`_sql.Insert` illustrating the target table
and the VALUES clause at once::
>>> from sqlalchemy import insert
@@ -248,9 +249,7 @@ as well as the values for server defaults. However the RETURNING clause
may also be specified explicitly using the :meth:`_sql.Insert.returning`
method; in this case, the :class:`_engine.Result`
object that's returned when the statement is executed has rows which
-can be fetched. It is only supported for single-statement
-forms, and for some backends may only support single-row INSERT statements
-overall::
+can be fetched::
>>> insert_stmt = insert(address_table).returning(address_table.c.id, address_table.c.email_address)
>>> print(insert_stmt)
@@ -258,7 +257,6 @@ overall::
VALUES (:id, :user_id, :email_address)
RETURNING address.id, address.email_address
-
It can also be combined with :meth:`_sql.Insert.from_select`,
as in the example below that builds upon the example stated in
:ref:`tutorial_insert_from_select`::
@@ -272,6 +270,27 @@ as in the example below that builds upon the example stated in
SELECT user_account.id, user_account.name || :name_1 AS anon_1
FROM user_account RETURNING address.id, address.email_address
+.. tip::
+
+ The RETURNING feature is also supported by UPDATE and DELETE statements,
+ which will be introduced later in this tutorial.
+ The RETURNING feature is generally [1]_ only
+ supported for statement executions that use a single set of bound
+ parameters; that is, it wont work with the "executemany" form introduced
+ at :ref:`tutorial_multiple_parameters`. Additionally, some dialects
+ such as the Oracle dialect only allow RETURNING to return a single row
+ overall, meaning it won't work with "INSERT..FROM SELECT" nor will it
+ work with multiple row :class:`_sql.Update` or :class:`_sql.Delete`
+ forms.
+
+ .. [1] There is internal support for the
+ :mod:`_postgresql.psycopg2` dialect to INSERT many rows at once
+ and also support RETURNING, which is leveraged by the SQLAlchemy
+ ORM. However this feature has not been generalized to all dialects
+ and is not yet part of SQLAlchemy's regular API.
+
+
+
.. seealso::
:class:`_sql.Insert` - in the SQL Expression API documentation
@@ -354,6 +373,16 @@ complete entities, such as instances of the ``User`` class, as column values:
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
{opensql}ROLLBACK{stop}
+.. topic:: select() from a Table vs. ORM class
+
+ While the SQL generated in these examples looks the same whether we invoke
+ ``select(user_table)`` or ``select(User)``, in the more general case
+ they do not necessarily render the same thing, as an ORM-mapped class
+ may be mapped to other kinds of "selectables" besides tables. The
+ ``select()`` that's against an ORM entity also indicates that ORM-mapped
+ instances should be returned in a result, which is not the case when
+ SELECTing from a :class:`_schema.Table` object.
+
The following sections will discuss the SELECT construct in more detail.
@@ -455,7 +484,7 @@ or ``user_id > 10``, by making use of standard Python operators in
conjunction with
:class:`_schema.Column` and similar objects. For boolean expressions, most
Python operators such as ``==``, ``!=``, ``<``, ``>=`` etc. generate new
-SQL Expression objects, rather than plain boolean True/False values::
+SQL Expression objects, rather than plain boolean ``True``/``False`` values::
>>> print(user_table.c.name == 'squidward')
user_account.name = :name_1
@@ -561,9 +590,10 @@ clause::
{opensql}SELECT user_account.name, address.email_address
FROM user_account, address
-In order to JOIN these two tables together, two methods that are
-most straightforward are :meth:`_sql.Select.join_from`, which
-allows us to indicate the left and right side of the JOIN explicitly::
+In order to JOIN these two tables together, we typically use one of two methods
+on :class:`_sql.Select`. The first is the :meth:`_sql.Select.join_from`
+method, which allows us to indicate the left and right side of the JOIN
+explicitly::
>>> print(
... select(user_table.c.name, address_table.c.email_address).
@@ -573,7 +603,7 @@ allows us to indicate the left and right side of the JOIN explicitly::
FROM user_account JOIN address ON user_account.id = address.user_id
-the other is the :meth:`_sql.Select.join` method, which indicates only the
+The other is the the :meth:`_sql.Select.join` method, which indicates only the
right side of the JOIN, the left hand-side is inferred::
>>> print(
@@ -586,8 +616,8 @@ right side of the JOIN, the left hand-side is inferred::
.. sidebar:: The ON Clause is inferred
When using :meth:`_sql.Select.join_from` or :meth:`_sql.Select.join`, we may
- observe that the ON clause of the join is also inferred for us in simple cases.
- More on that in the next section.
+ observe that the ON clause of the join is also inferred for us in simple
+ foreign key cases. More on that in the next section.
We also have the option add elements to the FROM clause explicitly, if it is not
inferred the way we want from the columns clause. We use the
@@ -621,7 +651,7 @@ produce the SQL ``count()`` function::
Setting the ON Clause
~~~~~~~~~~~~~~~~~~~~~
-The previous examples on JOIN illustrated that the :class:`_sql.Select` construct
+The previous examples of JOIN illustrated that the :class:`_sql.Select` construct
can join between two tables and produce the ON clause automatically. This
occurs in those examples because the ``user_table`` and ``address_table``
:class:`_sql.Table` objects include a single :class:`_schema.ForeignKeyConstraint`
@@ -644,9 +674,10 @@ same SQL Expression mechanics as we saw about in :ref:`tutorial_select_where_cla
.. container:: orm-header
**ORM Tip** - there's another way to generate the ON clause when using
- ORM entities as well, when using the :func:`_orm.relationship` construct
- that can be seen in the mapping set up at :ref:`tutorial_declaring_mapped_classes`.
- This is a whole subject onto itself, which is introduced more fully
+ ORM entities that make use of the :func:`_orm.relationship` construct,
+ like the mapping set up in the previous section at
+ :ref:`tutorial_declaring_mapped_classes`.
+ This is a whole subject onto itself, which is introduced at length
at :ref:`tutorial_joining_relationships`.
OUTER and FULL join
@@ -661,17 +692,22 @@ and FULL OUTER JOIN, respectively::
... select(user_table).join(address_table, isouter=True)
... )
{opensql}SELECT user_account.id, user_account.name, user_account.fullname
- FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id
+ FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id{stop}
>>> print(
... select(user_table).join(address_table, full=True)
... )
{opensql}SELECT user_account.id, user_account.name, user_account.fullname
- FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id
+ FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id{stop}
There is also a method :meth:`_sql.Select.outerjoin` that is equivalent to
using ``.join(..., isouter=True)``.
+.. tip::
+
+ SQL also has a "RIGHT OUTER JOIN". SQLAlchemy doesn't render this directly;
+ instead, reverse the order of the tables and use "LEFT OUTER JOIN".
+
ORDER BY
^^^^^^^^^
@@ -706,8 +742,8 @@ value in a set of values.
SQLAlchemy provides for SQL functions in an open-ended way using a namespace
known as :data:`_sql.func`. This is a special constructor object which
will create new instances of :class:`_functions.Function` when given the name
-of a particular SQL function, which can be any name, as well as zero or
-more arguments to pass to the function, which are like in all other cases
+of a particular SQL function, which can have any name, as well as zero or
+more arguments to pass to the function, which are, like in all other cases,
SQL Expression constructs. For example, to
render the SQL COUNT() function against the ``user_account.id`` column,
we call upon the name ``count()`` name::
@@ -752,7 +788,7 @@ than one address:
Ordering or Grouping by a Label
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-An important technique in particular on some database backends is the ability
+An important technique, in particular on some database backends, is the ability
to ORDER BY or GROUP BY an expression that is already stated in the columns
clause, without re-stating the expression in the ORDER BY or GROUP BY clause
and instead using the column name or labeled name from the COLUMNS clause.
@@ -946,6 +982,13 @@ in a "recursive" style, and may in more elaborate cases be composed from the
RETURNING clause of an INSERT, UPDATE or DELETE statement. The docstring
for :class:`_sql.CTE` includes details on these additional patterns.
+In both cases, the subquery and CTE were named at the SQL level using an
+"anonymous" name. In the Python code, we don't need to provide these names
+at all. The object identity of the :class:`_sql.Subquery` or :class:`_sql.CTE`
+instances serves as the syntactical identity of the object when rendered.
+A name that will be rendered in the SQL can be provided by passing it as the
+first argument of the :meth:`_sql.Select.subquery` or :meth:`_sql.Select.cte` methods.
+
.. seealso::
:meth:`_sql.Select.subquery` - further detail on subqueries
@@ -1021,11 +1064,6 @@ Another example follows, which is exactly the same except it makes use of the
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
{opensql}ROLLBACK{stop}
-In both cases, the subquery and CTE were named at the SQL level using an
-"anonymous" name. In the Python code, we don't need to provide these names
-at all. The object identity of the :class:`_sql.Subquery` or :class:`_sql.CTE`
-instances serves as the syntactical identity of the object when rendered.
-
.. _tutorial_scalar_subquery:
Scalar and Correlated Subqueries
@@ -1385,7 +1423,8 @@ tuples so that this order may be controlled [1]_::
{opensql}UPDATE some_table SET y=:y, x=(some_table.y + :y_1)
-.. [1] While Python dictionaries are `guaranteed to be insert ordered
+.. [1] While Python dictionaries are
+ `guaranteed to be insert ordered
<https://mail.python.org/pipermail/python-dev/2017-December/151283.html>`_
as of Python 3.7, the
:meth:`_sql.Update.ordered_values` method stilll provides an additional
@@ -1403,14 +1442,12 @@ delete rows from a table.
The :func:`_sql.delete` statement from an API perspective is very similar to
that of the :func:`_sql.update` construct, traditionally returning no rows but
-allowing for a RETURNING variant.
+allowing for a RETURNING variant on some database backends.
::
>>> from sqlalchemy import delete
- >>> stmt = (
- ... delete(user_table).where(user_table.c.name == 'patrick')
- ... )
+ >>> stmt = delete(user_table).where(user_table.c.name == 'patrick')
>>> print(stmt)
{opensql}DELETE FROM user_account WHERE user_account.name = :name_1
@@ -1511,7 +1548,7 @@ be iterated::
>>> print(update_stmt)
{opensql}UPDATE user_account SET fullname=:fullname
WHERE user_account.name = :name_1
- RETURNING user_account.id, user_account.name
+ RETURNING user_account.id, user_account.name{stop}
>>> delete_stmt = (
... delete(user_table).where(user_table.c.name == 'patrick').
@@ -1520,7 +1557,7 @@ be iterated::
>>> print(delete_stmt)
{opensql}DELETE FROM user_account
WHERE user_account.name = :name_1
- RETURNING user_account.id, user_account.name
+ RETURNING user_account.id, user_account.name{stop}
Further Reading for UPDATE, DELETE
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/doc/build/tutorial/dbapi_transactions.rst b/doc/build/tutorial/dbapi_transactions.rst
index 24df53943..d9b20c760 100644
--- a/doc/build/tutorial/dbapi_transactions.rst
+++ b/doc/build/tutorial/dbapi_transactions.rst
@@ -337,6 +337,19 @@ which is one of six different formats allowed by the DBAPI specification.
SQLAlchemy abstracts these formats into just one, which is the "named" format
using a colon.
+.. topic:: Always use bound parameters
+
+ As mentioned at the beginning of this section, textual SQL is not the usual
+ way we work with SQLAlchemy. However, when using textual SQL, a Python
+ literal value, even non-strings like integers or dates, should **never be
+ stringified into SQL string directly**; a parameter should **always** be
+ used. This is most famously known as how to avoid SQL injection attacks
+ when the data is untrusted. However it also allows the SQLAlchemy dialects
+ and/or DBAPI to correctly handle the incoming input for the backend.
+ Outside of plain textual SQL use cases, SQLAlchemy's Core Expression API
+ otherwise ensures that Python literal values are passed as bound parameters
+ where appropriate.
+
.. _tutorial_multiple_parameters:
Sending Multiple Parameters
diff --git a/doc/build/tutorial/engine.rst b/doc/build/tutorial/engine.rst
index 55cd9acfd..a23cc939f 100644
--- a/doc/build/tutorial/engine.rst
+++ b/doc/build/tutorial/engine.rst
@@ -41,7 +41,8 @@ facts:
driver that SQLAlchemy uses to interact with a particular database. In
this case, we're using the name ``pysqlite``, which in modern Python
use is the `sqlite3 <http://docs.python.org/library/sqlite3.html>`_ standard
- library interface for SQLite.
+ library interface for SQLite. If omitted, SQLAlchemy will use a default
+ :term:`DBAPI` for the particular database selected.
3. How do we locate the database? In this case, our URL includes the phrase
``/:memory:``, which is an indicator to the ``sqlite3`` module that we
diff --git a/doc/build/tutorial/index.rst b/doc/build/tutorial/index.rst
index 8547e7f1d..d7f513860 100644
--- a/doc/build/tutorial/index.rst
+++ b/doc/build/tutorial/index.rst
@@ -29,7 +29,7 @@ SQLAlchemy 1.4 / 2.0 Tutorial
within the 1.4 transitional phase should check out the
:ref:`migration_20_toplevel` document as well.
- For the newcomer, this document has a **lot** of detail, however at the
+ For the newcomer, this document has a **lot** of detail, however by the
end they will be considered an **Alchemist**.
SQLAlchemy is presented as two distinct APIs, one building on top of the other.
@@ -104,7 +104,7 @@ The major sections of this tutorial are as follows:
* :ref:`tutorial_working_with_data` - here we learn how to create, select,
update and delete data in the database. The so-called :term:`CRUD`
operations here are given in terms of SQLAlchemy Core with links out towards
- their ORM counterparts. The SELECT operation is deeply introduced at
+ their ORM counterparts. The SELECT operation that is introduced in detail at
:ref:`tutorial_selecting_data` applies equally well to Core and ORM.
* :ref:`tutorial_orm_data_manipulation` covers the persistence framework of the
@@ -116,7 +116,7 @@ The major sections of this tutorial are as follows:
of how it's used, with links to deeper documentation.
* :ref:`tutorial_further_reading` lists a series of major top-level
- documentation sections which fully document the concepts introduced in this
+ documentation sections which fully documents the concepts introduced in this
tutorial.
diff --git a/doc/build/tutorial/orm_data_manipulation.rst b/doc/build/tutorial/orm_data_manipulation.rst
index 469d1096b..6068ec4fd 100644
--- a/doc/build/tutorial/orm_data_manipulation.rst
+++ b/doc/build/tutorial/orm_data_manipulation.rst
@@ -61,8 +61,9 @@ names as keys in the constructor.
In a similar manner as in our Core examples of :class:`_sql.Insert`, we did not
include a primary key (i.e. an entry for the ``id`` column), since we would
-like to make use of SQLite's auto-incrementing primary key feature which the
-ORM also integrates with. The value of the ``id`` attribute on the above
+like to make use of the auto-incrementing primary key feature of the database,
+SQLite in this case, which the ORM also integrates with.
+The value of the ``id`` attribute on the above
objects, if we were to view it, displays itself as ``None``::
>>> squidward
@@ -126,10 +127,11 @@ method:
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('ehkrabs', 'Eugene H. Krabs')
-Above we observe the :class:`_orm.Session` was first called upon to emit
-SQL, so it created a new transaction and emitted the appropriate INSERT
-statements for the two objects. The transaction now **remains open**
-until we call the :meth:`_orm.Session.commit` method.
+Above we observe the :class:`_orm.Session` was first called upon to emit SQL,
+so it created a new transaction and emitted the appropriate INSERT statements
+for the two objects. The transaction now **remains open** until we call any
+of the :meth:`_orm.Session.commit`, :meth:`_orm.Session.rollback`, or
+:meth:`_orm.Session.close` methods of :class:`_orm.Session`.
While :meth:`_orm.Session.flush` may be used to manually push out pending
changes to the current transaction, it is usually unnecessary as the
@@ -223,7 +225,7 @@ using the ORM, there are two ways in which this construct is used. The primary
way is that it is emitted automatically as part of the :term:`unit of work`
process used by the :class:`_orm.Session`, where an UPDATE statement is emitted
on a per-primary key basis corresponding to individual objects that have
-changes on them. A second form of ORM enabled UPDATE is called an "ORM enabled
+changes on them. A second form of UPDATE is called an "ORM enabled
UPDATE" and allows us to use the :class:`_sql.Update` construct with the
:class:`_orm.Session` explicitly; this is described in the next section.
@@ -413,8 +415,8 @@ ORM-enabled DELETE Statements
Like UPDATE operations, there is also an ORM-enabled version of DELETE which we can
illustrate by using the :func:`_sql.delete` construct with
:meth:`_orm.Session.execute`. It also has a feature by which **non expired**
-objects that match the given deletion criteria will be automatically marked
-as "deleted" in the :class:`_orm.Session`:
+objects (see :term:`expired`) that match the given deletion criteria will be
+automatically marked as ":term:`deleted`" in the :class:`_orm.Session`:
.. sourcecode:: pycon+sql
@@ -469,7 +471,7 @@ with the exception of a special SQLAlchemy internal state object::
>>> sandy.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x...>}
-This is the "expired" state; accessing the attribute again will autobegin
+This is the ":term:`expired`" state; accessing the attribute again will autobegin
a new transaction and refresh ``sandy`` with the current database row:
.. sourcecode:: pycon+sql
diff --git a/doc/build/tutorial/orm_related_objects.rst b/doc/build/tutorial/orm_related_objects.rst
index 120492c28..2afabc654 100644
--- a/doc/build/tutorial/orm_related_objects.rst
+++ b/doc/build/tutorial/orm_related_objects.rst
@@ -8,9 +8,9 @@
.. _tutorial_orm_related_objects:
Working with Related Objects
-=============================
+============================
-In this section, we will cover one more essential ORM concept, which is that of
+In this section, we will cover one more essential ORM concept, which is
how the ORM interacts with mapped classes that refer to other objects. In the
section :ref:`tutorial_declaring_mapped_classes`, the mapped class examples
made use of a construct called :func:`_orm.relationship`. This construct
@@ -170,7 +170,7 @@ objects are not yet associated with a real database row::
It's at this stage that we can see the very great utility that the unit of
work process provides; recall in the section :ref:`tutorial_core_insert_values_clause`,
-rows were inserted rows into the ``user_account`` and
+rows were inserted into the ``user_account`` and
``address`` tables using some elaborate syntaxes in order to automatically
associate the ``address.user_id`` columns with those of the ``user_account``
rows. Additionally, it was necessary that we emit INSERT for ``user_account``
@@ -199,7 +199,7 @@ newly generated primary key of the ``user_account`` row is applied to the
.. _tutorial_loading_relationships:
Loading Relationships
-----------------------
+---------------------
In the last step, we called :meth:`_orm.Session.commit` which emitted a COMMIT
for the transaction, and then per
@@ -266,11 +266,11 @@ section at :ref:`tutorial_orm_loader_strategies`.
.. _tutorial_select_relationships:
Using Relationships in Queries
--------------------------------
+------------------------------
The previous section introduced the behavior of the :func:`_orm.relationship`
construct when working with **instances of a mapped class**, above, the
-``u1``, ``a1`` and ``a2`` instances of the ``User`` and ``Address`` class.
+``u1``, ``a1`` and ``a2`` instances of the ``User`` and ``Address`` classes.
In this section, we introduce the behavior of :func:`_orm.relationship` as it
applies to **class level behavior of a mapped class**, where it serves in
several ways to help automate the construction of SQL queries.
@@ -323,7 +323,7 @@ between the two mapped :class:`_schema.Table` objects, not because of the
.. _tutorial_joining_relationships_aliased:
Joining between Aliased targets
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the section :ref:`tutorial_orm_entity_aliases` we introduced the
:func:`_orm.aliased` construct, which is used to apply a SQL alias to an
@@ -391,8 +391,8 @@ email addresses:
.. _tutorial_relationship_exists:
-EXISTS forms / has() / any()
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+EXISTS forms: has() / any()
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the section :ref:`tutorial_exists`, we introduced the :class:`_sql.Exists`
object that provides for the SQL EXISTS keyword in conjunction with a
@@ -463,7 +463,7 @@ which belonged to "pearl":
.. _tutorial_relationship_operators:
Common Relationship Operators
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are some additional varieties of SQL generation helpers that come with
:func:`_orm.relationship`, including:
@@ -587,7 +587,7 @@ loader strategies.
loader strategies
Selectin Load
-^^^^^^^^^^^^^^
+^^^^^^^^^^^^^
The most useful loader in modern SQLAlchemy is the
:func:`_orm.selectinload` loader option. This option solves the most common
@@ -667,7 +667,8 @@ as below where we know that all ``Address`` objects have an associated
pearl.krabs@gmail.com pkrabs
pearl@aol.com pkrabs
-:func:`_orm.joinedload` also works for collections, however it has the effect
+:func:`_orm.joinedload` also works for collections, meaning one-to-many relationships,
+however it has the effect
of multiplying out primary rows per related item in a recursive way
that grows the amount of data sent for a result set by orders of magnitude for
nested collections and/or larger collections, so its use vs. another option
@@ -777,7 +778,7 @@ arbitrary criteria to a JOIN rendered with :func:`_orm.relationship` to also
include additional criteria in the ON clause. The :meth:`_orm.PropComparator.and_`
method is in fact generally available for most loader options. For example,
if we wanted to re-load the names of users and their email addresses, but omitting
-the email addresses at the ``sqlalchemy.org`` domain, we can apply
+the email addresses with the ``sqlalchemy.org`` domain, we can apply
:meth:`_orm.PropComparator.and_` to the argument passed to
:func:`_orm.selectinload` to limit this criteria:
@@ -839,7 +840,7 @@ Raiseload
One additional loader strategy worth mentioning is :func:`_orm.raiseload`.
This option is used to completely block an application from having the
:term:`N plus one` problem at all by causing what would normally be a lazy
-load to raise instead. It has two variants that are controlled via
+load to raise an error instead. It has two variants that are controlled via
the :paramref:`_orm.raiseload.sql_only` option to block either lazy loads
that require SQL, versus all "load" operations including those which
only need to consult the current :class:`_orm.Session`.