mirror of
https://github.com/sqlalchemy/sqlalchemy.git
synced 2026-05-15 21:27:23 -04:00
a4bb502cf9
All modules in sqlalchemy.engine are strictly typed with the exception of cursor, default, and reflection. cursor and default pass with non-strict typing, reflection is waiting on the multi-reflection refactor. Behavioral changes: * create_connect_args() methods return a tuple of list, dict, rather than a list of list, dict * removed allow_chars parameter from pyodbc connector ._get_server_version_info() method * the parameter list passed to do_executemany is now a list in all cases. previously, this was being run through dialect.execute_sequence_format, which defaults to tuple and was only intended for individual tuple params. * broke up dialect.dbapi into dialect.import_dbapi class method and dialect.dbapi module object. added a deprecation path for legacy dialects. it's not really feasible to type a single attr as a classmethod vs. module type. The "type_compiler" attribute also has this problem with greater ability to work around, left that one for now. * lots of constants changing to be Enum, so that we can type them. for fixed tuple-position constants in cursor.py / compiler.py (which are used to avoid the speed overhead of namedtuple), using Literal[value] which seems to work well * some tightening up in Row regarding __getitem__, which we can do since we are on full 2.0 style result use * altered the set_connection_execution_options and set_engine_execution_options event flows so that the dictionary of options may be mutated within the event hook, where it will then take effect as the actual options used. Previously, changing the dict would be silently ignored which seems counter-intuitive and not very useful. * A lot of DefaultDialect/DefaultExecutionContext methods and attributes, including underscored ones, move to interfaces. This is not fully ideal as it means the Dialect/ExecutionContext interfaces aren't publicly subclassable directly, but their current purpose is more of documentation for dialect authors who should (and certainly are) still be subclassing the DefaultXYZ versions in all cases Overall, Result was the most extremely difficult class hierarchy to type here as this hierarchy passes through largely amorphous "row" datatypes throughout, which can in fact by all kinds of different things, like raw DBAPI rows, or Row objects, or "scalar"/Any, but at the same time these types have meaning so I tried still maintaining some level of semantic markings for these, it highlights how complex Result is now, as it's trying to be extremely efficient and inlined while also being very open-ended and extensible. Change-Id: I98b75c0c09eab5355fc7a33ba41dd9874274f12a
352 lines
13 KiB
ReStructuredText
352 lines
13 KiB
ReStructuredText
.. highlight:: pycon+sql
|
|
|
|
.. |prev| replace:: :doc:`data_select`
|
|
.. |next| replace:: :doc:`orm_data_manipulation`
|
|
|
|
.. include:: tutorial_nav_include.rst
|
|
|
|
|
|
.. rst-class:: core-header, orm-addin
|
|
|
|
.. _tutorial_core_update_delete:
|
|
|
|
Updating and Deleting Rows with Core
|
|
-------------------------------------
|
|
|
|
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`.
|
|
|
|
.. _tutorial_core_update:
|
|
|
|
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:
|
|
|
|
.. _tutorial_correlated_updates:
|
|
|
|
Correlated Updates
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
An UPDATE statement can make use of rows in other tables by using a
|
|
:ref:`correlated subquery <tutorial_scalar_subquery>`. A 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)
|
|
|
|
|
|
.. _tutorial_update_from:
|
|
|
|
UPDATE..FROM
|
|
~~~~~~~~~~~~~
|
|
|
|
Some databases such as PostgreSQL and MySQL support a syntax "UPDATE FROM"
|
|
where additional tables may be stated directly in a special 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
|
|
|
|
|
|
There is also a MySQL specific syntax that can UPDATE multiple tables. This
|
|
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 [2]_::
|
|
|
|
>>> 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)
|
|
|
|
|
|
.. [2] 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 still 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.
|
|
|
|
.. _tutorial_deletes:
|
|
|
|
The delete() SQL Expression Construct
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
The :func:`_sql.delete` function generates a new instance of
|
|
:class:`_sql.Delete` which represents a 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 on some database backends.
|
|
|
|
::
|
|
|
|
>>> 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
|
|
|
|
|
|
.. _tutorial_multi_table_deletes:
|
|
|
|
Multiple Table Deletes
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
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
|
|
|
|
.. _tutorial_update_delete_rowcount:
|
|
|
|
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{stop}
|
|
|
|
>>> delete_stmt = (
|
|
... delete(user_table).where(user_table.c.name == 'patrick').
|
|
... returning(user_table.c.id, user_table.c.name)
|
|
... )
|
|
>>> print(delete_stmt)
|
|
{opensql}DELETE FROM user_account
|
|
WHERE user_account.name = :name_1
|
|
RETURNING user_account.id, user_account.name{stop}
|
|
|
|
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`
|
|
|