summaryrefslogtreecommitdiff
path: root/doc/build/tutorial/data.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/build/tutorial/data.rst')
-rw-r--r--doc/build/tutorial/data.rst1509
1 files changed, 1509 insertions, 0 deletions
diff --git a/doc/build/tutorial/data.rst b/doc/build/tutorial/data.rst
new file mode 100644
index 000000000..6238e5e1f
--- /dev/null
+++ b/doc/build/tutorial/data.rst
@@ -0,0 +1,1509 @@
+.. highlight:: pycon+sql
+
+.. |prev| replace:: :doc:`metadata`
+.. |next| replace:: :doc:`orm_data_manipulation`
+
+.. include:: tutorial_nav_include.rst
+
+.. _tutorial_working_with_data:
+
+Working with Data
+==================
+
+In :ref:`tutorial_working_with_transactions`, we learned the basics of how to
+interact with the Python DBAPI and its transactional state. Then, in
+:ref:`tutorial_working_with_metadata`, we learned how to represent database
+tables, columns, and constraints within SQLAlchemy using the
+:class:`_schema.MetaData` and related objects. In this section we will combine
+both concepts above to create, select and manipulate data within a relational
+database. Our interaction with the database is **always** in terms
+of a transaction, even if we've set our database driver to use :ref:`autocommit
+<dbapi_autocommit>` behind the scenes.
+
+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`.
+
+* :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
+ section :ref:`tutorial_select_relationships` as well as the
+ :ref:`queryguide_toplevel`.
+
+* :ref:`tutorial_core_update_delete` - Rounding out the INSERT and SELECtion
+ of data, this section will describe from a Core perspective the use of the
+ :class:`_sql.Update` and :class:`_sql.Delete` constructs. ORM-specific
+ UPDATE and DELETE is similarly described in the
+ :ref:`tutorial_orm_data_manipulation` section.
+
+.. rst-class:: core-header
+
+.. _tutorial_core_insert:
+
+Core Insert
+-----------
+
+When using Core, a SQL INSERT statement is generated using the
+:func:`_sql.insert` function - this function generates a new instance of
+:class:`_sql.Insert` which represents an INSERT statement in SQL, that adds
+new data into a table.
+
+.. container:: orm-header
+
+ **ORM Readers** - The way that rows are INSERTed into the database from an ORM
+ perspective makes use of object-centric APIs on the :class:`_orm.Session` object known as the
+ :term:`unit of work` process,
+ and is fairly different from the Core-only approach described here.
+ The more ORM-focused sections later starting at :ref:`tutorial_inserting_orm`
+ subsequent to the Expression Language sections introduce this.
+
+The insert() SQL Expression Construct
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A simple example of :class:`_sql.Insert` illustrates the target table
+and the VALUES clause at once::
+
+ >>> from sqlalchemy import insert
+ >>> stmt = insert(user_table).values(name='spongebob', fullname="Spongebob Squarepants")
+
+The above ``stmt`` variable is an instance of :class:`_sql.Insert`. Most
+SQL expressions can be stringified in place as a means to see the general
+form of what's being produced::
+
+ >>> print(stmt)
+ {opensql}INSERT INTO user_account (name, fullname) VALUES (:name, :fullname)
+
+The stringified form is created by producing a :class:`_engine.Compiled` form
+of the object which includes a database-specific string SQL representation of
+the statement; we can acquire this object directly using the
+:meth:`_sql.ClauseElement.compile` method::
+
+ >>> compiled = stmt.compile()
+
+Our :class:`_sql.Insert` construct is an example of a "parameterized"
+construct, illustrated previously at :ref:`tutorial_sending_parameters`; to
+view the ``name`` and ``fullname`` :term:`bound parameters`, these are
+available from the :class:`_engine.Compiled` construct as well::
+
+ >>> compiled.params
+ {'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}
+
+Executing the Statement
+^^^^^^^^^^^^^^^^^^^^^^^
+
+Invoking the statement we can INSERT a row into ``user_table``.
+The INSERT SQL as well as the bundled parameters can be seen in the
+SQL logging:
+
+.. sourcecode:: pycon+sql
+
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(stmt)
+ ... conn.commit()
+ {opensql}BEGIN (implicit)
+ INSERT INTO user_account (name, fullname) VALUES (?, ?)
+ [...] ('spongebob', 'Spongebob Squarepants')
+ COMMIT
+
+In its simple form above, the INSERT statement does not return any rows, and if
+only a single row is inserted, it will usually include the ability to return
+information about column-level default values that were generated during the
+INSERT of that row, most commonly an integer primary key value. In the above
+case the first row in a SQLite database will normally return ``1`` for the
+first integer primary key value, which we can acquire using the
+:attr:`_engine.CursorResult.inserted_primary_key` accessor:
+
+.. sourcecode:: pycon+sql
+
+ >>> result.inserted_primary_key
+ (1,)
+
+.. tip:: :attr:`_engine.CursorResult.inserted_primary_key` returns a tuple
+ because a primary key may contain multiple columns. This is known as
+ a :term:`composite primary key`. The :attr:`_engine.CursorResult.inserted_primary_key`
+ is intended to always contain the complete primary key of the record just
+ inserted, not just a "cursor.lastrowid" kind of value, and is also intended
+ to be populated regardless of whether or not "autoincrement" were used, hence
+ to express a complete primary key it's a tuple.
+
+.. _tutorial_core_insert_values_clause:
+
+INSERT usually generates the "values" clause automatically
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The example above made use of the :meth:`_sql.Insert.values` method to
+explicitly create the VALUES clause of the SQL INSERT statement. This method
+in fact has some variants that allow for special forms such as multiple rows in
+one statement and insertion of SQL expressions. However the usual way that
+:class:`_sql.Insert` is used is such that the VALUES clause is generated
+automatically from the parameters passed to the
+:meth:`_future.Connection.execute` method; below we INSERT two more rows to
+illustrate this:
+
+.. sourcecode:: pycon+sql
+
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... insert(user_table),
+ ... [
+ ... {"name": "sandy", "fullname": "Sandy Cheeks"},
+ ... {"name": "patrick", "fullname": "Patrick Star"}
+ ... ]
+ ... )
+ ... conn.commit()
+ {opensql}BEGIN (implicit)
+ INSERT INTO user_account (name, fullname) VALUES (?, ?)
+ [...] (('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star'))
+ COMMIT{stop}
+
+The execution above features "executemany" form first illustrated at
+:ref:`tutorial_multiple_parameters`, however unlike when using the
+:func:`_sql.text` construct, we didn't have to spell out any SQL.
+By passing a dictionary or list of dictionaries to the :meth:`_future.Connection.execute`
+method in conjunction with the :class:`_sql.Insert` construct, the
+:class:`_future.Connection` ensures that the column names which are passed
+will be expressed in the VALUES clause of the :class:`_sql.Insert`
+construct automatically.
+
+.. deepalchemy::
+
+ Hi, welcome to the first edition of **Deep Alchemy**. The person on the
+ left is known as **The Alchemist**, and you'll note they are **not** a wizard,
+ as the pointy hat is not sticking upwards. The Alchemist comes around to
+ describe things that are generally **more advanced and/or tricky** and
+ additionally **not usually needed**, but for whatever reason they feel you
+ should know about this thing that SQLAlchemy can do.
+
+ In this edition, towards the goal of having some interesting data in the
+ ``address_table`` as well, below is a more advanced example illustrating
+ how the :meth:`_sql.Insert.values` method may be used explicitly while at
+ the same time including for additional VALUES generated from the
+ parameters. A :term:`scalar subquery` is constructed, making use of the
+ :func:`_sql.select` construct introduced in the next section, and the
+ parameters used in the subquery are set up using an explicit bound
+ parameter name, established using the :func:`_sql.bindparam` construct.
+
+ This is some slightly **deeper** alchemy just so that we can add related
+ rows without fetching the primary key identifiers from the ``user_table``
+ operation into the application. Most Alchemists will simply use the ORM
+ which takes care of things like this for us.
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import select, bindparam
+ >>> scalar_subquery = (
+ ... select(user_table.c.id).
+ ... where(user_table.c.name==bindparam('username')).
+ ... scalar_subquery()
+ ... )
+
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... insert(address_table).values(user_id=scalar_subquery),
+ ... [
+ ... {"username": 'spongebob', "email_address": "spongebob@sqlalchemy.org"},
+ ... {"username": 'sandy', "email_address": "sandy@sqlalchemy.org"},
+ ... {"username": 'sandy', "email_address": "sandy@squirrelpower.org"},
+ ... ]
+ ... )
+ ... conn.commit()
+ {opensql}BEGIN (implicit)
+ INSERT INTO address (user_id, email_address) VALUES ((SELECT user_account.id
+ FROM user_account
+ WHERE user_account.name = ?), ?)
+ [...] (('spongebob', 'spongebob@sqlalchemy.org'), ('sandy', 'sandy@sqlalchemy.org'),
+ ('sandy', 'sandy@squirrelpower.org'))
+ COMMIT{stop}
+
+Other INSERT Options
+^^^^^^^^^^^^^^^^^^^^^
+
+A quick overview of some other patterns that are available with :func:`_sql.insert`:
+
+* **INSERT..FROM SELECT** - the :class:`_sql.Insert` construct can compose
+ an INSERT that gets rows directly from a SELECT using the :meth:`_sql.Insert.from_select`
+ method::
+
+ >>> select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
+ >>> insert_stmt = insert(address_table).from_select(
+ ... ["user_id", "email_address"], select_stmt
+ ... )
+ >>> print(insert_stmt)
+ {opensql}INSERT INTO address (user_id, email_address)
+ SELECT user_account.id, user_account.name || :name_1 AS anon_1
+ FROM user_account
+
+ ..
+
+* **RETURNING clause** - the RETURNING clause for supported backends is used
+ automatically in order to retrieve the last inserted primary key value
+ 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. It can also be combined with :meth:`_sql.Insert.from_select`,
+ as in the example below that builds upon the previous example::
+
+ >>> print(insert_stmt.returning(address_table.c.id, address_table.c.email_address))
+ {opensql}INSERT INTO address (user_id, email_address)
+ SELECT user_account.id, user_account.name || :name_1 AS anon_1
+ FROM user_account RETURNING address.id, address.email_address
+
+ ..
+
+.. seealso::
+
+ :class:`_sql.Insert` - in the SQL Expression API documentation
+
+
+.. _tutorial_selecting_data:
+
+.. rst-class:: core-header, orm-dependency
+
+Selecting Data
+--------------
+
+For both Core and ORM, the :func:`_sql.select` function generates a
+:class:`_sql.Select` construct which is used for all SELECT queries.
+Passed to methods like :meth:`_future.Connection.execute` in Core and
+:meth:`_orm.Session.execute` in ORM, a SELECT statement is emitted in the
+current transaction and the result rows available via the returned
+:class:`_engine.Result` object.
+
+.. container:: orm-header
+
+ **ORM Readers** - the content here applies equally well to both Core and ORM
+ use and basic ORM variant use cases are mentioned here. However there are
+ a lot more ORM-specific features available as well; these are documented
+ at :ref:`queryguide_toplevel`.
+
+
+The select() SQL Expression Construct
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :func:`_sql.select` construct builds up a statement in the same way
+as that of :func:`_sql.insert`, using a :term:`generative` approach where
+each method builds more state onto the object. Like the other SQL constructs,
+it can be stringified in place::
+
+ >>> from sqlalchemy import select
+ >>> stmt = select(user_table).where(user_table.c.name == 'spongebob')
+ >>> print(stmt)
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = :name_1
+
+Also in the same manner as all other statement-level SQL constructs, to
+actually run the statement we pass it to an execution method.
+Since a SELECT statement returns
+rows we can always iterate the result object to get :class:`_engine.Row`
+objects back:
+
+.. sourcecode:: pycon+sql
+
+ >>> with engine.connect() as conn:
+ ... for row in conn.execute(stmt):
+ ... print(row)
+ {opensql}BEGIN (implicit)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = ?
+ [...] ('spongebob',){stop}
+ (1, 'spongebob', 'Spongebob Squarepants')
+ {opensql}ROLLBACK{stop}
+
+When using the ORM, particularly with a :func:`_sql.select` construct that's
+composed against ORM entities, we will want to execute it using the
+:meth:`_orm.Session.execute` method on the :class:`_orm.Session`; using
+this approach, we continue to get :class:`_engine.Row` objects from the
+result, however these rows are now capable of including
+complete entities, such as instances of the ``User`` class, as column values:
+
+.. sourcecode:: pycon+sql
+
+ >>> stmt = select(User).where(User.name == 'spongebob')
+ >>> with Session(engine) as session:
+ ... for row in session.execute(stmt):
+ ... print(row)
+ {opensql}BEGIN (implicit)
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = ?
+ [...] ('spongebob',){stop}
+ (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
+ {opensql}ROLLBACK{stop}
+
+The following sections will discuss the SELECT construct in more detail.
+
+
+Setting the COLUMNS and FROM clause
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :func:`_sql.select` function accepts positional elements representing any
+number of :class:`_schema.Column` and/or :class:`_schema.Table` expressions, as
+well as a wide range of compatible objects, which are resolved into a list of SQL
+expressions to be SELECTed from that will be returned as columns in the result
+set. These elements also serve in simpler cases to create the FROM clause,
+which is inferred from the columns and table-like expressions passed::
+
+ >>> print(select(user_table))
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+
+To SELECT from individual columns using a Core approach,
+:class:`_schema.Column` objects are accessed from the :attr:`_schema.Table.c`
+accessor and can be sent directly; the FROM clause will be inferred as the set
+of all :class:`_schema.Table` and other :class:`_sql.FromClause` objects that
+are represented by those columns::
+
+ >>> print(select(user_table.c.name, user_table.c.fullname))
+ {opensql}SELECT user_account.name, user_account.fullname
+ FROM user_account
+
+.. _tutorial_selecting_orm_entities:
+
+Selecting ORM Entities and Columns
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ORM entities, such our ``User`` class as well as the column-mapped
+attributes upon it such as ``User.name``, also participate in the SQL Expression
+Language system representing tables and columns. Below illustrates an
+example of SELECTing from the ``User`` entity, which ultimately renders
+in the same way as if we had used ``user_table`` directly::
+
+ >>> print(select(User))
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+
+To select from individual columns using ORM entities, the class-bound
+attributes can be passed directly which are resolved into the
+:class:`_schema.Column` or other SQL expression represented by each attribute::
+
+ >>> print(select(User.name, User.fullname))
+ {opensql}SELECT user_account.name, user_account.fullname
+ FROM user_account
+
+.. tip::
+
+ When ORM-related objects are used within the :class:`_sql.Select`
+ construct, they are resolved into the underlying :class:`_schema.Table` and
+ :class:`_schema.Column` and similar Core constructs they represent; at the
+ same time, they apply a **plugin** to the core :class:`_sql.Select`
+ construct such that a new set of ORM-specific behaviors make take
+ effect when the construct is being compiled.
+
+.. seealso::
+
+ :ref:`orm_queryguide_select_columns` - in the :ref:`queryguide_toplevel`
+
+Selecting from Labeled SQL Expressions
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :meth:`_sql.ColumnElement.label` method as well as the same-named method
+available on ORM attributes provides a SQL label of a column or expression,
+allowing it to have a specific name in a result set. This can be helpful
+when referring to arbitrary SQL expressions in a result row by name:
+
+.. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import func, cast
+ >>> stmt = (
+ ... select(
+ ... ("Username: " + user_table.c.name).label("username"),
+ ... ).order_by(user_table.c.name)
+ ... )
+ >>> with engine.connect() as conn:
+ ... for row in conn.execute(stmt):
+ ... print(f"{row.username}")
+ {opensql}BEGIN (implicit)
+ SELECT ? || user_account.name AS username
+ FROM user_account ORDER BY user_account.name
+ [...] ('Username: ',){stop}
+ Username: patrick
+ Username: sandy
+ Username: spongebob
+ {opensql}ROLLBACK{stop}
+
+.. _tutorial_select_where_clause:
+
+The WHERE clause
+^^^^^^^^^^^^^^^^
+
+SQLAlchemy allows us to compose SQL expressions, such as ``name = 'squidward'``
+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::
+
+ >>> print(user_table.c.name == 'squidward')
+ user_account.name = :name_1
+
+ >>> print(address_table.c.user_id > 10)
+ address.user_id > :user_id_1
+
+
+We can use expressions like these to generate the WHERE clause by passing
+the resulting objects to the :meth:`_sql.Select.where` method::
+
+ >>> print(select(user_table).where(user_table.c.name == 'squidward'))
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = :name_1
+
+
+To produce multiple expressions joined by AND, the :meth:`_sql.Select.where`
+method may be invoked any number of times::
+
+ >>> print(
+ ... select(address_table.c.email_address).
+ ... where(user_table.c.name == 'squidward').
+ ... where(address_table.c.user_id == user_table.c.id)
+ ... )
+ {opensql}SELECT address.email_address
+ FROM address, user_account
+ WHERE user_account.name = :name_1 AND address.user_id = user_account.id
+
+A single call to :meth:`_sql.Select.where` also accepts multiple expressions
+with the same effect::
+
+ >>> print(
+ ... select(address_table.c.email_address).
+ ... where(
+ ... user_table.c.name == 'squidward',
+ ... address_table.c.user_id == user_table.c.id
+ ... )
+ ... )
+ {opensql}SELECT address.email_address
+ FROM address, user_account
+ WHERE user_account.name = :name_1 AND address.user_id = user_account.id
+
+"AND" and "OR" conjunctions are both available directly using the
+:func:`_sql.and_` and :func:`_sql.or_` functions, illustrated below in terms
+of ORM entities::
+
+ >>> from sqlalchemy import and_, or_
+ >>> print(
+ ... select(Address.email_address).
+ ... where(
+ ... and_(
+ ... or_(User.name == 'squidward', User.name == 'sandy'),
+ ... Address.user_id == User.id
+ ... )
+ ... )
+ ... )
+ {opensql}SELECT address.email_address
+ FROM address, user_account
+ WHERE (user_account.name = :name_1 OR user_account.name = :name_2)
+ AND address.user_id = user_account.id
+
+For simple "equality" comparisons against a single entity, there's also a
+popular method known as :meth:`_sql.Select.filter_by` which accepts keyword
+arguments that match to column keys or ORM attribute names. It will filter
+against the leftmost FROM clause or the last entity joined::
+
+ >>> print(
+ ... select(User).filter_by(name='spongebob', fullname='Spongebob Squarepants')
+ ... )
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name = :name_1 AND user_account.fullname = :fullname_1
+
+
+.. seealso::
+
+
+ :doc:`/core/operators` - descriptions of most SQL operator functions in SQLAlchemy
+
+
+.. _tutorial_select_join:
+
+Explicit FROM clauses and JOINs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+As mentioned previously, the FROM clause is usually **inferred**
+based on the expressions that we are setting in the columns
+clause as well as other elements of the :class:`_sql.Select`.
+
+If we set a single column from a particular :class:`_schema.Table`
+in the COLUMNS clause, it puts that :class:`_schema.Table` in the FROM
+clause as well::
+
+ >>> print(select(user_table.c.name))
+ {opensql}SELECT user_account.name
+ FROM user_account
+
+If we were to put columns from two tables, then we get a comma-separated FROM
+clause::
+
+ >>> print(select(user_table.c.name, address_table.c.email_address))
+ {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::
+
+ >>> print(
+ ... select(user_table.c.name, address_table.c.email_address).
+ ... join_from(user_table, address_table)
+ ... )
+ {opensql}SELECT user_account.name, address.email_address
+ 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
+right side of the JOIN, the left hand-side is inferred::
+
+ >>> print(
+ ... select(user_table.c.name, address_table.c.email_address).
+ ... join(address_table)
+ ... )
+ {opensql}SELECT user_account.name, address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+.. 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.
+
+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
+:meth:`_sql.Select.select_from` method to achieve this, as below
+where we establish ``user_table`` as the first element in the FROM
+clause and :meth:`_sql.Select.join` to establish ``address_table`` as
+the second::
+
+ >>> print(
+ ... select(address_table.c.email_address).
+ ... select_from(user_table).join(address_table)
+ ... )
+ {opensql}SELECT address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+Another example where we might want to use :meth:`_sql.Select.select_from`
+is if our columns clause doesn't have enough information to provide for a
+FROM clause. For example, to SELECT from the common SQL expression
+``count(*)``, we use a SQLAlchemy element known as :attr:`_sql.func` to
+produce the SQL ``count()`` function::
+
+ >>> from sqlalchemy import func
+ >>> print (
+ ... select(func.count('*')).select_from(user_table)
+ ... )
+ {opensql}SELECT count(:count_2) AS count_1
+ FROM user_account
+
+.. _tutorial_select_join_onclause:
+
+Setting the ON Clause
+~~~~~~~~~~~~~~~~~~~~~
+
+The previous examples on 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`
+definition which is used to form this ON clause.
+
+If the left and right targets of the join do not have such a constraint, or
+there are multiple constraints in place, we need to specify the ON clause
+directly. Both :meth:`_sql.Select.join` and :meth:`_sql.Select.join_from`
+accept an additional argument for the ON clause, which is stated using the
+same SQL Expression mechanics as we saw about in :ref:`tutorial_select_where_clause`::
+
+ >>> print(
+ ... select(address_table.c.email_address).
+ ... select_from(user_table).
+ ... join(address_table, user_table.c.id == address_table.c.user_id)
+ ... )
+ {opensql}SELECT address.email_address
+ FROM user_account JOIN address ON user_account.id = address.user_id
+
+.. 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
+ at :ref:`tutorial_joining_relationships`.
+
+OUTER and FULL join
+~~~~~~~~~~~~~~~~~~~
+
+Both the :meth:`_sql.Select.join` and :meth:`_sql.Select.join_from` methods
+accept keyword arguments :paramref:`_sql.Select.join.isouter` and
+:paramref:`_sql.Select.join.full` which will render LEFT OUTER JOIN
+and FULL OUTER JOIN, respectively::
+
+ >>> print(
+ ... 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
+
+ >>> 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
+
+There is also a method :meth:`_sql.Select.outerjoin` that is equivalent to
+using ``.join(..., isouter=True)``.
+
+ORDER BY
+^^^^^^^^^
+
+The ORDER BY clause is constructed in terms
+of SQL Expression constructs typically based on :class:`_schema.Column` or
+similar objects. The :meth:`_sql.Select.order_by` method accepts one or
+more of these expressions positionally::
+
+ >>> print(select(user_table).order_by(user_table.c.name))
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account ORDER BY user_account.name
+
+Ascending / descending is available from the :meth:`_sql.ColumnElement.asc`
+and :meth:`_sql.ColumnElement.desc` modifiers, which are present
+from ORM-bound attributes as well::
+
+
+ >>> print(select(User).order_by(User.name.asc(), User.fullname.desc()))
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account ORDER BY user_account.name ASC, user_account.fullname DESC
+
+.. _tutorial_group_by_w_aggregates:
+
+Aggregate functions with GROUP BY / HAVING
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In SQL, aggregate functions allow column expressions across multiple rows
+to be aggregated together to produce a single result. Examples include
+counting, computing averages, as well as locating the maximum or minimum
+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
+SQL Expression constructs. For example, to
+render the SQL COUNT() function against the ``user_account.id`` column,
+we call upon the name ``count()`` name::
+
+ >>> from sqlalchemy import func
+ >>> count_fn = func.count(user_table.c.id)
+ >>> print(count_fn)
+ {opensql}count(user_account.id)
+
+When using aggregate functions in SQL, the GROUP BY clause is essential in that
+it allows rows to be partitioned into groups where aggregate functions will
+be applied to each group individually. When requesting non-aggregated columns
+in the COLUMNS clause of a SELECT statement, SQL requires that these columns
+all be subject to a GROUP BY clause, either directly or indirectly based on
+a primary key association. The HAVING clause is then used in a similar
+manner as the WHERE clause, except that it filters out rows based on aggregated
+values rather than direct row contents.
+
+SQLAlchemy provides for these two clauses using the :meth:`_sql.Select.group_by`
+and :meth:`_sql.Select.having` methods. Below we illustrate selecting
+user name fields as well as count of addresses, for those users that have more
+than one address:
+
+.. sourcecode:: python+sql
+
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... select(User.name, func.count(Address.id).label("count")).
+ ... join(Address).
+ ... group_by(User.name).
+ ... having(func.count(Address.id) > 1)
+ ... )
+ ... print(result.all())
+ {opensql}BEGIN (implicit)
+ SELECT user_account.name, count(address.id) AS count
+ FROM user_account JOIN address ON user_account.id = address.user_id GROUP BY user_account.name
+ HAVING count(address.id) > ?
+ [...] (1,){stop}
+ [('sandy', 2)]
+ {opensql}ROLLBACK{stop}
+
+Ordering or Grouping by a Label
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+This form is available by passing the string text of the name to the
+:meth:`_sql.Select.order_by` or :meth:`_sql.Select.group_by` method. The text
+passed is **not rendered directly**; instead, the name given to an expression
+in the columns clause and rendered as that expression name in context, raising an
+error if no match is found. The unary modifiers
+:func:`.asc` and :func:`.desc` may also be used in this form:
+
+.. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import func, desc
+ >>> stmt = select(
+ ... Address.user_id,
+ ... func.count(Address.id).label('num_addresses')).\
+ ... group_by("user_id").order_by("user_id", desc("num_addresses"))
+ >>> print(stmt)
+ {opensql}SELECT address.user_id, count(address.id) AS num_addresses
+ FROM address GROUP BY address.user_id ORDER BY address.user_id, num_addresses DESC
+
+.. _tutorial_using_aliases:
+
+Using Aliases
+^^^^^^^^^^^^^
+
+Now that we are selecting from multiple tables and using joins, we quickly
+run into the case where we need to refer to the same table mutiple times
+in the FROM clause of a statement. We accomplish this using SQL **aliases**,
+which are a syntax that supplies an alternative name to a table or subquery
+from which it can be referred towards in the statement.
+
+In the SQLAlchemy Expression Language, these "names" are instead represented by
+:class:`_sql.FromClause` objects known as the :class:`_sql.Alias` construct,
+which is constructed in Core using the :meth:`_sql.FromClause.alias`
+method. An :class:`_sql.Alias` construct is just like a :class:`_sql.Table`
+construct in that it also has a namespace of :class:`_schema.Column`
+objects within the :attr:`_sql.Alias.c` collection. The SELECT statement
+below for example returns all unique pairs of user names::
+
+ >>> user_alias_1 = user_table.alias()
+ >>> user_alias_2 = user_table.alias()
+ >>> print(
+ ... select(user_alias_1.c.name, user_alias_2.c.name).
+ ... join_from(user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id)
+ ... )
+ {opensql}SELECT user_account_1.name, user_account_2.name
+ FROM user_account AS user_account_1
+ JOIN user_account AS user_account_2 ON user_account_1.id > user_account_2.id
+
+.. _tutorial_orm_entity_aliases:
+
+ORM Entity Aliases
+~~~~~~~~~~~~~~~~~~
+
+The ORM equivalent of the :meth:`_sql.FromClause.alias` method is the
+ORM :func:`_orm.aliased` function, which may be applied to an entity
+such as ``User`` and ``Address``. This produces a :class:`_sql.Alias` object
+internally that's against the original mapped :class:`_schema.Table` object,
+while maintaining ORM functionality. The SELECT below selects from the
+``User`` entity all objects that include two particular email addresses::
+
+ >>> from sqlalchemy.orm import aliased
+ >>> address_alias_1 = aliased(Address)
+ >>> address_alias_2 = aliased(Address)
+ >>> print(
+ ... select(User).
+ ... join_from(User, address_alias_1).
+ ... where(address_alias_1.email_address == 'patrick@aol.com').
+ ... join_from(User, address_alias_2).
+ ... where(address_alias_2.email_address == 'patrick@gmail.com')
+ ... )
+ {opensql}SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ JOIN address AS address_1 ON user_account.id = address_1.user_id
+ JOIN address AS address_2 ON user_account.id = address_2.user_id
+ WHERE address_1.email_address = :email_address_1
+ AND address_2.email_address = :email_address_2
+
+.. tip::
+
+ As mentioned in :ref:`tutorial_select_join_onclause`, the ORM provides
+ for another way to join using the :func:`_orm.relationship` construct.
+ The above example using aliases is demonstrated using :func:`_orm.relationship`
+ at :ref:`tutorial_joining_relationships_aliased`.
+
+
+.. _tutorial_subqueries_ctes:
+
+Subqueries and CTEs
+^^^^^^^^^^^^^^^^^^^^
+
+A subquery in SQL is a SELECT statement that is rendered within parenthesis and
+placed within the context of an enclosing statement, typically a SELECT
+statement but not necessarily.
+
+This section will cover a so-called "non-scalar" subquery, which is typically
+placed in the FROM clause of an enclosing SELECT. We will also cover the
+Common Table Expression or CTE, which is used in a similar way as a subquery,
+but includes additional features.
+
+SQLAlchemy uses the :class:`_sql.Subquery` object to represent a subquery and
+the :class:`_sql.CTE` to represent a CTE, usually obtained from the
+:meth:`_sql.Select.subquery` and :meth:`_sql.Select.cte` methods, respectively.
+Either object can be used as a FROM element inside of a larger
+:func:`_sql.select` construct.
+
+We can construct a :class:`_sql.Subquery` that will select an aggregate count
+of rows from the ``address`` table (aggregate functions and GROUP BY were
+introduced previously at :ref:`tutorial_group_by_w_aggregates`):
+
+ >>> subq = select(
+ ... func.count(address_table.c.id).label("count"),
+ ... address_table.c.user_id
+ ... ).group_by(address_table.c.user_id).subquery()
+
+Stringifying the subquery by itself without it being embedded inside of another
+:class:`_sql.Select` or other statement produces the plain SELECT statement
+without any enclosing parenthesis::
+
+ >>> print(subq)
+ {opensql}SELECT count(address.id) AS count, address.user_id
+ FROM address GROUP BY address.user_id
+
+
+The :class:`_sql.Subquery` object behaves like any other FROM object such
+as a :class:`_schema.Table`, notably that it includes a :attr:`_sql.Subquery.c`
+namespace of the columns which it selects. We can use this namespace to
+refer to both the ``user_id`` column as well as our custom labeled
+``count`` expression::
+
+ >>> print(select(subq.c.user_id, subq.c.count))
+ {opensql}SELECT anon_1.user_id, anon_1.count
+ FROM (SELECT count(address.id) AS count, address.user_id AS user_id
+ FROM address GROUP BY address.user_id) AS anon_1
+
+With a selection of rows contained within the ``subq`` object, we can apply
+the object to a larger :class:`_sql.Select` that will join the data to
+the ``user_account`` table::
+
+ >>> stmt = select(
+ ... user_table.c.name,
+ ... user_table.c.fullname,
+ ... subq.c.count
+ ... ).join_from(user_table, subq)
+
+ >>> print(stmt)
+ {opensql}SELECT user_account.name, user_account.fullname, anon_1.count
+ FROM user_account JOIN (SELECT count(address.id) AS count, address.user_id AS user_id
+ FROM address GROUP BY address.user_id) AS anon_1 ON user_account.id = anon_1.user_id
+
+In order to join from ``user_account`` to ``address``, we made use of the
+:meth:`_sql.Select.join_from` method. As has been illustrated previously, the
+ON clause of this join was again **inferred** based on foreign key constraints.
+Even though a SQL subquery does not itself have any constraints, SQLAlchemy can
+act upon constraints represented on the columns by determining that the
+``subq.c.user_id`` column is **derived** from the ``address_table.c.user_id``
+column, which does express a foreign key relationship back to the
+``user_table.c.id`` column which is then used to generate the ON clause.
+
+Common Table Expressions (CTEs)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Usage of the :class:`_sql.CTE` construct in SQLAlchemy is virtually
+the same as how the :class:`_sql.Subquery` construct is used. By changing
+the invocation of the :meth:`_sql.Select.subquery` method to use
+:meth:`_sql.Select.cte` instead, we can use the resulting object as a FROM
+element in the same way, but the SQL rendered is the very different common
+table expression syntax::
+
+ >>> subq = select(
+ ... func.count(address_table.c.id).label("count"),
+ ... address_table.c.user_id
+ ... ).group_by(address_table.c.user_id).cte()
+
+ >>> stmt = select(
+ ... user_table.c.name,
+ ... user_table.c.fullname,
+ ... subq.c.count
+ ... ).join_from(user_table, subq)
+
+ >>> print(stmt)
+ {opensql}WITH anon_1 AS
+ (SELECT count(address.id) AS count, address.user_id AS user_id
+ FROM address GROUP BY address.user_id)
+ SELECT user_account.name, user_account.fullname, anon_1.count
+ FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id
+
+The :class:`_sql.CTE` construct also features the ability to be used
+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.
+
+.. seealso::
+
+ :meth:`_sql.Select.subquery` - further detail on subqueries
+
+ :meth:`_sql.Select.cte` - examples for CTE including how to use
+ RECURSIVE as well as DML-oriented CTEs
+
+ORM Entity Subqueries/CTEs
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the ORM, the :func:`_orm.aliased` construct may be used to associate an ORM
+entity, such as our ``User`` or ``Address`` class, with any :class:`_sql.FromClause`
+concept that represents a source of rows. The preceding section
+:ref:`tutorial_orm_entity_aliases` illustrates using :func:`_orm.aliased`
+to associate the mapped class with an :class:`_sql.Alias` of its
+mapped :class:`_schema.Table`. Here we illustrate :func:`_orm.aliased` doing the same
+thing against both a :class:`_sql.Subquery` as well as a :class:`_sql.CTE`
+generated against a :class:`_sql.Select` construct, that ultimately derives
+from that same mapped :class:`_schema.Table`.
+
+Below is an example of applying :func:`_orm.aliased` to the :class:`_sql.Subquery`
+construct, so that ORM entities can be extracted from its rows. The result
+shows a series of ``User`` and ``Address`` objects, where the data for
+each ``Address`` object ultimately came from a subquery against the
+``address`` table rather than that table directly:
+
+.. sourcecode:: python+sql
+
+ >>> subq = select(Address).where(~Address.email_address.like('%@aol.com')).subquery()
+ >>> address_subq = aliased(Address, subq)
+ >>> stmt = select(User, address_subq).join_from(User, address_subq).order_by(User.id, address_subq.id)
+ >>> with Session(engine) as session:
+ ... for user, address in session.execute(stmt):
+ ... print(f"{user} {address}")
+ {opensql}BEGIN (implicit)
+ SELECT user_account.id, user_account.name, user_account.fullname,
+ anon_1.id AS id_1, anon_1.email_address, anon_1.user_id
+ FROM user_account JOIN
+ (SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id
+ FROM address
+ WHERE address.email_address NOT LIKE ?) AS anon_1 ON user_account.id = anon_1.user_id
+ ORDER BY user_account.id, anon_1.id
+ [...] ('%@aol.com',){stop}
+ User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
+ User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
+ User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
+ {opensql}ROLLBACK{stop}
+
+Another example follows, which is exactly the same except it makes use of the
+:class:`_sql.CTE` construct instead:
+
+.. sourcecode:: python+sql
+
+ >>> cte = select(Address).where(~Address.email_address.like('%@aol.com')).cte()
+ >>> address_cte = aliased(Address, cte)
+ >>> stmt = select(User, address_cte).join_from(User, address_cte).order_by(User.id, address_cte.id)
+ >>> with Session(engine) as session:
+ ... for user, address in session.execute(stmt):
+ ... print(f"{user} {address}")
+ {opensql}BEGIN (implicit)
+ WITH anon_1 AS
+ (SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id
+ FROM address
+ WHERE address.email_address NOT LIKE ?)
+ SELECT user_account.id, user_account.name, user_account.fullname,
+ anon_1.id AS id_1, anon_1.email_address, anon_1.user_id
+ FROM user_account
+ JOIN anon_1 ON user_account.id = anon_1.user_id
+ ORDER BY user_account.id, anon_1.id
+ [...] ('%@aol.com',){stop}
+ User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
+ User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
+ 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
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A scalar subquery is a subquery that returns exactly zero or one row and
+exactly one column. The subquery is then used in the COLUMNS or WHERE clause
+of an enclosing SELECT statement and is different than a regular subquery in
+that it is not used in the FROM clause. A :term:`correlated subquery` is a
+scalar subquery that refers to a table in the enclosing SELECT statement.
+
+SQLAlchemy represents the scalar subquery using the
+:class:`_sql.ScalarSelect` construct, which is part of the
+:class:`_sql.ColumnElement` expression hierarchy, in contrast to the regular
+subquery which is represented by the :class:`_sql.Subquery` construct, which is
+in the :class:`_sql.FromClause` hierarchy.
+
+Scalar subqueries are often, but not necessarily, used with aggregate functions,
+introduced previously at :ref:`tutorial_group_by_w_aggregates`. A scalar
+subquery is indicated explicitly by making use of the :meth:`_sql.Select.scalar_subquery`
+method as below. It's default string form when stringified by itself
+renders as an ordinary SELECT statement that is selecting from two tables::
+
+ >>> subq = select(func.count(address_table.c.id)).\
+ ... where(user_table.c.id == address_table.c.user_id).\
+ ... scalar_subquery()
+ >>> print(subq)
+ {opensql}(SELECT count(address.id) AS count_1
+ FROM address, user_account
+ WHERE user_account.id = address.user_id)
+
+The above ``subq`` object now falls within the :class:`_sql.ColumnElement`
+SQL expression hierarchy, in that it may be used like any other column
+expression::
+
+ >>> print(subq == 5)
+ {opensql}(SELECT count(address.id) AS count_1
+ FROM address, user_account
+ WHERE user_account.id = address.user_id) = :param_1
+
+
+Although the scalar subquery by itself renders both ``user_account`` and
+``address`` in its FROM clause when stringified by itself, when embedding it
+into an enclosing :func:`_sql.select` construct that deals with the
+``user_account`` table, the ``user_account`` table is automatically
+**correlated**, meaning it does not render in the FROM clause of the subquery::
+
+ >>> stmt = select(user_table.c.name, subq.label("address_count"))
+ >>> print(stmt)
+ {opensql}SELECT user_account.name, (SELECT count(address.id) AS count_1
+ FROM address
+ WHERE user_account.id = address.user_id) AS address_count
+ FROM user_account
+
+Simple correlated subqueries will usually do the right thing that's desired.
+However, in the case where the correlation is ambiguous, SQLAlchemy will let
+us know that more clarity is needed::
+
+ >>> stmt = select(
+ ... user_table.c.name,
+ ... address_table.c.email_address,
+ ... subq.label("address_count")
+ ... ).\
+ ... join_from(user_table, address_table).\
+ ... order_by(user_table.c.id, address_table.c.id)
+ >>> print(stmt)
+ Traceback (most recent call last):
+ ...
+ InvalidRequestError: Select statement '<... Select object at ...>' returned
+ no FROM clauses due to auto-correlation; specify correlate(<tables>) to
+ control correlation manually.
+
+To specify that the ``user_table`` is the one we seek to correlate we specify
+this using the :meth:`_sql.ScalarSelect.correlate` or
+:meth:`_sql.ScalarSelect.correlate_except` methods::
+
+ >>> subq = select(func.count(address_table.c.id)).\
+ ... where(user_table.c.id == address_table.c.user_id).\
+ ... scalar_subquery().correlate(user_table)
+
+The statement then can return the data for this column like any other:
+
+.. sourcecode:: pycon+sql
+
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... select(
+ ... user_table.c.name,
+ ... address_table.c.email_address,
+ ... subq.label("address_count")
+ ... ).
+ ... join_from(user_table, address_table).
+ ... order_by(user_table.c.id, address_table.c.id)
+ ... )
+ ... print(result.all())
+ {opensql}BEGIN (implicit)
+ SELECT user_account.name, address.email_address, (SELECT count(address.id) AS count_1
+ FROM address
+ WHERE user_account.id = address.user_id) AS address_count
+ FROM user_account JOIN address ON user_account.id = address.user_id ORDER BY user_account.id, address.id
+ [...] (){stop}
+ [('spongebob', 'spongebob@sqlalchemy.org', 1), ('sandy', 'sandy@sqlalchemy.org', 2),
+ ('sandy', 'sandy@squirrelpower.org', 2)]
+ {opensql}ROLLBACK{stop}
+
+.. _tutorial_exists:
+
+EXISTS subqueries
+^^^^^^^^^^^^^^^^^^
+
+The SQL EXISTS keyword is an operator that is used with :ref:`scalar subqueries
+<tutorial_scalar_subquery>` to return a boolean true or false depending on if
+the SELECT statement would return a row. SQLAlchemy includes a variant of the
+:class:`_sql.ScalarSelect` object called :class:`_sql.Exists`, which will
+generate an EXISTS subquery and is most conveniently generated using the
+:meth:`_sql.SelectBase.exists` method. Below we produce an EXISTS so that we
+can return ``user_account`` rows that have more than one related row in
+``address``:
+
+.. sourcecode:: pycon+sql
+
+ >>> subq = (
+ ... select(func.count(address_table.c.id)).
+ ... where(user_table.c.id == address_table.c.user_id).
+ ... group_by(address_table.c.user_id).
+ ... having(func.count(address_table.c.id) > 1)
+ ... ).exists()
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... select(user_table.c.name).where(subq)
+ ... )
+ ... print(result.all())
+ {opensql}BEGIN (implicit)
+ SELECT user_account.name
+ FROM user_account
+ WHERE EXISTS (SELECT count(address.id) AS count_1
+ FROM address
+ WHERE user_account.id = address.user_id GROUP BY address.user_id
+ HAVING count(address.id) > ?)
+ [...] (1,){stop}
+ [('sandy',)]
+ {opensql}ROLLBACK{stop}
+
+The EXISTS construct is more often than not used as a negation, e.g. NOT EXISTS,
+as it provides a SQL-efficient form of locating rows for which a related
+table has no rows. Below we select user names that have no email addresses;
+note the binary negation operator (``~``) used inside the second WHERE
+clause:
+
+.. sourcecode:: pycon+sql
+
+ >>> subq = (
+ ... select(address_table.c.id).
+ ... where(user_table.c.id == address_table.c.user_id)
+ ... ).exists()
+ >>> with engine.connect() as conn:
+ ... result = conn.execute(
+ ... select(user_table.c.name).where(~subq)
+ ... )
+ ... print(result.all())
+ {opensql}BEGIN (implicit)
+ SELECT user_account.name
+ FROM user_account
+ WHERE NOT (EXISTS (SELECT address.id
+ FROM address
+ WHERE user_account.id = address.user_id))
+ [...] (){stop}
+ [('patrick',)]
+ {opensql}ROLLBACK{stop}
+
+
+.. rst-class:: core-header, orm-addin
+
+.. _tutorial_core_update_delete:
+
+Core UPDATE and DELETE
+----------------------
+
+So far we've covered :class:`_sql.Insert`, so that we can get some data into
+our database, and then spent a lot of time on :class:`_sql.Select` which
+handles the broad range of usage patterns used for retrieving data from the
+database. In this section we will cover the :class:`_sql.Update` and
+:class:`_sql.Delete` constructs, which are used to modify existing rows
+as well as delete existing rows. This section will cover these constructs
+from a Core-centric perspective.
+
+
+.. container:: orm-header
+
+ **ORM Readers** - As was the case mentioned at :ref:`tutorial_core_insert`,
+ the :class:`_sql.Update` and :class:`_sql.Delete` operations when used with
+ the ORM are usually invoked internally from the :class:`_orm.Session`
+ object as part of the :term:`unit of work` process.
+
+ However, unlike :class:`_sql.Insert`, the :class:`_sql.Update` and
+ :class:`_sql.Delete` constructs can also be used directly with the ORM,
+ using a pattern known as "ORM-enabled update and delete"; for this reason,
+ familiarity with these constructs is useful for ORM use. Both styles of
+ use are discussed in the sections :ref:`tutorial_orm_updating` and
+ :ref:`tutorial_orm_deleting`.
+
+The update() SQL Expression Construct
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :func:`_sql.update` function generates a new instance of
+:class:`_sql.Update` which represents an UPDATE statement in SQL, that will
+update existing data in a table.
+
+Like the :func:`_sql.insert` construct, there is a "traditional" form of
+:func:`_sql.update`, which emits UPDATE against a single table at a time and
+does not return any rows. However some backends support an UPDATE statement
+that may modify multiple tables at once, and the UPDATE statement also
+supports RETURNING such that columns contained in matched rows may be returned
+in the result set.
+
+A basic UPDATE looks like::
+
+ >>> from sqlalchemy import update
+ >>> stmt = (
+ ... update(user_table).where(user_table.c.name == 'patrick').
+ ... values(fullname='Patrick the Star')
+ ... )
+ >>> print(stmt)
+ {opensql}UPDATE user_account SET fullname=:fullname WHERE user_account.name = :name_1
+
+The :meth:`_sql.Update.values` method controls the contents of the SET elements
+of the UPDATE statement. This is the same method shared by the :class:`_sql.Insert`
+construct. Parameters can normally be passed using the column names as
+keyword arguments.
+
+UPDATE supports all the major SQL forms of UPDATE, including updates against expressions,
+where we can make use of :class:`_schema.Column` expressions::
+
+ >>> stmt = (
+ ... update(user_table).
+ ... values(fullname="Username: " + user_table.c.name)
+ ... )
+ >>> print(stmt)
+ {opensql}UPDATE user_account SET fullname=(:name_1 || user_account.name)
+
+To support UPDATE in an "executemany" context, where many parameter sets will
+be invoked against the same statement, the :func:`_sql.bindparam`
+construct may be used to set up bound parameters; these replace the places
+that literal values would normally go:
+
+.. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import bindparam
+ >>> stmt = (
+ ... update(user_table).
+ ... where(user_table.c.name == bindparam('oldname')).
+ ... values(name=bindparam('newname'))
+ ... )
+ >>> with engine.begin() as conn:
+ ... conn.execute(
+ ... stmt,
+ ... [
+ ... {'oldname':'jack', 'newname':'ed'},
+ ... {'oldname':'wendy', 'newname':'mary'},
+ ... {'oldname':'jim', 'newname':'jake'},
+ ... ]
+ ... )
+ {opensql}BEGIN (implicit)
+ UPDATE user_account SET name=? WHERE user_account.name = ?
+ [...] (('ed', 'jack'), ('mary', 'wendy'), ('jake', 'jim'))
+ <sqlalchemy.engine.cursor.CursorResult object at 0x...>
+ COMMIT{stop}
+
+Other techniques which may be applied to UPDATE include:
+
+* **Correlated Updates**: a :ref:`correlated subquery <tutorial_scalar_subquery>`
+ may be used anywhere a column expression might be
+ placed::
+
+ >>> scalar_subq = (
+ ... select(address_table.c.email_address).
+ ... where(address_table.c.user_id == user_table.c.id).
+ ... order_by(address_table.c.id).
+ ... limit(1).
+ ... scalar_subquery()
+ ... )
+ >>> update_stmt = update(user_table).values(fullname=scalar_subq)
+ >>> print(update_stmt)
+ {opensql}UPDATE user_account SET fullname=(SELECT address.email_address
+ FROM address
+ WHERE address.user_id = user_account.id ORDER BY address.id
+ LIMIT :param_1)
+
+ ..
+
+
+* **UPDATE..FROM**: Some databases such as PostgreSQL and MySQL support a syntax
+ "UPDATE FROM" where additional tables may be stated in the FROM clause.
+ This syntax will be generated implicitly when additional tables are located
+ in the WHERE clause of the statement::
+
+ >>> update_stmt = (
+ ... update(user_table).
+ ... where(user_table.c.id == address_table.c.user_id).
+ ... where(address_table.c.email_address == 'patrick@aol.com').
+ ... values(fullname='Pat')
+ ... )
+ >>> print(update_stmt)
+ {opensql}UPDATE user_account SET fullname=:fullname FROM address
+ WHERE user_account.id = address.user_id AND address.email_address = :email_address_1
+
+ ..
+
+* **UPDATE..FROM updating multiple tables**: this is a MySQL specific syntax which
+ requires we refer to :class:`_schema.Table` objects in the VALUES
+ clause in order to refer to additional tables::
+
+ >>> update_stmt = (
+ ... update(user_table).
+ ... where(user_table.c.id == address_table.c.user_id).
+ ... where(address_table.c.email_address == 'patrick@aol.com').
+ ... values(
+ ... {
+ ... user_table.c.fullname: "Pat",
+ ... address_table.c.email_address: "pat@aol.com"
+ ... }
+ ... )
+ ... )
+ >>> from sqlalchemy.dialects import mysql
+ >>> print(update_stmt.compile(dialect=mysql.dialect()))
+ {opensql}UPDATE user_account, address
+ SET address.email_address=%s, user_account.fullname=%s
+ WHERE user_account.id = address.user_id AND address.email_address = %s
+
+ ..
+
+* **Parameter Ordered Updates**: Another MySQL-only behavior is that the order
+ of parameters in the SET clause of an UPDATE actually impacts the evaluation
+ of each expression. For this use case, the :meth:`_sql.Update.ordered_values`
+ method accepts a sequence of tuples so that this order may be controlled [1]_::
+
+ >>> update_stmt = (
+ ... update(some_table).
+ ... ordered_values(
+ ... (some_table.c.y, 20),
+ ... (some_table.c.x, some_table.c.y + 10)
+ ... )
+ ... )
+ >>> print(update_stmt)
+ {opensql}UPDATE some_table SET y=:y, x=(some_table.y + :y_1)
+
+ ..
+
+
+.. [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
+ measure of clarity of intent when it is essential that the SET clause
+ of a MySQL UPDATE statement proceed in a specific way.
+
+
+The delete() SQL Expression Construct
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The :func:`_sql.delete` function generates a new instance of
+:class:`_sql.Delete` which represents an DELETE statement in SQL, that will
+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.
+
+::
+
+ >>> from sqlalchemy import delete
+ >>> stmt = (
+ ... delete(user_table).where(user_table.c.name == 'patrick')
+ ... )
+ >>> print(stmt)
+ {opensql}DELETE FROM user_account WHERE user_account.name = :name_1
+
+Like :class:`_sql.Update`, :class:`_sql.Delete` supports the use of correlated
+subqueries in the WHERE clause as well as backend-specific multiple table
+syntaxes, such as ``DELETE FROM..USING`` on MySQL::
+
+ >>> delete_stmt = (
+ ... delete(user_table).
+ ... where(user_table.c.id == address_table.c.user_id).
+ ... where(address_table.c.email_address == 'patrick@aol.com')
+ ... )
+ >>> from sqlalchemy.dialects import mysql
+ >>> print(delete_stmt.compile(dialect=mysql.dialect()))
+ {opensql}DELETE FROM user_account USING user_account, address
+ WHERE user_account.id = address.user_id AND address.email_address = %s
+
+Getting Affected Row Count from UPDATE, DELETE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Both :class:`_sql.Update` and :class:`_sql.Delete` support the ability to
+return the number of rows matched after the statement proceeds, for statements
+that are invoked using Core :class:`_engine.Connection`, i.e.
+:meth:`_engine.Connection.execute`. Per the caveats mentioned below, this value
+is available from the :attr:`_engine.CursorResult.rowcount` attribute:
+
+.. sourcecode:: pycon+sql
+
+ >>> with engine.begin() as conn:
+ ... result = conn.execute(
+ ... update(user_table).
+ ... values(fullname="Patrick McStar").
+ ... where(user_table.c.name == 'patrick')
+ ... )
+ ... print(result.rowcount)
+ {opensql}BEGIN (implicit)
+ UPDATE user_account SET fullname=? WHERE user_account.name = ?
+ [...] ('Patrick McStar', 'patrick'){stop}
+ 1
+ {opensql}COMMIT{stop}
+
+.. tip::
+
+ The :class:`_engine.CursorResult` class is a subclass of
+ :class:`_engine.Result` which contains additional attributes that are
+ specific to the DBAPI ``cursor`` object. An instance of this subclass is
+ returned when a statement is invoked via the
+ :meth:`_engine.Connection.execute` method. When using the ORM, the
+ :meth:`_orm.Session.execute` method returns an object of this type for
+ all INSERT, UPDATE, and DELETE statements.
+
+Facts about :attr:`_engine.CursorResult.rowcount`:
+
+* The value returned is the number of rows **matched** by the WHERE clause of
+ the statement. It does not matter if the row were actually modified or not.
+
+* :attr:`_engine.CursorResult.rowcount` is not necessarily available for an UPDATE
+ or DELETE statement that uses RETURNING.
+
+* For an :ref:`executemany <tutorial_multiple_parameters>` execution,
+ :attr:`_engine.CursorResult.rowcount` may not be available either, which depends
+ highly on the DBAPI module in use as well as configured options. The
+ attribute :attr:`_engine.CursorResult.supports_sane_multi_rowcount` indicates
+ if this value will be available for the current backend in use.
+
+* Some drivers, particularly third party dialects for non-relational databases,
+ may not support :attr:`_engine.CursorResult.rowcount` at all. The
+ :attr:`_engine.CursorResult.supports_sane_rowcount` will indicate this.
+
+* "rowcount" is used by the ORM :term:`unit of work` process to validate that
+ an UPDATE or DELETE statement matched the expected number of rows, and is
+ also essential for the ORM versioning feature documented at
+ :ref:`mapper_version_counter`.
+
+Using RETURNING with UPDATE, DELETE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Like the :class:`_sql.Insert` construct, :class:`_sql.Update` and :class:`_sql.Delete`
+also support the RETURNING clause which is added by using the
+:meth:`_sql.Update.returning` and :meth:`_sql.Delete.returning` methods.
+When these methods are used on a backend that supports RETURNING, selected
+columns from all rows that match the WHERE criteria of the statement
+will be returned in the :class:`_engine.Result` object as rows that can
+be iterated::
+
+
+ >>> update_stmt = (
+ ... update(user_table).where(user_table.c.name == 'patrick').
+ ... values(fullname='Patrick the Star').
+ ... returning(user_table.c.id, user_table.c.name)
+ ... )
+ >>> print(update_stmt)
+ {opensql}UPDATE user_account SET fullname=:fullname
+ WHERE user_account.name = :name_1
+ RETURNING user_account.id, user_account.name
+
+ >>> delete_stmt = (
+ ... delete(user_table).where(user_table.c.name == 'patrick').
+ ... returning(user_table.c.id, user_table.c.name)
+ ... )
+ >>> print(delete_stmt.returning(user_table.c.id, user_table.c.name))
+ {opensql}DELETE FROM user_account
+ WHERE user_account.name = :name_1
+ RETURNING user_account.id, user_account.name
+
+Further Reading for UPDATE, DELETE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. seealso::
+
+ API documentation for UPDATE / DELETE:
+
+ * :class:`_sql.Update`
+
+ * :class:`_sql.Delete`
+
+ ORM-enabled UPDATE and DELETE:
+
+ * :ref:`tutorial_orm_enabled_update`
+
+ * :ref:`tutorial_orm_enabled_delete`
+