:ticket:`11234`
+.. _change_7066:
+
+Improved ``params()`` implementation for executable statements
+--------------------------------------------------------------
+
+The :meth:`_sql.ClauseElement.params` and :meth:`_sql.ClauseElement.unique_params`
+methods have been deprecated in favor of a new implementation on executable
+statements that provides improved performance and better integration with
+ORM-enabled statements.
+
+Executable statement objects like :class:`_sql.Select`, :class:`_sql.CompoundSelect`,
+and :class:`_sql.TextClause` now provide an improved :meth:`_sql.ExecutableStatement.params`
+method that avoids a full cloned traversal of the statement tree. Instead, parameters
+are stored directly on the statement object and efficiently merged during compilation
+and/or cache key traversal.
+
+The new implementation provides several benefits:
+
+* **Better performance** - Parameters are stored in a simple dictionary rather than
+ requiring a full statement tree traversal with cloning
+* **Proper caching integration** - Parameters are correctly integrated into SQLAlchemy's
+ cache key system via ``_generate_cache_key()``
+* **ORM statement compatibility** - Works correctly with ORM-enabled statements, including
+ ORM entities used with :func:`_orm.aliased`, subqueries, CTEs, etc.
+
+Use of :meth:`_sql.ExecutableStatement.params` is unchanged, provided the given
+object is a statement object such as :func:`_sql.select`::
+
+ stmt = select(table).where(table.c.data == bindparam("x"))
+
+ # Execute with parameter value
+ result = connection.execute(stmt.params(x=5))
+
+ # Can be chained and used in subqueries
+ stmt2 = stmt.params(x=6).subquery().select()
+ result = connection.execute(stmt2.params(x=7)) # Uses x=7
+
+The deprecated :meth:`_sql.ClauseElement.params` and :meth:`_sql.ClauseElement.unique_params`
+methods on non-executable elements like :class:`_sql.ColumnElement` and general
+:class:`_sql.ClauseElement` instances will continue to work during the deprecation
+period but will emit deprecation warnings.
+
+:ticket:`7066`
+
+
.. _change_4950:
- CREATE TABLE AS SELECT Support
- -------------------------------
+ CREATE VIEW and CREATE TABLE AS SELECT Support
+ ----------------------------------------------
+
+ SQLAlchemy 2.1 adds support for the SQL ``CREATE VIEW`` and
+ ``CREATE TABLE ... AS SELECT`` constructs, as well as the ``SELECT ... INTO``
+ variant for selected backends. Both DDL statements generate a table
+ or table-like construct based on the structure and rows represented by a
+ SELECT statement. The constructs are available via the :class:`.CreateView`
+ and :class:`_schema.CreateTableAs` DDL classes, as well as the
+ :meth:`_sql.SelectBase.into` convenience method.
+
+ Both constructs work in exactly the same way, including that a :class:`.Table`
+ object is automatically generated from a given :class:`.Select`. DDL
+ can then be emitted by executing the construct directly or by allowing the
+ :meth:`.MetaData.create_all` or :meth:`.Table.create` sequences to emit the
+ correct DDL.
+
+ E.g. using :class:`.CreateView`::
+
+ >>> from sqlalchemy import Table, Column, Integer, String, MetaData
+ >>> from sqlalchemy import CreateView, select
+ >>>
+ >>> metadata_obj = MetaData()
+ >>> user_table = Table(
+ ... "user_account",
+ ... metadata_obj,
+ ... Column("id", Integer, primary_key=True),
+ ... Column("name", String(30)),
+ ... Column("fullname", String),
+ ... )
+ >>> view = CreateView(
+ ... select(user_table).where(user_table.c.name.like("%spongebob%")),
+ ... "spongebob_view",
+ ... metadata=metadata_obj,
+ ... )
+
+
+ The above ``CreateView`` construct will emit CREATE VIEW when executed directly,
+ or when a DDL create operation is run. When using :meth:`.MetaData.create_all`,
+ the view is created after all dependent tables have been created:
+
+ .. sourcecode:: pycon+sql
+
+ >>> from sqlalchemy import create_engine
+ >>> e = create_engine("sqlite://", echo=True)
+ >>> metadata_obj.create_all(e)
+ {opensql}BEGIN (implicit)
+
+ CREATE TABLE user_account (
+ id INTEGER NOT NULL,
+ name VARCHAR(30),
+ fullname VARCHAR,
+ PRIMARY KEY (id)
+ )
+
+ CREATE VIEW spongebob_view AS
+ SELECT user_account.id, user_account.name, user_account.fullname
+ FROM user_account
+ WHERE user_account.name LIKE '%spongebob%'
+
+ COMMIT
+
+ The view is usable in SQL expressions via the :attr:`.CreateView.table` attribute:
+
+ .. sourcecode:: pycon+sql
+
+ >>> with e.connect() as conn:
+ ... conn.execute(select(view.table))
+ {opensql}BEGIN (implicit)
+ SELECT spongebob_view.id, spongebob_view.name, spongebob_view.fullname
+ FROM spongebob_view
+ <sqlalchemy.engine.cursor.CursorResult object at 0x7f573e4a4ad0>
+ ROLLBACK
+
+ :class:`_schema.CreateTableAs` works in the same way, emitting ``CREATE TABLE AS``::
+
+ >>> from sqlalchemy import CreateTableAs
+ >>> select_stmt = select(users.c.id, users.c.name).where(users.c.name == "squidward")
+ >>> create_table_as = CreateTableAs(select_stmt, "squidward_users")
+
+ In this case, :class:`.CreateTableAs` was not given a :class:`.MetaData` collection.
+ While a :class:`.MetaData` collection will be created automatically in this case,
+ the actual ``CREATE TABLE AS`` statement can also be generated by directly
+ executing the object:
+
+ .. sourcecode:: pycon+sql
+
+ >>> with e.begin() as conn:
+ ... conn.execute(create_table_as)
+ {opensql}BEGIN (implicit)
+ CREATE TABLE squidward_users AS SELECT user_account.id, user_account.name
+ FROM user_account
+ WHERE user_account.name = 'squidward'
+ COMMIT
+
+ Like before, the :class:`.Table` is accessible from :attr:`.CreateTableAs.table`:
+
+ .. sourcecode:: pycon+sql
+
+ >>> with e.connect() as conn:
+ ... conn.execute(select(create_table_as.table))
+ {opensql}BEGIN (implicit)
+ SELECT squidward_users.id, squidward_users.name
+ FROM squidward_users
+ <sqlalchemy.engine.cursor.CursorResult object at 0x7f573e4a4f30>
+ ROLLBACK
- SQLAlchemy 2.1 adds support for the SQL ``CREATE TABLE ... AS SELECT``
- construct as well as the ``SELECT ... INTO`` variant for selected backends,
- which creates a new table directly from the results of a SELECT
- statement. This is available via the new :class:`_schema.CreateTableAs` DDL
- construct and the :meth:`_sql.SelectBase.into` convenience method.
-
- The :class:`_schema.CreateTableAs` construct can be used to create a new table
- from any SELECT statement::
-
- >>> from sqlalchemy import select, CreateTableAs
- >>> select_stmt = select(users.c.id, users.c.name).where(users.c.status == "active")
- >>> create_table_as = CreateTableAs(select_stmt, "active_users")
-
- The above construct renders as a ``CREATE TABLE AS`` statement::
-
- >>> print(create_table_as)
- CREATE TABLE active_users AS SELECT users.id, users.name
- FROM users
- WHERE users.status = 'active'
-
- The construct can be executed to emit the above DDL, and the table may then
- be accessed using the :attr:`.CreateTableAs.table` attribute which
- supplies a :class:`.Table`::
-
- >>> print(select(create_table_as.table))
- SELECT users.id, users.name
- FROM active_users
+ .. seealso::
- See :ref:`tutorial_create_table_as` for a tutorial.
+ :ref:`metadata_create_view` - in :ref:`metadata_toplevel`
- .. seealso::
+ :ref:`metadata_create_table_as` - in :ref:`metadata_toplevel`
- :ref:`tutorial_create_table_as` - in the :ref:`unified_tutorial`
+ :class:`_schema.CreateView` - DDL construct for CREATE VIEW
:class:`_schema.CreateTableAs` - DDL construct for CREATE TABLE AS