about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py
diff options
context:
space:
mode:
Diffstat (limited to '.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py')
-rw-r--r--.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py7183
1 files changed, 7183 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py b/.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py
new file mode 100644
index 00000000..d137ab50
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/sqlalchemy/sql/selectable.py
@@ -0,0 +1,7183 @@
+# sql/selectable.py
+# Copyright (C) 2005-2025 the SQLAlchemy authors and contributors
+# <see AUTHORS file>
+#
+# This module is part of SQLAlchemy and is released under
+# the MIT License: https://www.opensource.org/licenses/mit-license.php
+
+"""The :class:`_expression.FromClause` class of SQL expression elements,
+representing
+SQL tables and derived rowsets.
+
+"""
+
+from __future__ import annotations
+
+import collections
+from enum import Enum
+import itertools
+from typing import AbstractSet
+from typing import Any as TODO_Any
+from typing import Any
+from typing import Callable
+from typing import cast
+from typing import Dict
+from typing import Generic
+from typing import Iterable
+from typing import Iterator
+from typing import List
+from typing import NamedTuple
+from typing import NoReturn
+from typing import Optional
+from typing import overload
+from typing import Sequence
+from typing import Set
+from typing import Tuple
+from typing import Type
+from typing import TYPE_CHECKING
+from typing import TypeVar
+from typing import Union
+
+from . import cache_key
+from . import coercions
+from . import operators
+from . import roles
+from . import traversals
+from . import type_api
+from . import visitors
+from ._typing import _ColumnsClauseArgument
+from ._typing import _no_kw
+from ._typing import _T
+from ._typing import _TP
+from ._typing import is_column_element
+from ._typing import is_select_statement
+from ._typing import is_subquery
+from ._typing import is_table
+from ._typing import is_text_clause
+from .annotation import Annotated
+from .annotation import SupportsCloneAnnotations
+from .base import _clone
+from .base import _cloned_difference
+from .base import _cloned_intersection
+from .base import _entity_namespace_key
+from .base import _EntityNamespace
+from .base import _expand_cloned
+from .base import _from_objects
+from .base import _generative
+from .base import _never_select_column
+from .base import _NoArg
+from .base import _select_iterables
+from .base import CacheableOptions
+from .base import ColumnCollection
+from .base import ColumnSet
+from .base import CompileState
+from .base import DedupeColumnCollection
+from .base import Executable
+from .base import Generative
+from .base import HasCompileState
+from .base import HasMemoized
+from .base import Immutable
+from .coercions import _document_text_coercion
+from .elements import _anonymous_label
+from .elements import BindParameter
+from .elements import BooleanClauseList
+from .elements import ClauseElement
+from .elements import ClauseList
+from .elements import ColumnClause
+from .elements import ColumnElement
+from .elements import DQLDMLClauseElement
+from .elements import GroupedElement
+from .elements import literal_column
+from .elements import TableValuedColumn
+from .elements import UnaryExpression
+from .operators import OperatorType
+from .sqltypes import NULLTYPE
+from .visitors import _TraverseInternalsType
+from .visitors import InternalTraversal
+from .visitors import prefix_anon_map
+from .. import exc
+from .. import util
+from ..util import HasMemoized_ro_memoized_attribute
+from ..util.typing import Literal
+from ..util.typing import Protocol
+from ..util.typing import Self
+
+
+and_ = BooleanClauseList.and_
+
+
+if TYPE_CHECKING:
+    from ._typing import _ColumnExpressionArgument
+    from ._typing import _ColumnExpressionOrStrLabelArgument
+    from ._typing import _FromClauseArgument
+    from ._typing import _JoinTargetArgument
+    from ._typing import _LimitOffsetType
+    from ._typing import _MAYBE_ENTITY
+    from ._typing import _NOT_ENTITY
+    from ._typing import _OnClauseArgument
+    from ._typing import _SelectStatementForCompoundArgument
+    from ._typing import _T0
+    from ._typing import _T1
+    from ._typing import _T2
+    from ._typing import _T3
+    from ._typing import _T4
+    from ._typing import _T5
+    from ._typing import _T6
+    from ._typing import _T7
+    from ._typing import _TextCoercedExpressionArgument
+    from ._typing import _TypedColumnClauseArgument as _TCCA
+    from ._typing import _TypeEngineArgument
+    from .base import _AmbiguousTableNameMap
+    from .base import ExecutableOption
+    from .base import ReadOnlyColumnCollection
+    from .cache_key import _CacheKeyTraversalType
+    from .compiler import SQLCompiler
+    from .dml import Delete
+    from .dml import Update
+    from .elements import BinaryExpression
+    from .elements import KeyedColumnElement
+    from .elements import Label
+    from .elements import NamedColumn
+    from .elements import TextClause
+    from .functions import Function
+    from .schema import ForeignKey
+    from .schema import ForeignKeyConstraint
+    from .sqltypes import TableValueType
+    from .type_api import TypeEngine
+    from .visitors import _CloneCallableType
+
+
+_ColumnsClauseElement = Union["FromClause", ColumnElement[Any], "TextClause"]
+_LabelConventionCallable = Callable[
+    [Union["ColumnElement[Any]", "TextClause"]], Optional[str]
+]
+
+
+class _JoinTargetProtocol(Protocol):
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]: ...
+
+    @util.ro_non_memoized_property
+    def entity_namespace(self) -> _EntityNamespace: ...
+
+
+_JoinTargetElement = Union["FromClause", _JoinTargetProtocol]
+_OnClauseElement = Union["ColumnElement[bool]", _JoinTargetProtocol]
+
+_ForUpdateOfArgument = Union[
+    # single column, Table, ORM Entity
+    Union[
+        "_ColumnExpressionArgument[Any]",
+        "_FromClauseArgument",
+    ],
+    # or sequence of single column elements
+    Sequence["_ColumnExpressionArgument[Any]"],
+]
+
+
+_SetupJoinsElement = Tuple[
+    _JoinTargetElement,
+    Optional[_OnClauseElement],
+    Optional["FromClause"],
+    Dict[str, Any],
+]
+
+
+_SelectIterable = Iterable[Union["ColumnElement[Any]", "TextClause"]]
+
+
+class _OffsetLimitParam(BindParameter[int]):
+    inherit_cache = True
+
+    @property
+    def _limit_offset_value(self) -> Optional[int]:
+        return self.effective_value
+
+
+class ReturnsRows(roles.ReturnsRowsRole, DQLDMLClauseElement):
+    """The base-most class for Core constructs that have some concept of
+    columns that can represent rows.
+
+    While the SELECT statement and TABLE are the primary things we think
+    of in this category,  DML like INSERT, UPDATE and DELETE can also specify
+    RETURNING which means they can be used in CTEs and other forms, and
+    PostgreSQL has functions that return rows also.
+
+    .. versionadded:: 1.4
+
+    """
+
+    _is_returns_rows = True
+
+    # sub-elements of returns_rows
+    _is_from_clause = False
+    _is_select_base = False
+    _is_select_statement = False
+    _is_lateral = False
+
+    @property
+    def selectable(self) -> ReturnsRows:
+        return self
+
+    @util.ro_non_memoized_property
+    def _all_selected_columns(self) -> _SelectIterable:
+        """A sequence of column expression objects that represents the
+        "selected" columns of this :class:`_expression.ReturnsRows`.
+
+        This is typically equivalent to .exported_columns except it is
+        delivered in the form of a straight sequence and not  keyed
+        :class:`_expression.ColumnCollection`.
+
+        """
+        raise NotImplementedError()
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        """Return ``True`` if this :class:`.ReturnsRows` is
+        'derived' from the given :class:`.FromClause`.
+
+        An example would be an Alias of a Table is derived from that Table.
+
+        """
+        raise NotImplementedError()
+
+    def _generate_fromclause_column_proxies(
+        self,
+        fromclause: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        """Populate columns into an :class:`.AliasedReturnsRows` object."""
+
+        raise NotImplementedError()
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        """reset internal collections for an incoming column being added."""
+        raise NotImplementedError()
+
+    @property
+    def exported_columns(self) -> ReadOnlyColumnCollection[Any, Any]:
+        """A :class:`_expression.ColumnCollection`
+        that represents the "exported"
+        columns of this :class:`_expression.ReturnsRows`.
+
+        The "exported" columns represent the collection of
+        :class:`_expression.ColumnElement`
+        expressions that are rendered by this SQL
+        construct.   There are primary varieties which are the
+        "FROM clause columns" of a FROM clause, such as a table, join,
+        or subquery, the "SELECTed columns", which are the columns in
+        the "columns clause" of a SELECT statement, and the RETURNING
+        columns in a DML statement..
+
+        .. versionadded:: 1.4
+
+        .. seealso::
+
+            :attr:`_expression.FromClause.exported_columns`
+
+            :attr:`_expression.SelectBase.exported_columns`
+        """
+
+        raise NotImplementedError()
+
+
+class ExecutableReturnsRows(Executable, ReturnsRows):
+    """base for executable statements that return rows."""
+
+
+class TypedReturnsRows(ExecutableReturnsRows, Generic[_TP]):
+    """base for a typed executable statements that return rows."""
+
+
+class Selectable(ReturnsRows):
+    """Mark a class as being selectable."""
+
+    __visit_name__ = "selectable"
+
+    is_selectable = True
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        raise NotImplementedError()
+
+    def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+        """Return a LATERAL alias of this :class:`_expression.Selectable`.
+
+        The return value is the :class:`_expression.Lateral` construct also
+        provided by the top-level :func:`_expression.lateral` function.
+
+        .. seealso::
+
+            :ref:`tutorial_lateral_correlation` -  overview of usage.
+
+        """
+        return Lateral._construct(self, name=name)
+
+    @util.deprecated(
+        "1.4",
+        message="The :meth:`.Selectable.replace_selectable` method is "
+        "deprecated, and will be removed in a future release.  Similar "
+        "functionality is available via the sqlalchemy.sql.visitors module.",
+    )
+    @util.preload_module("sqlalchemy.sql.util")
+    def replace_selectable(self, old: FromClause, alias: Alias) -> Self:
+        """Replace all occurrences of :class:`_expression.FromClause`
+        'old' with the given :class:`_expression.Alias`
+        object, returning a copy of this :class:`_expression.FromClause`.
+
+        """
+        return util.preloaded.sql_util.ClauseAdapter(alias).traverse(self)
+
+    def corresponding_column(
+        self, column: KeyedColumnElement[Any], require_embedded: bool = False
+    ) -> Optional[KeyedColumnElement[Any]]:
+        """Given a :class:`_expression.ColumnElement`, return the exported
+        :class:`_expression.ColumnElement` object from the
+        :attr:`_expression.Selectable.exported_columns`
+        collection of this :class:`_expression.Selectable`
+        which corresponds to that
+        original :class:`_expression.ColumnElement` via a common ancestor
+        column.
+
+        :param column: the target :class:`_expression.ColumnElement`
+                      to be matched.
+
+        :param require_embedded: only return corresponding columns for
+         the given :class:`_expression.ColumnElement`, if the given
+         :class:`_expression.ColumnElement`
+         is actually present within a sub-element
+         of this :class:`_expression.Selectable`.
+         Normally the column will match if
+         it merely shares a common ancestor with one of the exported
+         columns of this :class:`_expression.Selectable`.
+
+        .. seealso::
+
+            :attr:`_expression.Selectable.exported_columns` - the
+            :class:`_expression.ColumnCollection`
+            that is used for the operation.
+
+            :meth:`_expression.ColumnCollection.corresponding_column`
+            - implementation
+            method.
+
+        """
+
+        return self.exported_columns.corresponding_column(
+            column, require_embedded
+        )
+
+
+class HasPrefixes:
+    _prefixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
+
+    _has_prefixes_traverse_internals: _TraverseInternalsType = [
+        ("_prefixes", InternalTraversal.dp_prefix_sequence)
+    ]
+
+    @_generative
+    @_document_text_coercion(
+        "prefixes",
+        ":meth:`_expression.HasPrefixes.prefix_with`",
+        ":paramref:`.HasPrefixes.prefix_with.*prefixes`",
+    )
+    def prefix_with(
+        self,
+        *prefixes: _TextCoercedExpressionArgument[Any],
+        dialect: str = "*",
+    ) -> Self:
+        r"""Add one or more expressions following the statement keyword, i.e.
+        SELECT, INSERT, UPDATE, or DELETE. Generative.
+
+        This is used to support backend-specific prefix keywords such as those
+        provided by MySQL.
+
+        E.g.::
+
+            stmt = table.insert().prefix_with("LOW_PRIORITY", dialect="mysql")
+
+            # MySQL 5.7 optimizer hints
+            stmt = select(table).prefix_with("/*+ BKA(t1) */", dialect="mysql")
+
+        Multiple prefixes can be specified by multiple calls
+        to :meth:`_expression.HasPrefixes.prefix_with`.
+
+        :param \*prefixes: textual or :class:`_expression.ClauseElement`
+         construct which
+         will be rendered following the INSERT, UPDATE, or DELETE
+         keyword.
+        :param dialect: optional string dialect name which will
+         limit rendering of this prefix to only that dialect.
+
+        """
+        self._prefixes = self._prefixes + tuple(
+            [
+                (coercions.expect(roles.StatementOptionRole, p), dialect)
+                for p in prefixes
+            ]
+        )
+        return self
+
+
+class HasSuffixes:
+    _suffixes: Tuple[Tuple[DQLDMLClauseElement, str], ...] = ()
+
+    _has_suffixes_traverse_internals: _TraverseInternalsType = [
+        ("_suffixes", InternalTraversal.dp_prefix_sequence)
+    ]
+
+    @_generative
+    @_document_text_coercion(
+        "suffixes",
+        ":meth:`_expression.HasSuffixes.suffix_with`",
+        ":paramref:`.HasSuffixes.suffix_with.*suffixes`",
+    )
+    def suffix_with(
+        self,
+        *suffixes: _TextCoercedExpressionArgument[Any],
+        dialect: str = "*",
+    ) -> Self:
+        r"""Add one or more expressions following the statement as a whole.
+
+        This is used to support backend-specific suffix keywords on
+        certain constructs.
+
+        E.g.::
+
+            stmt = (
+                select(col1, col2)
+                .cte()
+                .suffix_with(
+                    "cycle empno set y_cycle to 1 default 0", dialect="oracle"
+                )
+            )
+
+        Multiple suffixes can be specified by multiple calls
+        to :meth:`_expression.HasSuffixes.suffix_with`.
+
+        :param \*suffixes: textual or :class:`_expression.ClauseElement`
+         construct which
+         will be rendered following the target clause.
+        :param dialect: Optional string dialect name which will
+         limit rendering of this suffix to only that dialect.
+
+        """
+        self._suffixes = self._suffixes + tuple(
+            [
+                (coercions.expect(roles.StatementOptionRole, p), dialect)
+                for p in suffixes
+            ]
+        )
+        return self
+
+
+class HasHints:
+    _hints: util.immutabledict[Tuple[FromClause, str], str] = (
+        util.immutabledict()
+    )
+    _statement_hints: Tuple[Tuple[str, str], ...] = ()
+
+    _has_hints_traverse_internals: _TraverseInternalsType = [
+        ("_statement_hints", InternalTraversal.dp_statement_hint_list),
+        ("_hints", InternalTraversal.dp_table_hint_list),
+    ]
+
+    @_generative
+    def with_statement_hint(self, text: str, dialect_name: str = "*") -> Self:
+        """Add a statement hint to this :class:`_expression.Select` or
+        other selectable object.
+
+        .. tip::
+
+            :meth:`_expression.Select.with_statement_hint` generally adds hints
+            **at the trailing end** of a SELECT statement.  To place
+            dialect-specific hints such as optimizer hints at the **front** of
+            the SELECT statement after the SELECT keyword, use the
+            :meth:`_expression.Select.prefix_with` method for an open-ended
+            space, or for table-specific hints the
+            :meth:`_expression.Select.with_hint` may be used, which places
+            hints in a dialect-specific location.
+
+        This method is similar to :meth:`_expression.Select.with_hint` except
+        that it does not require an individual table, and instead applies to
+        the statement as a whole.
+
+        Hints here are specific to the backend database and may include
+        directives such as isolation levels, file directives, fetch directives,
+        etc.
+
+        .. seealso::
+
+            :meth:`_expression.Select.with_hint`
+
+            :meth:`_expression.Select.prefix_with` - generic SELECT prefixing
+            which also can suit some database-specific HINT syntaxes such as
+            MySQL or Oracle Database optimizer hints
+
+        """
+        return self._with_hint(None, text, dialect_name)
+
+    @_generative
+    def with_hint(
+        self,
+        selectable: _FromClauseArgument,
+        text: str,
+        dialect_name: str = "*",
+    ) -> Self:
+        r"""Add an indexing or other executional context hint for the given
+        selectable to this :class:`_expression.Select` or other selectable
+        object.
+
+        .. tip::
+
+            The :meth:`_expression.Select.with_hint` method adds hints that are
+            **specific to a single table** to a statement, in a location that
+            is **dialect-specific**.  To add generic optimizer hints to the
+            **beginning** of a statement ahead of the SELECT keyword such as
+            for MySQL or Oracle Database, use the
+            :meth:`_expression.Select.prefix_with` method.  To add optimizer
+            hints to the **end** of a statement such as for PostgreSQL, use the
+            :meth:`_expression.Select.with_statement_hint` method.
+
+        The text of the hint is rendered in the appropriate
+        location for the database backend in use, relative
+        to the given :class:`_schema.Table` or :class:`_expression.Alias`
+        passed as the
+        ``selectable`` argument. The dialect implementation
+        typically uses Python string substitution syntax
+        with the token ``%(name)s`` to render the name of
+        the table or alias. E.g. when using Oracle Database, the
+        following::
+
+            select(mytable).with_hint(mytable, "index(%(name)s ix_mytable)")
+
+        Would render SQL as:
+
+        .. sourcecode:: sql
+
+            select /*+ index(mytable ix_mytable) */ ... from mytable
+
+        The ``dialect_name`` option will limit the rendering of a particular
+        hint to a particular backend. Such as, to add hints for both Oracle
+        Database and MSSql simultaneously::
+
+            select(mytable).with_hint(
+                mytable, "index(%(name)s ix_mytable)", "oracle"
+            ).with_hint(mytable, "WITH INDEX ix_mytable", "mssql")
+
+        .. seealso::
+
+            :meth:`_expression.Select.with_statement_hint`
+
+            :meth:`_expression.Select.prefix_with` - generic SELECT prefixing
+            which also can suit some database-specific HINT syntaxes such as
+            MySQL or Oracle Database optimizer hints
+
+        """
+
+        return self._with_hint(selectable, text, dialect_name)
+
+    def _with_hint(
+        self,
+        selectable: Optional[_FromClauseArgument],
+        text: str,
+        dialect_name: str,
+    ) -> Self:
+        if selectable is None:
+            self._statement_hints += ((dialect_name, text),)
+        else:
+            self._hints = self._hints.union(
+                {
+                    (
+                        coercions.expect(roles.FromClauseRole, selectable),
+                        dialect_name,
+                    ): text
+                }
+            )
+        return self
+
+
+class FromClause(roles.AnonymizedFromClauseRole, Selectable):
+    """Represent an element that can be used within the ``FROM``
+    clause of a ``SELECT`` statement.
+
+    The most common forms of :class:`_expression.FromClause` are the
+    :class:`_schema.Table` and the :func:`_expression.select` constructs.  Key
+    features common to all :class:`_expression.FromClause` objects include:
+
+    * a :attr:`.c` collection, which provides per-name access to a collection
+      of :class:`_expression.ColumnElement` objects.
+    * a :attr:`.primary_key` attribute, which is a collection of all those
+      :class:`_expression.ColumnElement`
+      objects that indicate the ``primary_key`` flag.
+    * Methods to generate various derivations of a "from" clause, including
+      :meth:`_expression.FromClause.alias`,
+      :meth:`_expression.FromClause.join`,
+      :meth:`_expression.FromClause.select`.
+
+
+    """
+
+    __visit_name__ = "fromclause"
+    named_with_column = False
+
+    @util.ro_non_memoized_property
+    def _hide_froms(self) -> Iterable[FromClause]:
+        return ()
+
+    _is_clone_of: Optional[FromClause]
+
+    _columns: ColumnCollection[Any, Any]
+
+    schema: Optional[str] = None
+    """Define the 'schema' attribute for this :class:`_expression.FromClause`.
+
+    This is typically ``None`` for most objects except that of
+    :class:`_schema.Table`, where it is taken as the value of the
+    :paramref:`_schema.Table.schema` argument.
+
+    """
+
+    is_selectable = True
+    _is_from_clause = True
+    _is_join = False
+
+    _use_schema_map = False
+
+    def select(self) -> Select[Any]:
+        r"""Return a SELECT of this :class:`_expression.FromClause`.
+
+
+        e.g.::
+
+            stmt = some_table.select().where(some_table.c.id == 5)
+
+        .. seealso::
+
+            :func:`_expression.select` - general purpose
+            method which allows for arbitrary column lists.
+
+        """
+        return Select(self)
+
+    def join(
+        self,
+        right: _FromClauseArgument,
+        onclause: Optional[_ColumnExpressionArgument[bool]] = None,
+        isouter: bool = False,
+        full: bool = False,
+    ) -> Join:
+        """Return a :class:`_expression.Join` from this
+        :class:`_expression.FromClause`
+        to another :class:`FromClause`.
+
+        E.g.::
+
+            from sqlalchemy import join
+
+            j = user_table.join(
+                address_table, user_table.c.id == address_table.c.user_id
+            )
+            stmt = select(user_table).select_from(j)
+
+        would emit SQL along the lines of:
+
+        .. sourcecode:: sql
+
+            SELECT user.id, user.name FROM user
+            JOIN address ON user.id = address.user_id
+
+        :param right: the right side of the join; this is any
+         :class:`_expression.FromClause` object such as a
+         :class:`_schema.Table` object, and
+         may also be a selectable-compatible object such as an ORM-mapped
+         class.
+
+        :param onclause: a SQL expression representing the ON clause of the
+         join.  If left at ``None``, :meth:`_expression.FromClause.join`
+         will attempt to
+         join the two tables based on a foreign key relationship.
+
+        :param isouter: if True, render a LEFT OUTER JOIN, instead of JOIN.
+
+        :param full: if True, render a FULL OUTER JOIN, instead of LEFT OUTER
+         JOIN.  Implies :paramref:`.FromClause.join.isouter`.
+
+        .. seealso::
+
+            :func:`_expression.join` - standalone function
+
+            :class:`_expression.Join` - the type of object produced
+
+        """
+
+        return Join(self, right, onclause, isouter, full)
+
+    def outerjoin(
+        self,
+        right: _FromClauseArgument,
+        onclause: Optional[_ColumnExpressionArgument[bool]] = None,
+        full: bool = False,
+    ) -> Join:
+        """Return a :class:`_expression.Join` from this
+        :class:`_expression.FromClause`
+        to another :class:`FromClause`, with the "isouter" flag set to
+        True.
+
+        E.g.::
+
+            from sqlalchemy import outerjoin
+
+            j = user_table.outerjoin(
+                address_table, user_table.c.id == address_table.c.user_id
+            )
+
+        The above is equivalent to::
+
+            j = user_table.join(
+                address_table, user_table.c.id == address_table.c.user_id, isouter=True
+            )
+
+        :param right: the right side of the join; this is any
+         :class:`_expression.FromClause` object such as a
+         :class:`_schema.Table` object, and
+         may also be a selectable-compatible object such as an ORM-mapped
+         class.
+
+        :param onclause: a SQL expression representing the ON clause of the
+         join.  If left at ``None``, :meth:`_expression.FromClause.join`
+         will attempt to
+         join the two tables based on a foreign key relationship.
+
+        :param full: if True, render a FULL OUTER JOIN, instead of
+         LEFT OUTER JOIN.
+
+        .. seealso::
+
+            :meth:`_expression.FromClause.join`
+
+            :class:`_expression.Join`
+
+        """  # noqa: E501
+
+        return Join(self, right, onclause, True, full)
+
+    def alias(
+        self, name: Optional[str] = None, flat: bool = False
+    ) -> NamedFromClause:
+        """Return an alias of this :class:`_expression.FromClause`.
+
+        E.g.::
+
+            a2 = some_table.alias("a2")
+
+        The above code creates an :class:`_expression.Alias`
+        object which can be used
+        as a FROM clause in any SELECT statement.
+
+        .. seealso::
+
+            :ref:`tutorial_using_aliases`
+
+            :func:`_expression.alias`
+
+        """
+
+        return Alias._construct(self, name=name)
+
+    def tablesample(
+        self,
+        sampling: Union[float, Function[Any]],
+        name: Optional[str] = None,
+        seed: Optional[roles.ExpressionElementRole[Any]] = None,
+    ) -> TableSample:
+        """Return a TABLESAMPLE alias of this :class:`_expression.FromClause`.
+
+        The return value is the :class:`_expression.TableSample`
+        construct also
+        provided by the top-level :func:`_expression.tablesample` function.
+
+        .. seealso::
+
+            :func:`_expression.tablesample` - usage guidelines and parameters
+
+        """
+        return TableSample._construct(
+            self, sampling=sampling, name=name, seed=seed
+        )
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        """Return ``True`` if this :class:`_expression.FromClause` is
+        'derived' from the given ``FromClause``.
+
+        An example would be an Alias of a Table is derived from that Table.
+
+        """
+        # this is essentially an "identity" check in the base class.
+        # Other constructs override this to traverse through
+        # contained elements.
+        return fromclause in self._cloned_set
+
+    def _is_lexical_equivalent(self, other: FromClause) -> bool:
+        """Return ``True`` if this :class:`_expression.FromClause` and
+        the other represent the same lexical identity.
+
+        This tests if either one is a copy of the other, or
+        if they are the same via annotation identity.
+
+        """
+        return bool(self._cloned_set.intersection(other._cloned_set))
+
+    @util.ro_non_memoized_property
+    def description(self) -> str:
+        """A brief description of this :class:`_expression.FromClause`.
+
+        Used primarily for error message formatting.
+
+        """
+        return getattr(self, "name", self.__class__.__name__ + " object")
+
+    def _generate_fromclause_column_proxies(
+        self,
+        fromclause: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        columns._populate_separate_keys(
+            col._make_proxy(
+                fromclause, primary_key=primary_key, foreign_keys=foreign_keys
+            )
+            for col in self.c
+        )
+
+    @util.ro_non_memoized_property
+    def exported_columns(
+        self,
+    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        that represents the "exported"
+        columns of this :class:`_expression.Selectable`.
+
+        The "exported" columns for a :class:`_expression.FromClause`
+        object are synonymous
+        with the :attr:`_expression.FromClause.columns` collection.
+
+        .. versionadded:: 1.4
+
+        .. seealso::
+
+            :attr:`_expression.Selectable.exported_columns`
+
+            :attr:`_expression.SelectBase.exported_columns`
+
+
+        """
+        return self.c
+
+    @util.ro_non_memoized_property
+    def columns(
+        self,
+    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        """A named-based collection of :class:`_expression.ColumnElement`
+        objects maintained by this :class:`_expression.FromClause`.
+
+        The :attr:`.columns`, or :attr:`.c` collection, is the gateway
+        to the construction of SQL expressions using table-bound or
+        other selectable-bound columns::
+
+            select(mytable).where(mytable.c.somecolumn == 5)
+
+        :return: a :class:`.ColumnCollection` object.
+
+        """
+        return self.c
+
+    @util.ro_memoized_property
+    def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        """
+        A synonym for :attr:`.FromClause.columns`
+
+        :return: a :class:`.ColumnCollection`
+
+        """
+        if "_columns" not in self.__dict__:
+            self._setup_collections()
+        return self._columns.as_readonly()
+
+    def _setup_collections(self) -> None:
+        assert "_columns" not in self.__dict__
+        assert "primary_key" not in self.__dict__
+        assert "foreign_keys" not in self.__dict__
+
+        _columns: ColumnCollection[Any, Any] = ColumnCollection()
+        primary_key = ColumnSet()
+        foreign_keys: Set[KeyedColumnElement[Any]] = set()
+
+        self._populate_column_collection(
+            columns=_columns,
+            primary_key=primary_key,
+            foreign_keys=foreign_keys,
+        )
+
+        # assigning these three collections separately is not itself atomic,
+        # but greatly reduces the surface for problems
+        self._columns = _columns
+        self.primary_key = primary_key  # type: ignore
+        self.foreign_keys = foreign_keys  # type: ignore
+
+    @util.ro_non_memoized_property
+    def entity_namespace(self) -> _EntityNamespace:
+        """Return a namespace used for name-based access in SQL expressions.
+
+        This is the namespace that is used to resolve "filter_by()" type
+        expressions, such as::
+
+            stmt.filter_by(address="some address")
+
+        It defaults to the ``.c`` collection, however internally it can
+        be overridden using the "entity_namespace" annotation to deliver
+        alternative results.
+
+        """
+        return self.c
+
+    @util.ro_memoized_property
+    def primary_key(self) -> Iterable[NamedColumn[Any]]:
+        """Return the iterable collection of :class:`_schema.Column` objects
+        which comprise the primary key of this :class:`_selectable.FromClause`.
+
+        For a :class:`_schema.Table` object, this collection is represented
+        by the :class:`_schema.PrimaryKeyConstraint` which itself is an
+        iterable collection of :class:`_schema.Column` objects.
+
+        """
+        self._setup_collections()
+        return self.primary_key
+
+    @util.ro_memoized_property
+    def foreign_keys(self) -> Iterable[ForeignKey]:
+        """Return the collection of :class:`_schema.ForeignKey` marker objects
+        which this FromClause references.
+
+        Each :class:`_schema.ForeignKey` is a member of a
+        :class:`_schema.Table`-wide
+        :class:`_schema.ForeignKeyConstraint`.
+
+        .. seealso::
+
+            :attr:`_schema.Table.foreign_key_constraints`
+
+        """
+        self._setup_collections()
+        return self.foreign_keys
+
+    def _reset_column_collection(self) -> None:
+        """Reset the attributes linked to the ``FromClause.c`` attribute.
+
+        This collection is separate from all the other memoized things
+        as it has shown to be sensitive to being cleared out in situations
+        where enclosing code, typically in a replacement traversal scenario,
+        has already established strong relationships
+        with the exported columns.
+
+        The collection is cleared for the case where a table is having a
+        column added to it as well as within a Join during copy internals.
+
+        """
+
+        for key in ["_columns", "columns", "c", "primary_key", "foreign_keys"]:
+            self.__dict__.pop(key, None)
+
+    @util.ro_non_memoized_property
+    def _select_iterable(self) -> _SelectIterable:
+        return (c for c in self.c if not _never_select_column(c))
+
+    @property
+    def _cols_populated(self) -> bool:
+        return "_columns" in self.__dict__
+
+    def _populate_column_collection(
+        self,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        """Called on subclasses to establish the .c collection.
+
+        Each implementation has a different way of establishing
+        this collection.
+
+        """
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        """Given a column added to the .c collection of an underlying
+        selectable, produce the local version of that column, assuming this
+        selectable ultimately should proxy this column.
+
+        this is used to "ping" a derived selectable to add a new column
+        to its .c. collection when a Column has been added to one of the
+        Table objects it ultimately derives from.
+
+        If the given selectable hasn't populated its .c. collection yet,
+        it should at least pass on the message to the contained selectables,
+        but it will return None.
+
+        This method is currently used by Declarative to allow Table
+        columns to be added to a partially constructed inheritance
+        mapping that may have already produced joins.  The method
+        isn't public right now, as the full span of implications
+        and/or caveats aren't yet clear.
+
+        It's also possible that this functionality could be invoked by
+        default via an event, which would require that
+        selectables maintain a weak referencing collection of all
+        derivations.
+
+        """
+        self._reset_column_collection()
+
+    def _anonymous_fromclause(
+        self, *, name: Optional[str] = None, flat: bool = False
+    ) -> FromClause:
+        return self.alias(name=name)
+
+    if TYPE_CHECKING:
+
+        def self_group(
+            self, against: Optional[OperatorType] = None
+        ) -> Union[FromGrouping, Self]: ...
+
+
+class NamedFromClause(FromClause):
+    """A :class:`.FromClause` that has a name.
+
+    Examples include tables, subqueries, CTEs, aliased tables.
+
+    .. versionadded:: 2.0
+
+    """
+
+    named_with_column = True
+
+    name: str
+
+    @util.preload_module("sqlalchemy.sql.sqltypes")
+    def table_valued(self) -> TableValuedColumn[Any]:
+        """Return a :class:`_sql.TableValuedColumn` object for this
+        :class:`_expression.FromClause`.
+
+        A :class:`_sql.TableValuedColumn` is a :class:`_sql.ColumnElement` that
+        represents a complete row in a table. Support for this construct is
+        backend dependent, and is supported in various forms by backends
+        such as PostgreSQL, Oracle Database and SQL Server.
+
+        E.g.:
+
+        .. sourcecode:: pycon+sql
+
+            >>> from sqlalchemy import select, column, func, table
+            >>> a = table("a", column("id"), column("x"), column("y"))
+            >>> stmt = select(func.row_to_json(a.table_valued()))
+            >>> print(stmt)
+            {printsql}SELECT row_to_json(a) AS row_to_json_1
+            FROM a
+
+        .. versionadded:: 1.4.0b2
+
+        .. seealso::
+
+            :ref:`tutorial_functions` - in the :ref:`unified_tutorial`
+
+        """
+        return TableValuedColumn(self, type_api.TABLEVALUE)
+
+
+class SelectLabelStyle(Enum):
+    """Label style constants that may be passed to
+    :meth:`_sql.Select.set_label_style`."""
+
+    LABEL_STYLE_NONE = 0
+    """Label style indicating no automatic labeling should be applied to the
+    columns clause of a SELECT statement.
+
+    Below, the columns named ``columna`` are both rendered as is, meaning that
+    the name ``columna`` can only refer to the first occurrence of this name
+    within a result set, as well as if the statement were used as a subquery:
+
+    .. sourcecode:: pycon+sql
+
+        >>> from sqlalchemy import table, column, select, true, LABEL_STYLE_NONE
+        >>> table1 = table("table1", column("columna"), column("columnb"))
+        >>> table2 = table("table2", column("columna"), column("columnc"))
+        >>> print(
+        ...     select(table1, table2)
+        ...     .join(table2, true())
+        ...     .set_label_style(LABEL_STYLE_NONE)
+        ... )
+        {printsql}SELECT table1.columna, table1.columnb, table2.columna, table2.columnc
+        FROM table1 JOIN table2 ON true
+
+    Used with the :meth:`_sql.Select.set_label_style` method.
+
+    .. versionadded:: 1.4
+
+    """  # noqa: E501
+
+    LABEL_STYLE_TABLENAME_PLUS_COL = 1
+    """Label style indicating all columns should be labeled as
+    ``<tablename>_<columnname>`` when generating the columns clause of a SELECT
+    statement, to disambiguate same-named columns referenced from different
+    tables, aliases, or subqueries.
+
+    Below, all column names are given a label so that the two same-named
+    columns ``columna`` are disambiguated as ``table1_columna`` and
+    ``table2_columna``:
+
+    .. sourcecode:: pycon+sql
+
+        >>> from sqlalchemy import (
+        ...     table,
+        ...     column,
+        ...     select,
+        ...     true,
+        ...     LABEL_STYLE_TABLENAME_PLUS_COL,
+        ... )
+        >>> table1 = table("table1", column("columna"), column("columnb"))
+        >>> table2 = table("table2", column("columna"), column("columnc"))
+        >>> print(
+        ...     select(table1, table2)
+        ...     .join(table2, true())
+        ...     .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
+        ... )
+        {printsql}SELECT table1.columna AS table1_columna, table1.columnb AS table1_columnb, table2.columna AS table2_columna, table2.columnc AS table2_columnc
+        FROM table1 JOIN table2 ON true
+
+    Used with the :meth:`_sql.GenerativeSelect.set_label_style` method.
+    Equivalent to the legacy method ``Select.apply_labels()``;
+    :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` is SQLAlchemy's legacy
+    auto-labeling style. :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` provides a
+    less intrusive approach to disambiguation of same-named column expressions.
+
+
+    .. versionadded:: 1.4
+
+    """  # noqa: E501
+
+    LABEL_STYLE_DISAMBIGUATE_ONLY = 2
+    """Label style indicating that columns with a name that conflicts with
+    an existing name should be labeled with a semi-anonymizing label
+    when generating the columns clause of a SELECT statement.
+
+    Below, most column names are left unaffected, except for the second
+    occurrence of the name ``columna``, which is labeled using the
+    label ``columna_1`` to disambiguate it from that of ``tablea.columna``:
+
+    .. sourcecode:: pycon+sql
+
+        >>> from sqlalchemy import (
+        ...     table,
+        ...     column,
+        ...     select,
+        ...     true,
+        ...     LABEL_STYLE_DISAMBIGUATE_ONLY,
+        ... )
+        >>> table1 = table("table1", column("columna"), column("columnb"))
+        >>> table2 = table("table2", column("columna"), column("columnc"))
+        >>> print(
+        ...     select(table1, table2)
+        ...     .join(table2, true())
+        ...     .set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
+        ... )
+        {printsql}SELECT table1.columna, table1.columnb, table2.columna AS columna_1, table2.columnc
+        FROM table1 JOIN table2 ON true
+
+    Used with the :meth:`_sql.GenerativeSelect.set_label_style` method,
+    :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` is the default labeling style
+    for all SELECT statements outside of :term:`1.x style` ORM queries.
+
+    .. versionadded:: 1.4
+
+    """  # noqa: E501
+
+    LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
+    """The default label style, refers to
+    :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`.
+
+    .. versionadded:: 1.4
+
+    """
+
+    LABEL_STYLE_LEGACY_ORM = 3
+
+
+(
+    LABEL_STYLE_NONE,
+    LABEL_STYLE_TABLENAME_PLUS_COL,
+    LABEL_STYLE_DISAMBIGUATE_ONLY,
+    _,
+) = list(SelectLabelStyle)
+
+LABEL_STYLE_DEFAULT = LABEL_STYLE_DISAMBIGUATE_ONLY
+
+
+class Join(roles.DMLTableRole, FromClause):
+    """Represent a ``JOIN`` construct between two
+    :class:`_expression.FromClause`
+    elements.
+
+    The public constructor function for :class:`_expression.Join`
+    is the module-level
+    :func:`_expression.join()` function, as well as the
+    :meth:`_expression.FromClause.join` method
+    of any :class:`_expression.FromClause` (e.g. such as
+    :class:`_schema.Table`).
+
+    .. seealso::
+
+        :func:`_expression.join`
+
+        :meth:`_expression.FromClause.join`
+
+    """
+
+    __visit_name__ = "join"
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("left", InternalTraversal.dp_clauseelement),
+        ("right", InternalTraversal.dp_clauseelement),
+        ("onclause", InternalTraversal.dp_clauseelement),
+        ("isouter", InternalTraversal.dp_boolean),
+        ("full", InternalTraversal.dp_boolean),
+    ]
+
+    _is_join = True
+
+    left: FromClause
+    right: FromClause
+    onclause: Optional[ColumnElement[bool]]
+    isouter: bool
+    full: bool
+
+    def __init__(
+        self,
+        left: _FromClauseArgument,
+        right: _FromClauseArgument,
+        onclause: Optional[_OnClauseArgument] = None,
+        isouter: bool = False,
+        full: bool = False,
+    ):
+        """Construct a new :class:`_expression.Join`.
+
+        The usual entrypoint here is the :func:`_expression.join`
+        function or the :meth:`_expression.FromClause.join` method of any
+        :class:`_expression.FromClause` object.
+
+        """
+
+        # when deannotate was removed here, callcounts went up for ORM
+        # compilation of eager joins, since there were more comparisons of
+        # annotated objects.   test_orm.py -> test_fetch_results
+        # was therefore changed to show a more real-world use case, where the
+        # compilation is cached; there's no change in post-cache callcounts.
+        # callcounts for a single compilation in that particular test
+        # that includes about eight joins about 1100 extra fn calls, from
+        # 29200 -> 30373
+
+        self.left = coercions.expect(
+            roles.FromClauseRole,
+            left,
+        )
+        self.right = coercions.expect(
+            roles.FromClauseRole,
+            right,
+        ).self_group()
+
+        if onclause is None:
+            self.onclause = self._match_primaries(self.left, self.right)
+        else:
+            # note: taken from If91f61527236fd4d7ae3cad1f24c38be921c90ba
+            # not merged yet
+            self.onclause = coercions.expect(
+                roles.OnClauseRole, onclause
+            ).self_group(against=operators._asbool)
+
+        self.isouter = isouter
+        self.full = full
+
+    @util.ro_non_memoized_property
+    def description(self) -> str:
+        return "Join object on %s(%d) and %s(%d)" % (
+            self.left.description,
+            id(self.left),
+            self.right.description,
+            id(self.right),
+        )
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        return (
+            # use hash() to ensure direct comparison to annotated works
+            # as well
+            hash(fromclause) == hash(self)
+            or self.left.is_derived_from(fromclause)
+            or self.right.is_derived_from(fromclause)
+        )
+
+    def self_group(
+        self, against: Optional[OperatorType] = None
+    ) -> FromGrouping:
+        return FromGrouping(self)
+
+    @util.preload_module("sqlalchemy.sql.util")
+    def _populate_column_collection(
+        self,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        sqlutil = util.preloaded.sql_util
+        _columns: List[KeyedColumnElement[Any]] = [c for c in self.left.c] + [
+            c for c in self.right.c
+        ]
+
+        primary_key.extend(  # type: ignore
+            sqlutil.reduce_columns(
+                (c for c in _columns if c.primary_key), self.onclause
+            )
+        )
+        columns._populate_separate_keys(
+            (col._tq_key_label, col) for col in _columns  # type: ignore
+        )
+        foreign_keys.update(
+            itertools.chain(*[col.foreign_keys for col in _columns])  # type: ignore  # noqa: E501
+        )
+
+    def _copy_internals(
+        self, clone: _CloneCallableType = _clone, **kw: Any
+    ) -> None:
+        # see Select._copy_internals() for similar concept
+
+        # here we pre-clone "left" and "right" so that we can
+        # determine the new FROM clauses
+        all_the_froms = set(
+            itertools.chain(
+                _from_objects(self.left),
+                _from_objects(self.right),
+            )
+        )
+
+        # run the clone on those.  these will be placed in the
+        # cache used by the clone function
+        new_froms = {f: clone(f, **kw) for f in all_the_froms}
+
+        # set up a special replace function that will replace for
+        # ColumnClause with parent table referring to those
+        # replaced FromClause objects
+        def replace(
+            obj: Union[BinaryExpression[Any], ColumnClause[Any]],
+            **kw: Any,
+        ) -> Optional[KeyedColumnElement[Any]]:
+            if isinstance(obj, ColumnClause) and obj.table in new_froms:
+                newelem = new_froms[obj.table].corresponding_column(obj)
+                return newelem
+            return None
+
+        kw["replace"] = replace
+
+        # run normal _copy_internals.  the clones for
+        # left and right will come from the clone function's
+        # cache
+        super()._copy_internals(clone=clone, **kw)
+
+        self._reset_memoizations()
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        super()._refresh_for_new_column(column)
+        self.left._refresh_for_new_column(column)
+        self.right._refresh_for_new_column(column)
+
+    def _match_primaries(
+        self,
+        left: FromClause,
+        right: FromClause,
+    ) -> ColumnElement[bool]:
+        if isinstance(left, Join):
+            left_right = left.right
+        else:
+            left_right = None
+        return self._join_condition(left, right, a_subset=left_right)
+
+    @classmethod
+    def _join_condition(
+        cls,
+        a: FromClause,
+        b: FromClause,
+        *,
+        a_subset: Optional[FromClause] = None,
+        consider_as_foreign_keys: Optional[
+            AbstractSet[ColumnClause[Any]]
+        ] = None,
+    ) -> ColumnElement[bool]:
+        """Create a join condition between two tables or selectables.
+
+        See sqlalchemy.sql.util.join_condition() for full docs.
+
+        """
+        constraints = cls._joincond_scan_left_right(
+            a, a_subset, b, consider_as_foreign_keys
+        )
+
+        if len(constraints) > 1:
+            cls._joincond_trim_constraints(
+                a, b, constraints, consider_as_foreign_keys
+            )
+
+        if len(constraints) == 0:
+            if isinstance(b, FromGrouping):
+                hint = (
+                    " Perhaps you meant to convert the right side to a "
+                    "subquery using alias()?"
+                )
+            else:
+                hint = ""
+            raise exc.NoForeignKeysError(
+                "Can't find any foreign key relationships "
+                "between '%s' and '%s'.%s"
+                % (a.description, b.description, hint)
+            )
+
+        crit = [(x == y) for x, y in list(constraints.values())[0]]
+        if len(crit) == 1:
+            return crit[0]
+        else:
+            return and_(*crit)
+
+    @classmethod
+    def _can_join(
+        cls,
+        left: FromClause,
+        right: FromClause,
+        *,
+        consider_as_foreign_keys: Optional[
+            AbstractSet[ColumnClause[Any]]
+        ] = None,
+    ) -> bool:
+        if isinstance(left, Join):
+            left_right = left.right
+        else:
+            left_right = None
+
+        constraints = cls._joincond_scan_left_right(
+            a=left,
+            b=right,
+            a_subset=left_right,
+            consider_as_foreign_keys=consider_as_foreign_keys,
+        )
+
+        return bool(constraints)
+
+    @classmethod
+    @util.preload_module("sqlalchemy.sql.util")
+    def _joincond_scan_left_right(
+        cls,
+        a: FromClause,
+        a_subset: Optional[FromClause],
+        b: FromClause,
+        consider_as_foreign_keys: Optional[AbstractSet[ColumnClause[Any]]],
+    ) -> collections.defaultdict[
+        Optional[ForeignKeyConstraint],
+        List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
+    ]:
+        sql_util = util.preloaded.sql_util
+
+        a = coercions.expect(roles.FromClauseRole, a)
+        b = coercions.expect(roles.FromClauseRole, b)
+
+        constraints: collections.defaultdict[
+            Optional[ForeignKeyConstraint],
+            List[Tuple[ColumnClause[Any], ColumnClause[Any]]],
+        ] = collections.defaultdict(list)
+
+        for left in (a_subset, a):
+            if left is None:
+                continue
+            for fk in sorted(
+                b.foreign_keys,
+                key=lambda fk: fk.parent._creation_order,
+            ):
+                if (
+                    consider_as_foreign_keys is not None
+                    and fk.parent not in consider_as_foreign_keys
+                ):
+                    continue
+                try:
+                    col = fk.get_referent(left)
+                except exc.NoReferenceError as nrte:
+                    table_names = {t.name for t in sql_util.find_tables(left)}
+                    if nrte.table_name in table_names:
+                        raise
+                    else:
+                        continue
+
+                if col is not None:
+                    constraints[fk.constraint].append((col, fk.parent))
+            if left is not b:
+                for fk in sorted(
+                    left.foreign_keys,
+                    key=lambda fk: fk.parent._creation_order,
+                ):
+                    if (
+                        consider_as_foreign_keys is not None
+                        and fk.parent not in consider_as_foreign_keys
+                    ):
+                        continue
+                    try:
+                        col = fk.get_referent(b)
+                    except exc.NoReferenceError as nrte:
+                        table_names = {t.name for t in sql_util.find_tables(b)}
+                        if nrte.table_name in table_names:
+                            raise
+                        else:
+                            continue
+
+                    if col is not None:
+                        constraints[fk.constraint].append((col, fk.parent))
+            if constraints:
+                break
+        return constraints
+
+    @classmethod
+    def _joincond_trim_constraints(
+        cls,
+        a: FromClause,
+        b: FromClause,
+        constraints: Dict[Any, Any],
+        consider_as_foreign_keys: Optional[Any],
+    ) -> None:
+        # more than one constraint matched.  narrow down the list
+        # to include just those FKCs that match exactly to
+        # "consider_as_foreign_keys".
+        if consider_as_foreign_keys:
+            for const in list(constraints):
+                if {f.parent for f in const.elements} != set(
+                    consider_as_foreign_keys
+                ):
+                    del constraints[const]
+
+        # if still multiple constraints, but
+        # they all refer to the exact same end result, use it.
+        if len(constraints) > 1:
+            dedupe = {tuple(crit) for crit in constraints.values()}
+            if len(dedupe) == 1:
+                key = list(constraints)[0]
+                constraints = {key: constraints[key]}
+
+        if len(constraints) != 1:
+            raise exc.AmbiguousForeignKeysError(
+                "Can't determine join between '%s' and '%s'; "
+                "tables have more than one foreign key "
+                "constraint relationship between them. "
+                "Please specify the 'onclause' of this "
+                "join explicitly." % (a.description, b.description)
+            )
+
+    def select(self) -> Select[Any]:
+        r"""Create a :class:`_expression.Select` from this
+        :class:`_expression.Join`.
+
+        E.g.::
+
+            stmt = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
+
+            stmt = stmt.select()
+
+        The above will produce a SQL string resembling:
+
+        .. sourcecode:: sql
+
+            SELECT table_a.id, table_a.col, table_b.id, table_b.a_id
+            FROM table_a JOIN table_b ON table_a.id = table_b.a_id
+
+        """
+        return Select(self.left, self.right).select_from(self)
+
+    @util.preload_module("sqlalchemy.sql.util")
+    def _anonymous_fromclause(
+        self, name: Optional[str] = None, flat: bool = False
+    ) -> TODO_Any:
+        sqlutil = util.preloaded.sql_util
+        if flat:
+            if isinstance(self.left, (FromGrouping, Join)):
+                left_name = name  # will recurse
+            else:
+                if name and isinstance(self.left, NamedFromClause):
+                    left_name = f"{name}_{self.left.name}"
+                else:
+                    left_name = name
+            if isinstance(self.right, (FromGrouping, Join)):
+                right_name = name  # will recurse
+            else:
+                if name and isinstance(self.right, NamedFromClause):
+                    right_name = f"{name}_{self.right.name}"
+                else:
+                    right_name = name
+            left_a, right_a = (
+                self.left._anonymous_fromclause(name=left_name, flat=flat),
+                self.right._anonymous_fromclause(name=right_name, flat=flat),
+            )
+            adapter = sqlutil.ClauseAdapter(left_a).chain(
+                sqlutil.ClauseAdapter(right_a)
+            )
+
+            return left_a.join(
+                right_a,
+                adapter.traverse(self.onclause),
+                isouter=self.isouter,
+                full=self.full,
+            )
+        else:
+            return (
+                self.select()
+                .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
+                .correlate(None)
+                .alias(name)
+            )
+
+    @util.ro_non_memoized_property
+    def _hide_froms(self) -> Iterable[FromClause]:
+        return itertools.chain(
+            *[_from_objects(x.left, x.right) for x in self._cloned_set]
+        )
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        self_list: List[FromClause] = [self]
+        return self_list + self.left._from_objects + self.right._from_objects
+
+
+class NoInit:
+    def __init__(self, *arg: Any, **kw: Any):
+        raise NotImplementedError(
+            "The %s class is not intended to be constructed "
+            "directly.  Please use the %s() standalone "
+            "function or the %s() method available from appropriate "
+            "selectable objects."
+            % (
+                self.__class__.__name__,
+                self.__class__.__name__.lower(),
+                self.__class__.__name__.lower(),
+            )
+        )
+
+
+class LateralFromClause(NamedFromClause):
+    """mark a FROM clause as being able to render directly as LATERAL"""
+
+
+# FromClause ->
+#   AliasedReturnsRows
+#        -> Alias   only for FromClause
+#        -> Subquery  only for SelectBase
+#        -> CTE only for HasCTE -> SelectBase, DML
+#        -> Lateral -> FromClause, but we accept SelectBase
+#           w/ non-deprecated coercion
+#        -> TableSample -> only for FromClause
+
+
+class AliasedReturnsRows(NoInit, NamedFromClause):
+    """Base class of aliases against tables, subqueries, and other
+    selectables."""
+
+    _is_from_container = True
+
+    _supports_derived_columns = False
+
+    element: ReturnsRows
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("element", InternalTraversal.dp_clauseelement),
+        ("name", InternalTraversal.dp_anon_name),
+    ]
+
+    @classmethod
+    def _construct(
+        cls,
+        selectable: Any,
+        *,
+        name: Optional[str] = None,
+        **kw: Any,
+    ) -> Self:
+        obj = cls.__new__(cls)
+        obj._init(selectable, name=name, **kw)
+        return obj
+
+    def _init(self, selectable: Any, *, name: Optional[str] = None) -> None:
+        self.element = coercions.expect(
+            roles.ReturnsRowsRole, selectable, apply_propagate_attrs=self
+        )
+        self.element = selectable
+        self._orig_name = name
+        if name is None:
+            if (
+                isinstance(selectable, FromClause)
+                and selectable.named_with_column
+            ):
+                name = getattr(selectable, "name", None)
+                if isinstance(name, _anonymous_label):
+                    name = None
+            name = _anonymous_label.safe_construct(id(self), name or "anon")
+        self.name = name
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        super()._refresh_for_new_column(column)
+        self.element._refresh_for_new_column(column)
+
+    def _populate_column_collection(
+        self,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        self.element._generate_fromclause_column_proxies(
+            self, columns, primary_key=primary_key, foreign_keys=foreign_keys
+        )
+
+    @util.ro_non_memoized_property
+    def description(self) -> str:
+        name = self.name
+        if isinstance(name, _anonymous_label):
+            name = "anon_1"
+
+        return name
+
+    @util.ro_non_memoized_property
+    def implicit_returning(self) -> bool:
+        return self.element.implicit_returning  # type: ignore
+
+    @property
+    def original(self) -> ReturnsRows:
+        """Legacy for dialects that are referring to Alias.original."""
+        return self.element
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        if fromclause in self._cloned_set:
+            return True
+        return self.element.is_derived_from(fromclause)
+
+    def _copy_internals(
+        self, clone: _CloneCallableType = _clone, **kw: Any
+    ) -> None:
+        existing_element = self.element
+
+        super()._copy_internals(clone=clone, **kw)
+
+        # the element clone is usually against a Table that returns the
+        # same object.  don't reset exported .c. collections and other
+        # memoized details if it was not changed.  this saves a lot on
+        # performance.
+        if existing_element is not self.element:
+            self._reset_column_collection()
+
+    @property
+    def _from_objects(self) -> List[FromClause]:
+        return [self]
+
+
+class FromClauseAlias(AliasedReturnsRows):
+    element: FromClause
+
+
+class Alias(roles.DMLTableRole, FromClauseAlias):
+    """Represents an table or selectable alias (AS).
+
+    Represents an alias, as typically applied to any table or
+    sub-select within a SQL statement using the ``AS`` keyword (or
+    without the keyword on certain databases such as Oracle Database).
+
+    This object is constructed from the :func:`_expression.alias` module
+    level function as well as the :meth:`_expression.FromClause.alias`
+    method available
+    on all :class:`_expression.FromClause` subclasses.
+
+    .. seealso::
+
+        :meth:`_expression.FromClause.alias`
+
+    """
+
+    __visit_name__ = "alias"
+
+    inherit_cache = True
+
+    element: FromClause
+
+    @classmethod
+    def _factory(
+        cls,
+        selectable: FromClause,
+        name: Optional[str] = None,
+        flat: bool = False,
+    ) -> NamedFromClause:
+        return coercions.expect(
+            roles.FromClauseRole, selectable, allow_select=True
+        ).alias(name=name, flat=flat)
+
+
+class TableValuedAlias(LateralFromClause, Alias):
+    """An alias against a "table valued" SQL function.
+
+    This construct provides for a SQL function that returns columns
+    to be used in the FROM clause of a SELECT statement.   The
+    object is generated using the :meth:`_functions.FunctionElement.table_valued`
+    method, e.g.:
+
+    .. sourcecode:: pycon+sql
+
+        >>> from sqlalchemy import select, func
+        >>> fn = func.json_array_elements_text('["one", "two", "three"]').table_valued(
+        ...     "value"
+        ... )
+        >>> print(select(fn.c.value))
+        {printsql}SELECT anon_1.value
+        FROM json_array_elements_text(:json_array_elements_text_1) AS anon_1
+
+    .. versionadded:: 1.4.0b2
+
+    .. seealso::
+
+        :ref:`tutorial_functions_table_valued` - in the :ref:`unified_tutorial`
+
+    """  # noqa: E501
+
+    __visit_name__ = "table_valued_alias"
+
+    _supports_derived_columns = True
+    _render_derived = False
+    _render_derived_w_types = False
+    joins_implicitly = False
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("element", InternalTraversal.dp_clauseelement),
+        ("name", InternalTraversal.dp_anon_name),
+        ("_tableval_type", InternalTraversal.dp_type),
+        ("_render_derived", InternalTraversal.dp_boolean),
+        ("_render_derived_w_types", InternalTraversal.dp_boolean),
+    ]
+
+    def _init(
+        self,
+        selectable: Any,
+        *,
+        name: Optional[str] = None,
+        table_value_type: Optional[TableValueType] = None,
+        joins_implicitly: bool = False,
+    ) -> None:
+        super()._init(selectable, name=name)
+
+        self.joins_implicitly = joins_implicitly
+        self._tableval_type = (
+            type_api.TABLEVALUE
+            if table_value_type is None
+            else table_value_type
+        )
+
+    @HasMemoized.memoized_attribute
+    def column(self) -> TableValuedColumn[Any]:
+        """Return a column expression representing this
+        :class:`_sql.TableValuedAlias`.
+
+        This accessor is used to implement the
+        :meth:`_functions.FunctionElement.column_valued` method. See that
+        method for further details.
+
+        E.g.:
+
+        .. sourcecode:: pycon+sql
+
+            >>> print(select(func.some_func().table_valued("value").column))
+            {printsql}SELECT anon_1 FROM some_func() AS anon_1
+
+        .. seealso::
+
+            :meth:`_functions.FunctionElement.column_valued`
+
+        """
+
+        return TableValuedColumn(self, self._tableval_type)
+
+    def alias(
+        self, name: Optional[str] = None, flat: bool = False
+    ) -> TableValuedAlias:
+        """Return a new alias of this :class:`_sql.TableValuedAlias`.
+
+        This creates a distinct FROM object that will be distinguished
+        from the original one when used in a SQL statement.
+
+        """
+
+        tva: TableValuedAlias = TableValuedAlias._construct(
+            self,
+            name=name,
+            table_value_type=self._tableval_type,
+            joins_implicitly=self.joins_implicitly,
+        )
+
+        if self._render_derived:
+            tva._render_derived = True
+            tva._render_derived_w_types = self._render_derived_w_types
+
+        return tva
+
+    def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+        """Return a new :class:`_sql.TableValuedAlias` with the lateral flag
+        set, so that it renders as LATERAL.
+
+        .. seealso::
+
+            :func:`_expression.lateral`
+
+        """
+        tva = self.alias(name=name)
+        tva._is_lateral = True
+        return tva
+
+    def render_derived(
+        self,
+        name: Optional[str] = None,
+        with_types: bool = False,
+    ) -> TableValuedAlias:
+        """Apply "render derived" to this :class:`_sql.TableValuedAlias`.
+
+        This has the effect of the individual column names listed out
+        after the alias name in the "AS" sequence, e.g.:
+
+        .. sourcecode:: pycon+sql
+
+            >>> print(
+            ...     select(
+            ...         func.unnest(array(["one", "two", "three"]))
+            ...         .table_valued("x", with_ordinality="o")
+            ...         .render_derived()
+            ...     )
+            ... )
+            {printsql}SELECT anon_1.x, anon_1.o
+            FROM unnest(ARRAY[%(param_1)s, %(param_2)s, %(param_3)s]) WITH ORDINALITY AS anon_1(x, o)
+
+        The ``with_types`` keyword will render column types inline within
+        the alias expression (this syntax currently applies to the
+        PostgreSQL database):
+
+        .. sourcecode:: pycon+sql
+
+            >>> print(
+            ...     select(
+            ...         func.json_to_recordset('[{"a":1,"b":"foo"},{"a":"2","c":"bar"}]')
+            ...         .table_valued(column("a", Integer), column("b", String))
+            ...         .render_derived(with_types=True)
+            ...     )
+            ... )
+            {printsql}SELECT anon_1.a, anon_1.b FROM json_to_recordset(:json_to_recordset_1)
+            AS anon_1(a INTEGER, b VARCHAR)
+
+        :param name: optional string name that will be applied to the alias
+         generated.  If left as None, a unique anonymizing name will be used.
+
+        :param with_types: if True, the derived columns will include the
+         datatype specification with each column. This is a special syntax
+         currently known to be required by PostgreSQL for some SQL functions.
+
+        """  # noqa: E501
+
+        # note: don't use the @_generative system here, keep a reference
+        # to the original object.  otherwise you can have re-use of the
+        # python id() of the original which can cause name conflicts if
+        # a new anon-name grabs the same identifier as the local anon-name
+        # (just saw it happen on CI)
+
+        # construct against original to prevent memory growth
+        # for repeated generations
+        new_alias: TableValuedAlias = TableValuedAlias._construct(
+            self.element,
+            name=name,
+            table_value_type=self._tableval_type,
+            joins_implicitly=self.joins_implicitly,
+        )
+        new_alias._render_derived = True
+        new_alias._render_derived_w_types = with_types
+        return new_alias
+
+
+class Lateral(FromClauseAlias, LateralFromClause):
+    """Represent a LATERAL subquery.
+
+    This object is constructed from the :func:`_expression.lateral` module
+    level function as well as the :meth:`_expression.FromClause.lateral`
+    method available
+    on all :class:`_expression.FromClause` subclasses.
+
+    While LATERAL is part of the SQL standard, currently only more recent
+    PostgreSQL versions provide support for this keyword.
+
+    .. seealso::
+
+        :ref:`tutorial_lateral_correlation` -  overview of usage.
+
+    """
+
+    __visit_name__ = "lateral"
+    _is_lateral = True
+
+    inherit_cache = True
+
+    @classmethod
+    def _factory(
+        cls,
+        selectable: Union[SelectBase, _FromClauseArgument],
+        name: Optional[str] = None,
+    ) -> LateralFromClause:
+        return coercions.expect(
+            roles.FromClauseRole, selectable, explicit_subquery=True
+        ).lateral(name=name)
+
+
+class TableSample(FromClauseAlias):
+    """Represent a TABLESAMPLE clause.
+
+    This object is constructed from the :func:`_expression.tablesample` module
+    level function as well as the :meth:`_expression.FromClause.tablesample`
+    method
+    available on all :class:`_expression.FromClause` subclasses.
+
+    .. seealso::
+
+        :func:`_expression.tablesample`
+
+    """
+
+    __visit_name__ = "tablesample"
+
+    _traverse_internals: _TraverseInternalsType = (
+        AliasedReturnsRows._traverse_internals
+        + [
+            ("sampling", InternalTraversal.dp_clauseelement),
+            ("seed", InternalTraversal.dp_clauseelement),
+        ]
+    )
+
+    @classmethod
+    def _factory(
+        cls,
+        selectable: _FromClauseArgument,
+        sampling: Union[float, Function[Any]],
+        name: Optional[str] = None,
+        seed: Optional[roles.ExpressionElementRole[Any]] = None,
+    ) -> TableSample:
+        return coercions.expect(roles.FromClauseRole, selectable).tablesample(
+            sampling, name=name, seed=seed
+        )
+
+    @util.preload_module("sqlalchemy.sql.functions")
+    def _init(  # type: ignore[override]
+        self,
+        selectable: Any,
+        *,
+        name: Optional[str] = None,
+        sampling: Union[float, Function[Any]],
+        seed: Optional[roles.ExpressionElementRole[Any]] = None,
+    ) -> None:
+        assert sampling is not None
+        functions = util.preloaded.sql_functions
+        if not isinstance(sampling, functions.Function):
+            sampling = functions.func.system(sampling)
+
+        self.sampling: Function[Any] = sampling
+        self.seed = seed
+        super()._init(selectable, name=name)
+
+    def _get_method(self) -> Function[Any]:
+        return self.sampling
+
+
+class CTE(
+    roles.DMLTableRole,
+    roles.IsCTERole,
+    Generative,
+    HasPrefixes,
+    HasSuffixes,
+    AliasedReturnsRows,
+):
+    """Represent a Common Table Expression.
+
+    The :class:`_expression.CTE` object is obtained using the
+    :meth:`_sql.SelectBase.cte` method from any SELECT statement. A less often
+    available syntax also allows use of the :meth:`_sql.HasCTE.cte` method
+    present on :term:`DML` constructs such as :class:`_sql.Insert`,
+    :class:`_sql.Update` and
+    :class:`_sql.Delete`.   See the :meth:`_sql.HasCTE.cte` method for
+    usage details on CTEs.
+
+    .. seealso::
+
+        :ref:`tutorial_subqueries_ctes` - in the 2.0 tutorial
+
+        :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+    """
+
+    __visit_name__ = "cte"
+
+    _traverse_internals: _TraverseInternalsType = (
+        AliasedReturnsRows._traverse_internals
+        + [
+            ("_cte_alias", InternalTraversal.dp_clauseelement),
+            ("_restates", InternalTraversal.dp_clauseelement),
+            ("recursive", InternalTraversal.dp_boolean),
+            ("nesting", InternalTraversal.dp_boolean),
+        ]
+        + HasPrefixes._has_prefixes_traverse_internals
+        + HasSuffixes._has_suffixes_traverse_internals
+    )
+
+    element: HasCTE
+
+    @classmethod
+    def _factory(
+        cls,
+        selectable: HasCTE,
+        name: Optional[str] = None,
+        recursive: bool = False,
+    ) -> CTE:
+        r"""Return a new :class:`_expression.CTE`,
+        or Common Table Expression instance.
+
+        Please see :meth:`_expression.HasCTE.cte` for detail on CTE usage.
+
+        """
+        return coercions.expect(roles.HasCTERole, selectable).cte(
+            name=name, recursive=recursive
+        )
+
+    def _init(
+        self,
+        selectable: Select[Any],
+        *,
+        name: Optional[str] = None,
+        recursive: bool = False,
+        nesting: bool = False,
+        _cte_alias: Optional[CTE] = None,
+        _restates: Optional[CTE] = None,
+        _prefixes: Optional[Tuple[()]] = None,
+        _suffixes: Optional[Tuple[()]] = None,
+    ) -> None:
+        self.recursive = recursive
+        self.nesting = nesting
+        self._cte_alias = _cte_alias
+        # Keep recursivity reference with union/union_all
+        self._restates = _restates
+        if _prefixes:
+            self._prefixes = _prefixes
+        if _suffixes:
+            self._suffixes = _suffixes
+        super()._init(selectable, name=name)
+
+    def _populate_column_collection(
+        self,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        if self._cte_alias is not None:
+            self._cte_alias._generate_fromclause_column_proxies(
+                self,
+                columns,
+                primary_key=primary_key,
+                foreign_keys=foreign_keys,
+            )
+        else:
+            self.element._generate_fromclause_column_proxies(
+                self,
+                columns,
+                primary_key=primary_key,
+                foreign_keys=foreign_keys,
+            )
+
+    def alias(self, name: Optional[str] = None, flat: bool = False) -> CTE:
+        """Return an :class:`_expression.Alias` of this
+        :class:`_expression.CTE`.
+
+        This method is a CTE-specific specialization of the
+        :meth:`_expression.FromClause.alias` method.
+
+        .. seealso::
+
+            :ref:`tutorial_using_aliases`
+
+            :func:`_expression.alias`
+
+        """
+        return CTE._construct(
+            self.element,
+            name=name,
+            recursive=self.recursive,
+            nesting=self.nesting,
+            _cte_alias=self,
+            _prefixes=self._prefixes,
+            _suffixes=self._suffixes,
+        )
+
+    def union(self, *other: _SelectStatementForCompoundArgument[Any]) -> CTE:
+        r"""Return a new :class:`_expression.CTE` with a SQL ``UNION``
+        of the original CTE against the given selectables provided
+        as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+        .. seealso::
+
+            :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+        """
+        assert is_select_statement(
+            self.element
+        ), f"CTE element f{self.element} does not support union()"
+
+        return CTE._construct(
+            self.element.union(*other),
+            name=self.name,
+            recursive=self.recursive,
+            nesting=self.nesting,
+            _restates=self,
+            _prefixes=self._prefixes,
+            _suffixes=self._suffixes,
+        )
+
+    def union_all(
+        self, *other: _SelectStatementForCompoundArgument[Any]
+    ) -> CTE:
+        r"""Return a new :class:`_expression.CTE` with a SQL ``UNION ALL``
+        of the original CTE against the given selectables provided
+        as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28 multiple elements are now accepted.
+
+        .. seealso::
+
+            :meth:`_sql.HasCTE.cte` - examples of calling styles
+
+        """
+
+        assert is_select_statement(
+            self.element
+        ), f"CTE element f{self.element} does not support union_all()"
+
+        return CTE._construct(
+            self.element.union_all(*other),
+            name=self.name,
+            recursive=self.recursive,
+            nesting=self.nesting,
+            _restates=self,
+            _prefixes=self._prefixes,
+            _suffixes=self._suffixes,
+        )
+
+    def _get_reference_cte(self) -> CTE:
+        """
+        A recursive CTE is updated to attach the recursive part.
+        Updated CTEs should still refer to the original CTE.
+        This function returns this reference identifier.
+        """
+        return self._restates if self._restates is not None else self
+
+
+class _CTEOpts(NamedTuple):
+    nesting: bool
+
+
+class _ColumnsPlusNames(NamedTuple):
+    required_label_name: Optional[str]
+    """
+    string label name, if non-None, must be rendered as a
+    label, i.e. "AS <name>"
+    """
+
+    proxy_key: Optional[str]
+    """
+    proxy_key that is to be part of the result map for this
+    col.  this is also the key in a fromclause.c or
+    select.selected_columns collection
+    """
+
+    fallback_label_name: Optional[str]
+    """
+    name that can be used to render an "AS <name>" when
+    we have to render a label even though
+    required_label_name was not given
+    """
+
+    column: Union[ColumnElement[Any], TextClause]
+    """
+    the ColumnElement itself
+    """
+
+    repeated: bool
+    """
+    True if this is a duplicate of a previous column
+    in the list of columns
+    """
+
+
+class SelectsRows(ReturnsRows):
+    """Sub-base of ReturnsRows for elements that deliver rows
+    directly, namely SELECT and INSERT/UPDATE/DELETE..RETURNING"""
+
+    _label_style: SelectLabelStyle = LABEL_STYLE_NONE
+
+    def _generate_columns_plus_names(
+        self,
+        anon_for_dupe_key: bool,
+        cols: Optional[_SelectIterable] = None,
+    ) -> List[_ColumnsPlusNames]:
+        """Generate column names as rendered in a SELECT statement by
+        the compiler.
+
+        This is distinct from the _column_naming_convention generator that's
+        intended for population of .c collections and similar, which has
+        different rules.   the collection returned here calls upon the
+        _column_naming_convention as well.
+
+        """
+
+        if cols is None:
+            cols = self._all_selected_columns
+
+        key_naming_convention = SelectState._column_naming_convention(
+            self._label_style
+        )
+
+        names = {}
+
+        result: List[_ColumnsPlusNames] = []
+        result_append = result.append
+
+        table_qualified = self._label_style is LABEL_STYLE_TABLENAME_PLUS_COL
+        label_style_none = self._label_style is LABEL_STYLE_NONE
+
+        # a counter used for "dedupe" labels, which have double underscores
+        # in them and are never referred by name; they only act
+        # as positional placeholders.  they need only be unique within
+        # the single columns clause they're rendered within (required by
+        # some dbs such as mysql).  So their anon identity is tracked against
+        # a fixed counter rather than hash() identity.
+        dedupe_hash = 1
+
+        for c in cols:
+            repeated = False
+
+            if not c._render_label_in_columns_clause:
+                effective_name = required_label_name = fallback_label_name = (
+                    None
+                )
+            elif label_style_none:
+                if TYPE_CHECKING:
+                    assert is_column_element(c)
+
+                effective_name = required_label_name = None
+                fallback_label_name = c._non_anon_label or c._anon_name_label
+            else:
+                if TYPE_CHECKING:
+                    assert is_column_element(c)
+
+                if table_qualified:
+                    required_label_name = effective_name = (
+                        fallback_label_name
+                    ) = c._tq_label
+                else:
+                    effective_name = fallback_label_name = c._non_anon_label
+                    required_label_name = None
+
+                if effective_name is None:
+                    # it seems like this could be _proxy_key and we would
+                    # not need _expression_label but it isn't
+                    # giving us a clue when to use anon_label instead
+                    expr_label = c._expression_label
+                    if expr_label is None:
+                        repeated = c._anon_name_label in names
+                        names[c._anon_name_label] = c
+                        effective_name = required_label_name = None
+
+                        if repeated:
+                            # here, "required_label_name" is sent as
+                            # "None" and "fallback_label_name" is sent.
+                            if table_qualified:
+                                fallback_label_name = (
+                                    c._dedupe_anon_tq_label_idx(dedupe_hash)
+                                )
+                                dedupe_hash += 1
+                            else:
+                                fallback_label_name = c._dedupe_anon_label_idx(
+                                    dedupe_hash
+                                )
+                                dedupe_hash += 1
+                        else:
+                            fallback_label_name = c._anon_name_label
+                    else:
+                        required_label_name = effective_name = (
+                            fallback_label_name
+                        ) = expr_label
+
+            if effective_name is not None:
+                if TYPE_CHECKING:
+                    assert is_column_element(c)
+
+                if effective_name in names:
+                    # when looking to see if names[name] is the same column as
+                    # c, use hash(), so that an annotated version of the column
+                    # is seen as the same as the non-annotated
+                    if hash(names[effective_name]) != hash(c):
+                        # different column under the same name.  apply
+                        # disambiguating label
+                        if table_qualified:
+                            required_label_name = fallback_label_name = (
+                                c._anon_tq_label
+                            )
+                        else:
+                            required_label_name = fallback_label_name = (
+                                c._anon_name_label
+                            )
+
+                        if anon_for_dupe_key and required_label_name in names:
+                            # here, c._anon_tq_label is definitely unique to
+                            # that column identity (or annotated version), so
+                            # this should always be true.
+                            # this is also an infrequent codepath because
+                            # you need two levels of duplication to be here
+                            assert hash(names[required_label_name]) == hash(c)
+
+                            # the column under the disambiguating label is
+                            # already present.  apply the "dedupe" label to
+                            # subsequent occurrences of the column so that the
+                            # original stays non-ambiguous
+                            if table_qualified:
+                                required_label_name = fallback_label_name = (
+                                    c._dedupe_anon_tq_label_idx(dedupe_hash)
+                                )
+                                dedupe_hash += 1
+                            else:
+                                required_label_name = fallback_label_name = (
+                                    c._dedupe_anon_label_idx(dedupe_hash)
+                                )
+                                dedupe_hash += 1
+                            repeated = True
+                        else:
+                            names[required_label_name] = c
+                    elif anon_for_dupe_key:
+                        # same column under the same name. apply the "dedupe"
+                        # label so that the original stays non-ambiguous
+                        if table_qualified:
+                            required_label_name = fallback_label_name = (
+                                c._dedupe_anon_tq_label_idx(dedupe_hash)
+                            )
+                            dedupe_hash += 1
+                        else:
+                            required_label_name = fallback_label_name = (
+                                c._dedupe_anon_label_idx(dedupe_hash)
+                            )
+                            dedupe_hash += 1
+                        repeated = True
+                else:
+                    names[effective_name] = c
+
+            result_append(
+                _ColumnsPlusNames(
+                    required_label_name,
+                    key_naming_convention(c),
+                    fallback_label_name,
+                    c,
+                    repeated,
+                )
+            )
+
+        return result
+
+
+class HasCTE(roles.HasCTERole, SelectsRows):
+    """Mixin that declares a class to include CTE support."""
+
+    _has_ctes_traverse_internals: _TraverseInternalsType = [
+        ("_independent_ctes", InternalTraversal.dp_clauseelement_list),
+        ("_independent_ctes_opts", InternalTraversal.dp_plain_obj),
+    ]
+
+    _independent_ctes: Tuple[CTE, ...] = ()
+    _independent_ctes_opts: Tuple[_CTEOpts, ...] = ()
+
+    @_generative
+    def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self:
+        r"""Add one or more :class:`_sql.CTE` constructs to this statement.
+
+        This method will associate the given :class:`_sql.CTE` constructs with
+        the parent statement such that they will each be unconditionally
+        rendered in the WITH clause of the final statement, even if not
+        referenced elsewhere within the statement or any sub-selects.
+
+        The optional :paramref:`.HasCTE.add_cte.nest_here` parameter when set
+        to True will have the effect that each given :class:`_sql.CTE` will
+        render in a WITH clause rendered directly along with this statement,
+        rather than being moved to the top of the ultimate rendered statement,
+        even if this statement is rendered as a subquery within a larger
+        statement.
+
+        This method has two general uses. One is to embed CTE statements that
+        serve some purpose without being referenced explicitly, such as the use
+        case of embedding a DML statement such as an INSERT or UPDATE as a CTE
+        inline with a primary statement that may draw from its results
+        indirectly.  The other is to provide control over the exact placement
+        of a particular series of CTE constructs that should remain rendered
+        directly in terms of a particular statement that may be nested in a
+        larger statement.
+
+        E.g.::
+
+            from sqlalchemy import table, column, select
+
+            t = table("t", column("c1"), column("c2"))
+
+            ins = t.insert().values({"c1": "x", "c2": "y"}).cte()
+
+            stmt = select(t).add_cte(ins)
+
+        Would render:
+
+        .. sourcecode:: sql
+
+            WITH anon_1 AS (
+                INSERT INTO t (c1, c2) VALUES (:param_1, :param_2)
+            )
+            SELECT t.c1, t.c2
+            FROM t
+
+        Above, the "anon_1" CTE is not referenced in the SELECT
+        statement, however still accomplishes the task of running an INSERT
+        statement.
+
+        Similarly in a DML-related context, using the PostgreSQL
+        :class:`_postgresql.Insert` construct to generate an "upsert"::
+
+            from sqlalchemy import table, column
+            from sqlalchemy.dialects.postgresql import insert
+
+            t = table("t", column("c1"), column("c2"))
+
+            delete_statement_cte = t.delete().where(t.c.c1 < 1).cte("deletions")
+
+            insert_stmt = insert(t).values({"c1": 1, "c2": 2})
+            update_statement = insert_stmt.on_conflict_do_update(
+                index_elements=[t.c.c1],
+                set_={
+                    "c1": insert_stmt.excluded.c1,
+                    "c2": insert_stmt.excluded.c2,
+                },
+            ).add_cte(delete_statement_cte)
+
+            print(update_statement)
+
+        The above statement renders as:
+
+        .. sourcecode:: sql
+
+            WITH deletions AS (
+                DELETE FROM t WHERE t.c1 < %(c1_1)s
+            )
+            INSERT INTO t (c1, c2) VALUES (%(c1)s, %(c2)s)
+            ON CONFLICT (c1) DO UPDATE SET c1 = excluded.c1, c2 = excluded.c2
+
+        .. versionadded:: 1.4.21
+
+        :param \*ctes: zero or more :class:`.CTE` constructs.
+
+         .. versionchanged:: 2.0  Multiple CTE instances are accepted
+
+        :param nest_here: if True, the given CTE or CTEs will be rendered
+         as though they specified the :paramref:`.HasCTE.cte.nesting` flag
+         to ``True`` when they were added to this :class:`.HasCTE`.
+         Assuming the given CTEs are not referenced in an outer-enclosing
+         statement as well, the CTEs given should render at the level of
+         this statement when this flag is given.
+
+         .. versionadded:: 2.0
+
+         .. seealso::
+
+            :paramref:`.HasCTE.cte.nesting`
+
+
+        """  # noqa: E501
+        opt = _CTEOpts(nest_here)
+        for cte in ctes:
+            cte = coercions.expect(roles.IsCTERole, cte)
+            self._independent_ctes += (cte,)
+            self._independent_ctes_opts += (opt,)
+        return self
+
+    def cte(
+        self,
+        name: Optional[str] = None,
+        recursive: bool = False,
+        nesting: bool = False,
+    ) -> CTE:
+        r"""Return a new :class:`_expression.CTE`,
+        or Common Table Expression instance.
+
+        Common table expressions are a SQL standard whereby SELECT
+        statements can draw upon secondary statements specified along
+        with the primary statement, using a clause called "WITH".
+        Special semantics regarding UNION can also be employed to
+        allow "recursive" queries, where a SELECT statement can draw
+        upon the set of rows that have previously been selected.
+
+        CTEs can also be applied to DML constructs UPDATE, INSERT
+        and DELETE on some databases, both as a source of CTE rows
+        when combined with RETURNING, as well as a consumer of
+        CTE rows.
+
+        SQLAlchemy detects :class:`_expression.CTE` objects, which are treated
+        similarly to :class:`_expression.Alias` objects, as special elements
+        to be delivered to the FROM clause of the statement as well
+        as to a WITH clause at the top of the statement.
+
+        For special prefixes such as PostgreSQL "MATERIALIZED" and
+        "NOT MATERIALIZED", the :meth:`_expression.CTE.prefix_with`
+        method may be
+        used to establish these.
+
+        .. versionchanged:: 1.3.13 Added support for prefixes.
+           In particular - MATERIALIZED and NOT MATERIALIZED.
+
+        :param name: name given to the common table expression.  Like
+         :meth:`_expression.FromClause.alias`, the name can be left as
+         ``None`` in which case an anonymous symbol will be used at query
+         compile time.
+        :param recursive: if ``True``, will render ``WITH RECURSIVE``.
+         A recursive common table expression is intended to be used in
+         conjunction with UNION ALL in order to derive rows
+         from those already selected.
+        :param nesting: if ``True``, will render the CTE locally to the
+         statement in which it is referenced.   For more complex scenarios,
+         the :meth:`.HasCTE.add_cte` method using the
+         :paramref:`.HasCTE.add_cte.nest_here`
+         parameter may also be used to more carefully
+         control the exact placement of a particular CTE.
+
+         .. versionadded:: 1.4.24
+
+         .. seealso::
+
+            :meth:`.HasCTE.add_cte`
+
+        The following examples include two from PostgreSQL's documentation at
+        https://www.postgresql.org/docs/current/static/queries-with.html,
+        as well as additional examples.
+
+        Example 1, non recursive::
+
+            from sqlalchemy import (
+                Table,
+                Column,
+                String,
+                Integer,
+                MetaData,
+                select,
+                func,
+            )
+
+            metadata = MetaData()
+
+            orders = Table(
+                "orders",
+                metadata,
+                Column("region", String),
+                Column("amount", Integer),
+                Column("product", String),
+                Column("quantity", Integer),
+            )
+
+            regional_sales = (
+                select(orders.c.region, func.sum(orders.c.amount).label("total_sales"))
+                .group_by(orders.c.region)
+                .cte("regional_sales")
+            )
+
+
+            top_regions = (
+                select(regional_sales.c.region)
+                .where(
+                    regional_sales.c.total_sales
+                    > select(func.sum(regional_sales.c.total_sales) / 10)
+                )
+                .cte("top_regions")
+            )
+
+            statement = (
+                select(
+                    orders.c.region,
+                    orders.c.product,
+                    func.sum(orders.c.quantity).label("product_units"),
+                    func.sum(orders.c.amount).label("product_sales"),
+                )
+                .where(orders.c.region.in_(select(top_regions.c.region)))
+                .group_by(orders.c.region, orders.c.product)
+            )
+
+            result = conn.execute(statement).fetchall()
+
+        Example 2, WITH RECURSIVE::
+
+            from sqlalchemy import (
+                Table,
+                Column,
+                String,
+                Integer,
+                MetaData,
+                select,
+                func,
+            )
+
+            metadata = MetaData()
+
+            parts = Table(
+                "parts",
+                metadata,
+                Column("part", String),
+                Column("sub_part", String),
+                Column("quantity", Integer),
+            )
+
+            included_parts = (
+                select(parts.c.sub_part, parts.c.part, parts.c.quantity)
+                .where(parts.c.part == "our part")
+                .cte(recursive=True)
+            )
+
+
+            incl_alias = included_parts.alias()
+            parts_alias = parts.alias()
+            included_parts = included_parts.union_all(
+                select(
+                    parts_alias.c.sub_part, parts_alias.c.part, parts_alias.c.quantity
+                ).where(parts_alias.c.part == incl_alias.c.sub_part)
+            )
+
+            statement = select(
+                included_parts.c.sub_part,
+                func.sum(included_parts.c.quantity).label("total_quantity"),
+            ).group_by(included_parts.c.sub_part)
+
+            result = conn.execute(statement).fetchall()
+
+        Example 3, an upsert using UPDATE and INSERT with CTEs::
+
+            from datetime import date
+            from sqlalchemy import (
+                MetaData,
+                Table,
+                Column,
+                Integer,
+                Date,
+                select,
+                literal,
+                and_,
+                exists,
+            )
+
+            metadata = MetaData()
+
+            visitors = Table(
+                "visitors",
+                metadata,
+                Column("product_id", Integer, primary_key=True),
+                Column("date", Date, primary_key=True),
+                Column("count", Integer),
+            )
+
+            # add 5 visitors for the product_id == 1
+            product_id = 1
+            day = date.today()
+            count = 5
+
+            update_cte = (
+                visitors.update()
+                .where(
+                    and_(visitors.c.product_id == product_id, visitors.c.date == day)
+                )
+                .values(count=visitors.c.count + count)
+                .returning(literal(1))
+                .cte("update_cte")
+            )
+
+            upsert = visitors.insert().from_select(
+                [visitors.c.product_id, visitors.c.date, visitors.c.count],
+                select(literal(product_id), literal(day), literal(count)).where(
+                    ~exists(update_cte.select())
+                ),
+            )
+
+            connection.execute(upsert)
+
+        Example 4, Nesting CTE (SQLAlchemy 1.4.24 and above)::
+
+            value_a = select(literal("root").label("n")).cte("value_a")
+
+            # A nested CTE with the same name as the root one
+            value_a_nested = select(literal("nesting").label("n")).cte(
+                "value_a", nesting=True
+            )
+
+            # Nesting CTEs takes ascendency locally
+            # over the CTEs at a higher level
+            value_b = select(value_a_nested.c.n).cte("value_b")
+
+            value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
+
+        The above query will render the second CTE nested inside the first,
+        shown with inline parameters below as:
+
+        .. sourcecode:: sql
+
+            WITH
+                value_a AS
+                    (SELECT 'root' AS n),
+                value_b AS
+                    (WITH value_a AS
+                        (SELECT 'nesting' AS n)
+                    SELECT value_a.n AS n FROM value_a)
+            SELECT value_a.n AS a, value_b.n AS b
+            FROM value_a, value_b
+
+        The same CTE can be set up using the :meth:`.HasCTE.add_cte` method
+        as follows (SQLAlchemy 2.0 and above)::
+
+            value_a = select(literal("root").label("n")).cte("value_a")
+
+            # A nested CTE with the same name as the root one
+            value_a_nested = select(literal("nesting").label("n")).cte("value_a")
+
+            # Nesting CTEs takes ascendency locally
+            # over the CTEs at a higher level
+            value_b = (
+                select(value_a_nested.c.n)
+                .add_cte(value_a_nested, nest_here=True)
+                .cte("value_b")
+            )
+
+            value_ab = select(value_a.c.n.label("a"), value_b.c.n.label("b"))
+
+        Example 5, Non-Linear CTE (SQLAlchemy 1.4.28 and above)::
+
+            edge = Table(
+                "edge",
+                metadata,
+                Column("id", Integer, primary_key=True),
+                Column("left", Integer),
+                Column("right", Integer),
+            )
+
+            root_node = select(literal(1).label("node")).cte("nodes", recursive=True)
+
+            left_edge = select(edge.c.left).join(
+                root_node, edge.c.right == root_node.c.node
+            )
+            right_edge = select(edge.c.right).join(
+                root_node, edge.c.left == root_node.c.node
+            )
+
+            subgraph_cte = root_node.union(left_edge, right_edge)
+
+            subgraph = select(subgraph_cte)
+
+        The above query will render 2 UNIONs inside the recursive CTE:
+
+        .. sourcecode:: sql
+
+            WITH RECURSIVE nodes(node) AS (
+                    SELECT 1 AS node
+                UNION
+                    SELECT edge."left" AS "left"
+                    FROM edge JOIN nodes ON edge."right" = nodes.node
+                UNION
+                    SELECT edge."right" AS "right"
+                    FROM edge JOIN nodes ON edge."left" = nodes.node
+            )
+            SELECT nodes.node FROM nodes
+
+        .. seealso::
+
+            :meth:`_orm.Query.cte` - ORM version of
+            :meth:`_expression.HasCTE.cte`.
+
+        """  # noqa: E501
+        return CTE._construct(
+            self, name=name, recursive=recursive, nesting=nesting
+        )
+
+
+class Subquery(AliasedReturnsRows):
+    """Represent a subquery of a SELECT.
+
+    A :class:`.Subquery` is created by invoking the
+    :meth:`_expression.SelectBase.subquery` method, or for convenience the
+    :meth:`_expression.SelectBase.alias` method, on any
+    :class:`_expression.SelectBase` subclass
+    which includes :class:`_expression.Select`,
+    :class:`_expression.CompoundSelect`, and
+    :class:`_expression.TextualSelect`.  As rendered in a FROM clause,
+    it represents the
+    body of the SELECT statement inside of parenthesis, followed by the usual
+    "AS <somename>" that defines all "alias" objects.
+
+    The :class:`.Subquery` object is very similar to the
+    :class:`_expression.Alias`
+    object and can be used in an equivalent way.    The difference between
+    :class:`_expression.Alias` and :class:`.Subquery` is that
+    :class:`_expression.Alias` always
+    contains a :class:`_expression.FromClause` object whereas
+    :class:`.Subquery`
+    always contains a :class:`_expression.SelectBase` object.
+
+    .. versionadded:: 1.4 The :class:`.Subquery` class was added which now
+       serves the purpose of providing an aliased version of a SELECT
+       statement.
+
+    """
+
+    __visit_name__ = "subquery"
+
+    _is_subquery = True
+
+    inherit_cache = True
+
+    element: SelectBase
+
+    @classmethod
+    def _factory(
+        cls, selectable: SelectBase, name: Optional[str] = None
+    ) -> Subquery:
+        """Return a :class:`.Subquery` object."""
+
+        return coercions.expect(
+            roles.SelectStatementRole, selectable
+        ).subquery(name=name)
+
+    @util.deprecated(
+        "1.4",
+        "The :meth:`.Subquery.as_scalar` method, which was previously "
+        "``Alias.as_scalar()`` prior to version 1.4, is deprecated and "
+        "will be removed in a future release; Please use the "
+        ":meth:`_expression.Select.scalar_subquery` method of the "
+        ":func:`_expression.select` "
+        "construct before constructing a subquery object, or with the ORM "
+        "use the :meth:`_query.Query.scalar_subquery` method.",
+    )
+    def as_scalar(self) -> ScalarSelect[Any]:
+        return self.element.set_label_style(LABEL_STYLE_NONE).scalar_subquery()
+
+
+class FromGrouping(GroupedElement, FromClause):
+    """Represent a grouping of a FROM clause"""
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("element", InternalTraversal.dp_clauseelement)
+    ]
+
+    element: FromClause
+
+    def __init__(self, element: FromClause):
+        self.element = coercions.expect(roles.FromClauseRole, element)
+
+    @util.ro_non_memoized_property
+    def columns(
+        self,
+    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        return self.element.columns
+
+    @util.ro_non_memoized_property
+    def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        return self.element.columns
+
+    @property
+    def primary_key(self) -> Iterable[NamedColumn[Any]]:
+        return self.element.primary_key
+
+    @property
+    def foreign_keys(self) -> Iterable[ForeignKey]:
+        return self.element.foreign_keys
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        return self.element.is_derived_from(fromclause)
+
+    def alias(
+        self, name: Optional[str] = None, flat: bool = False
+    ) -> NamedFromGrouping:
+        return NamedFromGrouping(self.element.alias(name=name, flat=flat))
+
+    def _anonymous_fromclause(self, **kw: Any) -> FromGrouping:
+        return FromGrouping(self.element._anonymous_fromclause(**kw))
+
+    @util.ro_non_memoized_property
+    def _hide_froms(self) -> Iterable[FromClause]:
+        return self.element._hide_froms
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        return self.element._from_objects
+
+    def __getstate__(self) -> Dict[str, FromClause]:
+        return {"element": self.element}
+
+    def __setstate__(self, state: Dict[str, FromClause]) -> None:
+        self.element = state["element"]
+
+    if TYPE_CHECKING:
+
+        def self_group(
+            self, against: Optional[OperatorType] = None
+        ) -> Self: ...
+
+
+class NamedFromGrouping(FromGrouping, NamedFromClause):
+    """represent a grouping of a named FROM clause
+
+    .. versionadded:: 2.0
+
+    """
+
+    inherit_cache = True
+
+    if TYPE_CHECKING:
+
+        def self_group(
+            self, against: Optional[OperatorType] = None
+        ) -> Self: ...
+
+
+class TableClause(roles.DMLTableRole, Immutable, NamedFromClause):
+    """Represents a minimal "table" construct.
+
+    This is a lightweight table object that has only a name, a
+    collection of columns, which are typically produced
+    by the :func:`_expression.column` function, and a schema::
+
+        from sqlalchemy import table, column
+
+        user = table(
+            "user",
+            column("id"),
+            column("name"),
+            column("description"),
+        )
+
+    The :class:`_expression.TableClause` construct serves as the base for
+    the more commonly used :class:`_schema.Table` object, providing
+    the usual set of :class:`_expression.FromClause` services including
+    the ``.c.`` collection and statement generation methods.
+
+    It does **not** provide all the additional schema-level services
+    of :class:`_schema.Table`, including constraints, references to other
+    tables, or support for :class:`_schema.MetaData`-level services.
+    It's useful
+    on its own as an ad-hoc construct used to generate quick SQL
+    statements when a more fully fledged :class:`_schema.Table`
+    is not on hand.
+
+    """
+
+    __visit_name__ = "table"
+
+    _traverse_internals: _TraverseInternalsType = [
+        (
+            "columns",
+            InternalTraversal.dp_fromclause_canonical_column_collection,
+        ),
+        ("name", InternalTraversal.dp_string),
+        ("schema", InternalTraversal.dp_string),
+    ]
+
+    _is_table = True
+
+    fullname: str
+
+    implicit_returning = False
+    """:class:`_expression.TableClause`
+    doesn't support having a primary key or column
+    -level defaults, so implicit returning doesn't apply."""
+
+    @util.ro_memoized_property
+    def _autoincrement_column(self) -> Optional[ColumnClause[Any]]:
+        """No PK or default support so no autoincrement column."""
+        return None
+
+    def __init__(self, name: str, *columns: ColumnClause[Any], **kw: Any):
+        super().__init__()
+        self.name = name
+        self._columns = DedupeColumnCollection()
+        self.primary_key = ColumnSet()  # type: ignore
+        self.foreign_keys = set()  # type: ignore
+        for c in columns:
+            self.append_column(c)
+
+        schema = kw.pop("schema", None)
+        if schema is not None:
+            self.schema = schema
+        if self.schema is not None:
+            self.fullname = "%s.%s" % (self.schema, self.name)
+        else:
+            self.fullname = self.name
+        if kw:
+            raise exc.ArgumentError("Unsupported argument(s): %s" % list(kw))
+
+    if TYPE_CHECKING:
+
+        @util.ro_non_memoized_property
+        def columns(
+            self,
+        ) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
+
+        @util.ro_non_memoized_property
+        def c(self) -> ReadOnlyColumnCollection[str, ColumnClause[Any]]: ...
+
+    def __str__(self) -> str:
+        if self.schema is not None:
+            return self.schema + "." + self.name
+        else:
+            return self.name
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        pass
+
+    @util.ro_memoized_property
+    def description(self) -> str:
+        return self.name
+
+    def append_column(self, c: ColumnClause[Any]) -> None:
+        existing = c.table
+        if existing is not None and existing is not self:
+            raise exc.ArgumentError(
+                "column object '%s' already assigned to table '%s'"
+                % (c.key, existing)
+            )
+
+        self._columns.add(c)
+        c.table = self
+
+    @util.preload_module("sqlalchemy.sql.dml")
+    def insert(self) -> util.preloaded.sql_dml.Insert:
+        """Generate an :class:`_sql.Insert` construct against this
+        :class:`_expression.TableClause`.
+
+        E.g.::
+
+            table.insert().values(name="foo")
+
+        See :func:`_expression.insert` for argument and usage information.
+
+        """
+
+        return util.preloaded.sql_dml.Insert(self)
+
+    @util.preload_module("sqlalchemy.sql.dml")
+    def update(self) -> Update:
+        """Generate an :func:`_expression.update` construct against this
+        :class:`_expression.TableClause`.
+
+        E.g.::
+
+            table.update().where(table.c.id == 7).values(name="foo")
+
+        See :func:`_expression.update` for argument and usage information.
+
+        """
+        return util.preloaded.sql_dml.Update(
+            self,
+        )
+
+    @util.preload_module("sqlalchemy.sql.dml")
+    def delete(self) -> Delete:
+        """Generate a :func:`_expression.delete` construct against this
+        :class:`_expression.TableClause`.
+
+        E.g.::
+
+            table.delete().where(table.c.id == 7)
+
+        See :func:`_expression.delete` for argument and usage information.
+
+        """
+        return util.preloaded.sql_dml.Delete(self)
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        return [self]
+
+
+ForUpdateParameter = Union["ForUpdateArg", None, bool, Dict[str, Any]]
+
+
+class ForUpdateArg(ClauseElement):
+    _traverse_internals: _TraverseInternalsType = [
+        ("of", InternalTraversal.dp_clauseelement_list),
+        ("nowait", InternalTraversal.dp_boolean),
+        ("read", InternalTraversal.dp_boolean),
+        ("skip_locked", InternalTraversal.dp_boolean),
+        ("key_share", InternalTraversal.dp_boolean),
+    ]
+
+    of: Optional[Sequence[ClauseElement]]
+    nowait: bool
+    read: bool
+    skip_locked: bool
+
+    @classmethod
+    def _from_argument(
+        cls, with_for_update: ForUpdateParameter
+    ) -> Optional[ForUpdateArg]:
+        if isinstance(with_for_update, ForUpdateArg):
+            return with_for_update
+        elif with_for_update in (None, False):
+            return None
+        elif with_for_update is True:
+            return ForUpdateArg()
+        else:
+            return ForUpdateArg(**cast("Dict[str, Any]", with_for_update))
+
+    def __eq__(self, other: Any) -> bool:
+        return (
+            isinstance(other, ForUpdateArg)
+            and other.nowait == self.nowait
+            and other.read == self.read
+            and other.skip_locked == self.skip_locked
+            and other.key_share == self.key_share
+            and other.of is self.of
+        )
+
+    def __ne__(self, other: Any) -> bool:
+        return not self.__eq__(other)
+
+    def __hash__(self) -> int:
+        return id(self)
+
+    def __init__(
+        self,
+        *,
+        nowait: bool = False,
+        read: bool = False,
+        of: Optional[_ForUpdateOfArgument] = None,
+        skip_locked: bool = False,
+        key_share: bool = False,
+    ):
+        """Represents arguments specified to
+        :meth:`_expression.Select.for_update`.
+
+        """
+
+        self.nowait = nowait
+        self.read = read
+        self.skip_locked = skip_locked
+        self.key_share = key_share
+        if of is not None:
+            self.of = [
+                coercions.expect(roles.ColumnsClauseRole, elem)
+                for elem in util.to_list(of)
+            ]
+        else:
+            self.of = None
+
+
+class Values(roles.InElementRole, Generative, LateralFromClause):
+    """Represent a ``VALUES`` construct that can be used as a FROM element
+    in a statement.
+
+    The :class:`_expression.Values` object is created from the
+    :func:`_expression.values` function.
+
+    .. versionadded:: 1.4
+
+    """
+
+    __visit_name__ = "values"
+
+    _data: Tuple[Sequence[Tuple[Any, ...]], ...] = ()
+
+    _unnamed: bool
+    _traverse_internals: _TraverseInternalsType = [
+        ("_column_args", InternalTraversal.dp_clauseelement_list),
+        ("_data", InternalTraversal.dp_dml_multi_values),
+        ("name", InternalTraversal.dp_string),
+        ("literal_binds", InternalTraversal.dp_boolean),
+    ]
+
+    def __init__(
+        self,
+        *columns: ColumnClause[Any],
+        name: Optional[str] = None,
+        literal_binds: bool = False,
+    ):
+        super().__init__()
+        self._column_args = columns
+
+        if name is None:
+            self._unnamed = True
+            self.name = _anonymous_label.safe_construct(id(self), "anon")
+        else:
+            self._unnamed = False
+            self.name = name
+        self.literal_binds = literal_binds
+        self.named_with_column = not self._unnamed
+
+    @property
+    def _column_types(self) -> List[TypeEngine[Any]]:
+        return [col.type for col in self._column_args]
+
+    @_generative
+    def alias(self, name: Optional[str] = None, flat: bool = False) -> Self:
+        """Return a new :class:`_expression.Values`
+        construct that is a copy of this
+        one with the given name.
+
+        This method is a VALUES-specific specialization of the
+        :meth:`_expression.FromClause.alias` method.
+
+        .. seealso::
+
+            :ref:`tutorial_using_aliases`
+
+            :func:`_expression.alias`
+
+        """
+        non_none_name: str
+
+        if name is None:
+            non_none_name = _anonymous_label.safe_construct(id(self), "anon")
+        else:
+            non_none_name = name
+
+        self.name = non_none_name
+        self.named_with_column = True
+        self._unnamed = False
+        return self
+
+    @_generative
+    def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+        """Return a new :class:`_expression.Values` with the lateral flag set,
+        so that
+        it renders as LATERAL.
+
+        .. seealso::
+
+            :func:`_expression.lateral`
+
+        """
+        non_none_name: str
+
+        if name is None:
+            non_none_name = self.name
+        else:
+            non_none_name = name
+
+        self._is_lateral = True
+        self.name = non_none_name
+        self._unnamed = False
+        return self
+
+    @_generative
+    def data(self, values: Sequence[Tuple[Any, ...]]) -> Self:
+        """Return a new :class:`_expression.Values` construct,
+        adding the given data to the data list.
+
+        E.g.::
+
+            my_values = my_values.data([(1, "value 1"), (2, "value2")])
+
+        :param values: a sequence (i.e. list) of tuples that map to the
+         column expressions given in the :class:`_expression.Values`
+         constructor.
+
+        """
+
+        self._data += (values,)
+        return self
+
+    def scalar_values(self) -> ScalarValues:
+        """Returns a scalar ``VALUES`` construct that can be used as a
+        COLUMN element in a statement.
+
+        .. versionadded:: 2.0.0b4
+
+        """
+        return ScalarValues(self._column_args, self._data, self.literal_binds)
+
+    def _populate_column_collection(
+        self,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+    ) -> None:
+        for c in self._column_args:
+            if c.table is not None and c.table is not self:
+                _, c = c._make_proxy(
+                    self, primary_key=primary_key, foreign_keys=foreign_keys
+                )
+            else:
+                # if the column was used in other contexts, ensure
+                # no memoizations of other FROM clauses.
+                # see test_values.py -> test_auto_proxy_select_direct_col
+                c._reset_memoizations()
+            columns.add(c)
+            c.table = self
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        return [self]
+
+
+class ScalarValues(roles.InElementRole, GroupedElement, ColumnElement[Any]):
+    """Represent a scalar ``VALUES`` construct that can be used as a
+    COLUMN element in a statement.
+
+    The :class:`_expression.ScalarValues` object is created from the
+    :meth:`_expression.Values.scalar_values` method. It's also
+    automatically generated when a :class:`_expression.Values` is used in
+    an ``IN`` or ``NOT IN`` condition.
+
+    .. versionadded:: 2.0.0b4
+
+    """
+
+    __visit_name__ = "scalar_values"
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("_column_args", InternalTraversal.dp_clauseelement_list),
+        ("_data", InternalTraversal.dp_dml_multi_values),
+        ("literal_binds", InternalTraversal.dp_boolean),
+    ]
+
+    def __init__(
+        self,
+        columns: Sequence[ColumnClause[Any]],
+        data: Tuple[Sequence[Tuple[Any, ...]], ...],
+        literal_binds: bool,
+    ):
+        super().__init__()
+        self._column_args = columns
+        self._data = data
+        self.literal_binds = literal_binds
+
+    @property
+    def _column_types(self) -> List[TypeEngine[Any]]:
+        return [col.type for col in self._column_args]
+
+    def __clause_element__(self) -> ScalarValues:
+        return self
+
+    if TYPE_CHECKING:
+
+        def self_group(
+            self, against: Optional[OperatorType] = None
+        ) -> Self: ...
+
+
+class SelectBase(
+    roles.SelectStatementRole,
+    roles.DMLSelectRole,
+    roles.CompoundElementRole,
+    roles.InElementRole,
+    HasCTE,
+    SupportsCloneAnnotations,
+    Selectable,
+):
+    """Base class for SELECT statements.
+
+
+    This includes :class:`_expression.Select`,
+    :class:`_expression.CompoundSelect` and
+    :class:`_expression.TextualSelect`.
+
+
+    """
+
+    _is_select_base = True
+    is_select = True
+
+    _label_style: SelectLabelStyle = LABEL_STYLE_NONE
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        self._reset_memoizations()
+
+    @util.ro_non_memoized_property
+    def selected_columns(
+        self,
+    ) -> ColumnCollection[str, ColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        representing the columns that
+        this SELECT statement or similar construct returns in its result set.
+
+        This collection differs from the :attr:`_expression.FromClause.columns`
+        collection of a :class:`_expression.FromClause` in that the columns
+        within this collection cannot be directly nested inside another SELECT
+        statement; a subquery must be applied first which provides for the
+        necessary parenthesization required by SQL.
+
+        .. note::
+
+            The :attr:`_sql.SelectBase.selected_columns` collection does not
+            include expressions established in the columns clause using the
+            :func:`_sql.text` construct; these are silently omitted from the
+            collection. To use plain textual column expressions inside of a
+            :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+            construct.
+
+        .. seealso::
+
+            :attr:`_sql.Select.selected_columns`
+
+        .. versionadded:: 1.4
+
+        """
+        raise NotImplementedError()
+
+    def _generate_fromclause_column_proxies(
+        self,
+        subquery: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+        *,
+        proxy_compound_columns: Optional[
+            Iterable[Sequence[ColumnElement[Any]]]
+        ] = None,
+    ) -> None:
+        raise NotImplementedError()
+
+    @util.ro_non_memoized_property
+    def _all_selected_columns(self) -> _SelectIterable:
+        """A sequence of expressions that correspond to what is rendered
+        in the columns clause, including :class:`_sql.TextClause`
+        constructs.
+
+        .. versionadded:: 1.4.12
+
+        .. seealso::
+
+            :attr:`_sql.SelectBase.exported_columns`
+
+        """
+        raise NotImplementedError()
+
+    @property
+    def exported_columns(
+        self,
+    ) -> ReadOnlyColumnCollection[str, ColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        that represents the "exported"
+        columns of this :class:`_expression.Selectable`, not including
+        :class:`_sql.TextClause` constructs.
+
+        The "exported" columns for a :class:`_expression.SelectBase`
+        object are synonymous
+        with the :attr:`_expression.SelectBase.selected_columns` collection.
+
+        .. versionadded:: 1.4
+
+        .. seealso::
+
+            :attr:`_expression.Select.exported_columns`
+
+            :attr:`_expression.Selectable.exported_columns`
+
+            :attr:`_expression.FromClause.exported_columns`
+
+
+        """
+        return self.selected_columns.as_readonly()
+
+    @property
+    @util.deprecated(
+        "1.4",
+        "The :attr:`_expression.SelectBase.c` and "
+        ":attr:`_expression.SelectBase.columns` attributes "
+        "are deprecated and will be removed in a future release; these "
+        "attributes implicitly create a subquery that should be explicit.  "
+        "Please call :meth:`_expression.SelectBase.subquery` "
+        "first in order to create "
+        "a subquery, which then contains this attribute.  To access the "
+        "columns that this SELECT object SELECTs "
+        "from, use the :attr:`_expression.SelectBase.selected_columns` "
+        "attribute.",
+    )
+    def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        return self._implicit_subquery.columns
+
+    @property
+    def columns(
+        self,
+    ) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        return self.c
+
+    def get_label_style(self) -> SelectLabelStyle:
+        """
+        Retrieve the current label style.
+
+        Implemented by subclasses.
+
+        """
+        raise NotImplementedError()
+
+    def set_label_style(self, style: SelectLabelStyle) -> Self:
+        """Return a new selectable with the specified label style.
+
+        Implemented by subclasses.
+
+        """
+
+        raise NotImplementedError()
+
+    @util.deprecated(
+        "1.4",
+        "The :meth:`_expression.SelectBase.select` method is deprecated "
+        "and will be removed in a future release; this method implicitly "
+        "creates a subquery that should be explicit.  "
+        "Please call :meth:`_expression.SelectBase.subquery` "
+        "first in order to create "
+        "a subquery, which then can be selected.",
+    )
+    def select(self, *arg: Any, **kw: Any) -> Select[Any]:
+        return self._implicit_subquery.select(*arg, **kw)
+
+    @HasMemoized.memoized_attribute
+    def _implicit_subquery(self) -> Subquery:
+        return self.subquery()
+
+    def _scalar_type(self) -> TypeEngine[Any]:
+        raise NotImplementedError()
+
+    @util.deprecated(
+        "1.4",
+        "The :meth:`_expression.SelectBase.as_scalar` "
+        "method is deprecated and will be "
+        "removed in a future release.  Please refer to "
+        ":meth:`_expression.SelectBase.scalar_subquery`.",
+    )
+    def as_scalar(self) -> ScalarSelect[Any]:
+        return self.scalar_subquery()
+
+    def exists(self) -> Exists:
+        """Return an :class:`_sql.Exists` representation of this selectable,
+        which can be used as a column expression.
+
+        The returned object is an instance of :class:`_sql.Exists`.
+
+        .. seealso::
+
+            :func:`_sql.exists`
+
+            :ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
+
+        .. versionadded:: 1.4
+
+        """
+        return Exists(self)
+
+    def scalar_subquery(self) -> ScalarSelect[Any]:
+        """Return a 'scalar' representation of this selectable, which can be
+        used as a column expression.
+
+        The returned object is an instance of :class:`_sql.ScalarSelect`.
+
+        Typically, a select statement which has only one column in its columns
+        clause is eligible to be used as a scalar expression.  The scalar
+        subquery can then be used in the WHERE clause or columns clause of
+        an enclosing SELECT.
+
+        Note that the scalar subquery differentiates from the FROM-level
+        subquery that can be produced using the
+        :meth:`_expression.SelectBase.subquery`
+        method.
+
+        .. versionchanged: 1.4 - the ``.as_scalar()`` method was renamed to
+           :meth:`_expression.SelectBase.scalar_subquery`.
+
+        .. seealso::
+
+            :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+        """
+        if self._label_style is not LABEL_STYLE_NONE:
+            self = self.set_label_style(LABEL_STYLE_NONE)
+
+        return ScalarSelect(self)
+
+    def label(self, name: Optional[str]) -> Label[Any]:
+        """Return a 'scalar' representation of this selectable, embedded as a
+        subquery with a label.
+
+        .. seealso::
+
+            :meth:`_expression.SelectBase.scalar_subquery`.
+
+        """
+        return self.scalar_subquery().label(name)
+
+    def lateral(self, name: Optional[str] = None) -> LateralFromClause:
+        """Return a LATERAL alias of this :class:`_expression.Selectable`.
+
+        The return value is the :class:`_expression.Lateral` construct also
+        provided by the top-level :func:`_expression.lateral` function.
+
+        .. seealso::
+
+            :ref:`tutorial_lateral_correlation` -  overview of usage.
+
+        """
+        return Lateral._factory(self, name)
+
+    def subquery(self, name: Optional[str] = None) -> Subquery:
+        """Return a subquery of this :class:`_expression.SelectBase`.
+
+        A subquery is from a SQL perspective a parenthesized, named
+        construct that can be placed in the FROM clause of another
+        SELECT statement.
+
+        Given a SELECT statement such as::
+
+            stmt = select(table.c.id, table.c.name)
+
+        The above statement might look like:
+
+        .. sourcecode:: sql
+
+            SELECT table.id, table.name FROM table
+
+        The subquery form by itself renders the same way, however when
+        embedded into the FROM clause of another SELECT statement, it becomes
+        a named sub-element::
+
+            subq = stmt.subquery()
+            new_stmt = select(subq)
+
+        The above renders as:
+
+        .. sourcecode:: sql
+
+            SELECT anon_1.id, anon_1.name
+            FROM (SELECT table.id, table.name FROM table) AS anon_1
+
+        Historically, :meth:`_expression.SelectBase.subquery`
+        is equivalent to calling
+        the :meth:`_expression.FromClause.alias`
+        method on a FROM object; however,
+        as a :class:`_expression.SelectBase`
+        object is not directly  FROM object,
+        the :meth:`_expression.SelectBase.subquery`
+        method provides clearer semantics.
+
+        .. versionadded:: 1.4
+
+        """
+
+        return Subquery._construct(
+            self._ensure_disambiguated_names(), name=name
+        )
+
+    def _ensure_disambiguated_names(self) -> Self:
+        """Ensure that the names generated by this selectbase will be
+        disambiguated in some way, if possible.
+
+        """
+
+        raise NotImplementedError()
+
+    def alias(
+        self, name: Optional[str] = None, flat: bool = False
+    ) -> Subquery:
+        """Return a named subquery against this
+        :class:`_expression.SelectBase`.
+
+        For a :class:`_expression.SelectBase` (as opposed to a
+        :class:`_expression.FromClause`),
+        this returns a :class:`.Subquery` object which behaves mostly the
+        same as the :class:`_expression.Alias` object that is used with a
+        :class:`_expression.FromClause`.
+
+        .. versionchanged:: 1.4 The :meth:`_expression.SelectBase.alias`
+           method is now
+           a synonym for the :meth:`_expression.SelectBase.subquery` method.
+
+        """
+        return self.subquery(name=name)
+
+
+_SB = TypeVar("_SB", bound=SelectBase)
+
+
+class SelectStatementGrouping(GroupedElement, SelectBase, Generic[_SB]):
+    """Represent a grouping of a :class:`_expression.SelectBase`.
+
+    This differs from :class:`.Subquery` in that we are still
+    an "inner" SELECT statement, this is strictly for grouping inside of
+    compound selects.
+
+    """
+
+    __visit_name__ = "select_statement_grouping"
+    _traverse_internals: _TraverseInternalsType = [
+        ("element", InternalTraversal.dp_clauseelement)
+    ] + SupportsCloneAnnotations._clone_annotations_traverse_internals
+
+    _is_select_container = True
+
+    element: _SB
+
+    def __init__(self, element: _SB) -> None:
+        self.element = cast(
+            _SB, coercions.expect(roles.SelectStatementRole, element)
+        )
+
+    def _ensure_disambiguated_names(self) -> SelectStatementGrouping[_SB]:
+        new_element = self.element._ensure_disambiguated_names()
+        if new_element is not self.element:
+            return SelectStatementGrouping(new_element)
+        else:
+            return self
+
+    def get_label_style(self) -> SelectLabelStyle:
+        return self.element.get_label_style()
+
+    def set_label_style(
+        self, label_style: SelectLabelStyle
+    ) -> SelectStatementGrouping[_SB]:
+        return SelectStatementGrouping(
+            self.element.set_label_style(label_style)
+        )
+
+    @property
+    def select_statement(self) -> _SB:
+        return self.element
+
+    def self_group(self, against: Optional[OperatorType] = None) -> Self:
+        return self
+
+    if TYPE_CHECKING:
+
+        def _ungroup(self) -> _SB: ...
+
+    # def _generate_columns_plus_names(
+    #    self, anon_for_dupe_key: bool
+    # ) -> List[Tuple[str, str, str, ColumnElement[Any], bool]]:
+    #    return self.element._generate_columns_plus_names(anon_for_dupe_key)
+
+    def _generate_fromclause_column_proxies(
+        self,
+        subquery: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+        *,
+        proxy_compound_columns: Optional[
+            Iterable[Sequence[ColumnElement[Any]]]
+        ] = None,
+    ) -> None:
+        self.element._generate_fromclause_column_proxies(
+            subquery,
+            columns,
+            proxy_compound_columns=proxy_compound_columns,
+            primary_key=primary_key,
+            foreign_keys=foreign_keys,
+        )
+
+    @util.ro_non_memoized_property
+    def _all_selected_columns(self) -> _SelectIterable:
+        return self.element._all_selected_columns
+
+    @util.ro_non_memoized_property
+    def selected_columns(self) -> ColumnCollection[str, ColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        representing the columns that
+        the embedded SELECT statement returns in its result set, not including
+        :class:`_sql.TextClause` constructs.
+
+        .. versionadded:: 1.4
+
+        .. seealso::
+
+            :attr:`_sql.Select.selected_columns`
+
+        """
+        return self.element.selected_columns
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        return self.element._from_objects
+
+    def add_cte(self, *ctes: CTE, nest_here: bool = False) -> Self:
+        # SelectStatementGrouping not generative: has no attribute '_generate'
+        raise NotImplementedError
+
+
+class GenerativeSelect(SelectBase, Generative):
+    """Base class for SELECT statements where additional elements can be
+    added.
+
+    This serves as the base for :class:`_expression.Select` and
+    :class:`_expression.CompoundSelect`
+    where elements such as ORDER BY, GROUP BY can be added and column
+    rendering can be controlled.  Compare to
+    :class:`_expression.TextualSelect`, which,
+    while it subclasses :class:`_expression.SelectBase`
+    and is also a SELECT construct,
+    represents a fixed textual string which cannot be altered at this level,
+    only wrapped as a subquery.
+
+    """
+
+    _order_by_clauses: Tuple[ColumnElement[Any], ...] = ()
+    _group_by_clauses: Tuple[ColumnElement[Any], ...] = ()
+    _limit_clause: Optional[ColumnElement[Any]] = None
+    _offset_clause: Optional[ColumnElement[Any]] = None
+    _fetch_clause: Optional[ColumnElement[Any]] = None
+    _fetch_clause_options: Optional[Dict[str, bool]] = None
+    _for_update_arg: Optional[ForUpdateArg] = None
+
+    def __init__(self, _label_style: SelectLabelStyle = LABEL_STYLE_DEFAULT):
+        self._label_style = _label_style
+
+    @_generative
+    def with_for_update(
+        self,
+        *,
+        nowait: bool = False,
+        read: bool = False,
+        of: Optional[_ForUpdateOfArgument] = None,
+        skip_locked: bool = False,
+        key_share: bool = False,
+    ) -> Self:
+        """Specify a ``FOR UPDATE`` clause for this
+        :class:`_expression.GenerativeSelect`.
+
+        E.g.::
+
+            stmt = select(table).with_for_update(nowait=True)
+
+        On a database like PostgreSQL or Oracle Database, the above would
+        render a statement like:
+
+        .. sourcecode:: sql
+
+            SELECT table.a, table.b FROM table FOR UPDATE NOWAIT
+
+        on other backends, the ``nowait`` option is ignored and instead
+        would produce:
+
+        .. sourcecode:: sql
+
+            SELECT table.a, table.b FROM table FOR UPDATE
+
+        When called with no arguments, the statement will render with
+        the suffix ``FOR UPDATE``.   Additional arguments can then be
+        provided which allow for common database-specific
+        variants.
+
+        :param nowait: boolean; will render ``FOR UPDATE NOWAIT`` on Oracle
+         Database and PostgreSQL dialects.
+
+        :param read: boolean; will render ``LOCK IN SHARE MODE`` on MySQL,
+         ``FOR SHARE`` on PostgreSQL.  On PostgreSQL, when combined with
+         ``nowait``, will render ``FOR SHARE NOWAIT``.
+
+        :param of: SQL expression or list of SQL expression elements,
+         (typically :class:`_schema.Column` objects or a compatible expression,
+         for some backends may also be a table expression) which will render
+         into a ``FOR UPDATE OF`` clause; supported by PostgreSQL, Oracle
+         Database, some MySQL versions and possibly others. May render as a
+         table or as a column depending on backend.
+
+        :param skip_locked: boolean, will render ``FOR UPDATE SKIP LOCKED`` on
+         Oracle Database and PostgreSQL dialects or ``FOR SHARE SKIP LOCKED``
+         if ``read=True`` is also specified.
+
+        :param key_share: boolean, will render ``FOR NO KEY UPDATE``,
+         or if combined with ``read=True`` will render ``FOR KEY SHARE``,
+         on the PostgreSQL dialect.
+
+        """
+        self._for_update_arg = ForUpdateArg(
+            nowait=nowait,
+            read=read,
+            of=of,
+            skip_locked=skip_locked,
+            key_share=key_share,
+        )
+        return self
+
+    def get_label_style(self) -> SelectLabelStyle:
+        """
+        Retrieve the current label style.
+
+        .. versionadded:: 1.4
+
+        """
+        return self._label_style
+
+    def set_label_style(self, style: SelectLabelStyle) -> Self:
+        """Return a new selectable with the specified label style.
+
+        There are three "label styles" available,
+        :attr:`_sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY`,
+        :attr:`_sql.SelectLabelStyle.LABEL_STYLE_TABLENAME_PLUS_COL`, and
+        :attr:`_sql.SelectLabelStyle.LABEL_STYLE_NONE`.   The default style is
+        :attr:`_sql.SelectLabelStyle.LABEL_STYLE_DISAMBIGUATE_ONLY`.
+
+        In modern SQLAlchemy, there is not generally a need to change the
+        labeling style, as per-expression labels are more effectively used by
+        making use of the :meth:`_sql.ColumnElement.label` method. In past
+        versions, :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL` was used to
+        disambiguate same-named columns from different tables, aliases, or
+        subqueries; the newer :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY` now
+        applies labels only to names that conflict with an existing name so
+        that the impact of this labeling is minimal.
+
+        The rationale for disambiguation is mostly so that all column
+        expressions are available from a given :attr:`_sql.FromClause.c`
+        collection when a subquery is created.
+
+        .. versionadded:: 1.4 - the
+            :meth:`_sql.GenerativeSelect.set_label_style` method replaces the
+            previous combination of ``.apply_labels()``, ``.with_labels()`` and
+            ``use_labels=True`` methods and/or parameters.
+
+        .. seealso::
+
+            :data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`
+
+            :data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`
+
+            :data:`_sql.LABEL_STYLE_NONE`
+
+            :data:`_sql.LABEL_STYLE_DEFAULT`
+
+        """
+        if self._label_style is not style:
+            self = self._generate()
+            self._label_style = style
+        return self
+
+    @property
+    def _group_by_clause(self) -> ClauseList:
+        """ClauseList access to group_by_clauses for legacy dialects"""
+        return ClauseList._construct_raw(
+            operators.comma_op, self._group_by_clauses
+        )
+
+    @property
+    def _order_by_clause(self) -> ClauseList:
+        """ClauseList access to order_by_clauses for legacy dialects"""
+        return ClauseList._construct_raw(
+            operators.comma_op, self._order_by_clauses
+        )
+
+    def _offset_or_limit_clause(
+        self,
+        element: _LimitOffsetType,
+        name: Optional[str] = None,
+        type_: Optional[_TypeEngineArgument[int]] = None,
+    ) -> ColumnElement[Any]:
+        """Convert the given value to an "offset or limit" clause.
+
+        This handles incoming integers and converts to an expression; if
+        an expression is already given, it is passed through.
+
+        """
+        return coercions.expect(
+            roles.LimitOffsetRole, element, name=name, type_=type_
+        )
+
+    @overload
+    def _offset_or_limit_clause_asint(
+        self, clause: ColumnElement[Any], attrname: str
+    ) -> NoReturn: ...
+
+    @overload
+    def _offset_or_limit_clause_asint(
+        self, clause: Optional[_OffsetLimitParam], attrname: str
+    ) -> Optional[int]: ...
+
+    def _offset_or_limit_clause_asint(
+        self, clause: Optional[ColumnElement[Any]], attrname: str
+    ) -> Union[NoReturn, Optional[int]]:
+        """Convert the "offset or limit" clause of a select construct to an
+        integer.
+
+        This is only possible if the value is stored as a simple bound
+        parameter. Otherwise, a compilation error is raised.
+
+        """
+        if clause is None:
+            return None
+        try:
+            value = clause._limit_offset_value
+        except AttributeError as err:
+            raise exc.CompileError(
+                "This SELECT structure does not use a simple "
+                "integer value for %s" % attrname
+            ) from err
+        else:
+            return util.asint(value)
+
+    @property
+    def _limit(self) -> Optional[int]:
+        """Get an integer value for the limit.  This should only be used
+        by code that cannot support a limit as a BindParameter or
+        other custom clause as it will throw an exception if the limit
+        isn't currently set to an integer.
+
+        """
+        return self._offset_or_limit_clause_asint(self._limit_clause, "limit")
+
+    def _simple_int_clause(self, clause: ClauseElement) -> bool:
+        """True if the clause is a simple integer, False
+        if it is not present or is a SQL expression.
+        """
+        return isinstance(clause, _OffsetLimitParam)
+
+    @property
+    def _offset(self) -> Optional[int]:
+        """Get an integer value for the offset.  This should only be used
+        by code that cannot support an offset as a BindParameter or
+        other custom clause as it will throw an exception if the
+        offset isn't currently set to an integer.
+
+        """
+        return self._offset_or_limit_clause_asint(
+            self._offset_clause, "offset"
+        )
+
+    @property
+    def _has_row_limiting_clause(self) -> bool:
+        return (
+            self._limit_clause is not None
+            or self._offset_clause is not None
+            or self._fetch_clause is not None
+        )
+
+    @_generative
+    def limit(self, limit: _LimitOffsetType) -> Self:
+        """Return a new selectable with the given LIMIT criterion
+        applied.
+
+        This is a numerical value which usually renders as a ``LIMIT``
+        expression in the resulting select.  Backends that don't
+        support ``LIMIT`` will attempt to provide similar
+        functionality.
+
+        .. note::
+
+           The :meth:`_sql.GenerativeSelect.limit` method will replace
+           any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
+
+        :param limit: an integer LIMIT parameter, or a SQL expression
+         that provides an integer result. Pass ``None`` to reset it.
+
+        .. seealso::
+
+           :meth:`_sql.GenerativeSelect.fetch`
+
+           :meth:`_sql.GenerativeSelect.offset`
+
+        """
+
+        self._fetch_clause = self._fetch_clause_options = None
+        self._limit_clause = self._offset_or_limit_clause(limit)
+        return self
+
+    @_generative
+    def fetch(
+        self,
+        count: _LimitOffsetType,
+        with_ties: bool = False,
+        percent: bool = False,
+    ) -> Self:
+        """Return a new selectable with the given FETCH FIRST criterion
+        applied.
+
+        This is a numeric value which usually renders as ``FETCH {FIRST | NEXT}
+        [ count ] {ROW | ROWS} {ONLY | WITH TIES}`` expression in the resulting
+        select. This functionality is is currently implemented for Oracle
+        Database, PostgreSQL, MSSQL.
+
+        Use :meth:`_sql.GenerativeSelect.offset` to specify the offset.
+
+        .. note::
+
+           The :meth:`_sql.GenerativeSelect.fetch` method will replace
+           any clause applied with :meth:`_sql.GenerativeSelect.limit`.
+
+        .. versionadded:: 1.4
+
+        :param count: an integer COUNT parameter, or a SQL expression
+         that provides an integer result. When ``percent=True`` this will
+         represent the percentage of rows to return, not the absolute value.
+         Pass ``None`` to reset it.
+
+        :param with_ties: When ``True``, the WITH TIES option is used
+         to return any additional rows that tie for the last place in the
+         result set according to the ``ORDER BY`` clause. The
+         ``ORDER BY`` may be mandatory in this case. Defaults to ``False``
+
+        :param percent: When ``True``, ``count`` represents the percentage
+         of the total number of selected rows to return. Defaults to ``False``
+
+        .. seealso::
+
+           :meth:`_sql.GenerativeSelect.limit`
+
+           :meth:`_sql.GenerativeSelect.offset`
+
+        """
+
+        self._limit_clause = None
+        if count is None:
+            self._fetch_clause = self._fetch_clause_options = None
+        else:
+            self._fetch_clause = self._offset_or_limit_clause(count)
+            self._fetch_clause_options = {
+                "with_ties": with_ties,
+                "percent": percent,
+            }
+        return self
+
+    @_generative
+    def offset(self, offset: _LimitOffsetType) -> Self:
+        """Return a new selectable with the given OFFSET criterion
+        applied.
+
+
+        This is a numeric value which usually renders as an ``OFFSET``
+        expression in the resulting select.  Backends that don't
+        support ``OFFSET`` will attempt to provide similar
+        functionality.
+
+        :param offset: an integer OFFSET parameter, or a SQL expression
+         that provides an integer result. Pass ``None`` to reset it.
+
+        .. seealso::
+
+           :meth:`_sql.GenerativeSelect.limit`
+
+           :meth:`_sql.GenerativeSelect.fetch`
+
+        """
+
+        self._offset_clause = self._offset_or_limit_clause(offset)
+        return self
+
+    @_generative
+    @util.preload_module("sqlalchemy.sql.util")
+    def slice(
+        self,
+        start: int,
+        stop: int,
+    ) -> Self:
+        """Apply LIMIT / OFFSET to this statement based on a slice.
+
+        The start and stop indices behave like the argument to Python's
+        built-in :func:`range` function. This method provides an
+        alternative to using ``LIMIT``/``OFFSET`` to get a slice of the
+        query.
+
+        For example, ::
+
+            stmt = select(User).order_by(User.id).slice(1, 3)
+
+        renders as
+
+        .. sourcecode:: sql
+
+           SELECT users.id AS users_id,
+                  users.name AS users_name
+           FROM users ORDER BY users.id
+           LIMIT ? OFFSET ?
+           (2, 1)
+
+        .. note::
+
+           The :meth:`_sql.GenerativeSelect.slice` method will replace
+           any clause applied with :meth:`_sql.GenerativeSelect.fetch`.
+
+        .. versionadded:: 1.4  Added the :meth:`_sql.GenerativeSelect.slice`
+           method generalized from the ORM.
+
+        .. seealso::
+
+           :meth:`_sql.GenerativeSelect.limit`
+
+           :meth:`_sql.GenerativeSelect.offset`
+
+           :meth:`_sql.GenerativeSelect.fetch`
+
+        """
+        sql_util = util.preloaded.sql_util
+        self._fetch_clause = self._fetch_clause_options = None
+        self._limit_clause, self._offset_clause = sql_util._make_slice(
+            self._limit_clause, self._offset_clause, start, stop
+        )
+        return self
+
+    @_generative
+    def order_by(
+        self,
+        __first: Union[
+            Literal[None, _NoArg.NO_ARG],
+            _ColumnExpressionOrStrLabelArgument[Any],
+        ] = _NoArg.NO_ARG,
+        *clauses: _ColumnExpressionOrStrLabelArgument[Any],
+    ) -> Self:
+        r"""Return a new selectable with the given list of ORDER BY
+        criteria applied.
+
+        e.g.::
+
+            stmt = select(table).order_by(table.c.id, table.c.name)
+
+        Calling this method multiple times is equivalent to calling it once
+        with all the clauses concatenated. All existing ORDER BY criteria may
+        be cancelled by passing ``None`` by itself.  New ORDER BY criteria may
+        then be added by invoking :meth:`_orm.Query.order_by` again, e.g.::
+
+            # will erase all ORDER BY and ORDER BY new_col alone
+            stmt = stmt.order_by(None).order_by(new_col)
+
+        :param \*clauses: a series of :class:`_expression.ColumnElement`
+         constructs
+         which will be used to generate an ORDER BY clause.
+
+        .. seealso::
+
+            :ref:`tutorial_order_by` - in the :ref:`unified_tutorial`
+
+            :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
+
+        """
+
+        if not clauses and __first is None:
+            self._order_by_clauses = ()
+        elif __first is not _NoArg.NO_ARG:
+            self._order_by_clauses += tuple(
+                coercions.expect(
+                    roles.OrderByRole, clause, apply_propagate_attrs=self
+                )
+                for clause in (__first,) + clauses
+            )
+        return self
+
+    @_generative
+    def group_by(
+        self,
+        __first: Union[
+            Literal[None, _NoArg.NO_ARG],
+            _ColumnExpressionOrStrLabelArgument[Any],
+        ] = _NoArg.NO_ARG,
+        *clauses: _ColumnExpressionOrStrLabelArgument[Any],
+    ) -> Self:
+        r"""Return a new selectable with the given list of GROUP BY
+        criterion applied.
+
+        All existing GROUP BY settings can be suppressed by passing ``None``.
+
+        e.g.::
+
+            stmt = select(table.c.name, func.max(table.c.stat)).group_by(table.c.name)
+
+        :param \*clauses: a series of :class:`_expression.ColumnElement`
+         constructs
+         which will be used to generate an GROUP BY clause.
+
+        .. seealso::
+
+            :ref:`tutorial_group_by_w_aggregates` - in the
+            :ref:`unified_tutorial`
+
+            :ref:`tutorial_order_by_label` - in the :ref:`unified_tutorial`
+
+        """  # noqa: E501
+
+        if not clauses and __first is None:
+            self._group_by_clauses = ()
+        elif __first is not _NoArg.NO_ARG:
+            self._group_by_clauses += tuple(
+                coercions.expect(
+                    roles.GroupByRole, clause, apply_propagate_attrs=self
+                )
+                for clause in (__first,) + clauses
+            )
+        return self
+
+
+@CompileState.plugin_for("default", "compound_select")
+class CompoundSelectState(CompileState):
+    @util.memoized_property
+    def _label_resolve_dict(
+        self,
+    ) -> Tuple[
+        Dict[str, ColumnElement[Any]],
+        Dict[str, ColumnElement[Any]],
+        Dict[str, ColumnElement[Any]],
+    ]:
+        # TODO: this is hacky and slow
+        hacky_subquery = self.statement.subquery()
+        hacky_subquery.named_with_column = False
+        d = {c.key: c for c in hacky_subquery.c}
+        return d, d, d
+
+
+class _CompoundSelectKeyword(Enum):
+    UNION = "UNION"
+    UNION_ALL = "UNION ALL"
+    EXCEPT = "EXCEPT"
+    EXCEPT_ALL = "EXCEPT ALL"
+    INTERSECT = "INTERSECT"
+    INTERSECT_ALL = "INTERSECT ALL"
+
+
+class CompoundSelect(HasCompileState, GenerativeSelect, TypedReturnsRows[_TP]):
+    """Forms the basis of ``UNION``, ``UNION ALL``, and other
+    SELECT-based set operations.
+
+
+    .. seealso::
+
+        :func:`_expression.union`
+
+        :func:`_expression.union_all`
+
+        :func:`_expression.intersect`
+
+        :func:`_expression.intersect_all`
+
+        :func:`_expression.except`
+
+        :func:`_expression.except_all`
+
+    """
+
+    __visit_name__ = "compound_select"
+
+    _traverse_internals: _TraverseInternalsType = (
+        [
+            ("selects", InternalTraversal.dp_clauseelement_list),
+            ("_limit_clause", InternalTraversal.dp_clauseelement),
+            ("_offset_clause", InternalTraversal.dp_clauseelement),
+            ("_fetch_clause", InternalTraversal.dp_clauseelement),
+            ("_fetch_clause_options", InternalTraversal.dp_plain_dict),
+            ("_order_by_clauses", InternalTraversal.dp_clauseelement_list),
+            ("_group_by_clauses", InternalTraversal.dp_clauseelement_list),
+            ("_for_update_arg", InternalTraversal.dp_clauseelement),
+            ("keyword", InternalTraversal.dp_string),
+        ]
+        + SupportsCloneAnnotations._clone_annotations_traverse_internals
+        + HasCTE._has_ctes_traverse_internals
+    )
+
+    selects: List[SelectBase]
+
+    _is_from_container = True
+    _auto_correlate = False
+
+    def __init__(
+        self,
+        keyword: _CompoundSelectKeyword,
+        *selects: _SelectStatementForCompoundArgument[_TP],
+    ):
+        self.keyword = keyword
+        self.selects = [
+            coercions.expect(
+                roles.CompoundElementRole, s, apply_propagate_attrs=self
+            ).self_group(against=self)
+            for s in selects
+        ]
+
+        GenerativeSelect.__init__(self)
+
+    @classmethod
+    def _create_union(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.UNION, *selects)
+
+    @classmethod
+    def _create_union_all(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.UNION_ALL, *selects)
+
+    @classmethod
+    def _create_except(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.EXCEPT, *selects)
+
+    @classmethod
+    def _create_except_all(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.EXCEPT_ALL, *selects)
+
+    @classmethod
+    def _create_intersect(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.INTERSECT, *selects)
+
+    @classmethod
+    def _create_intersect_all(
+        cls, *selects: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        return CompoundSelect(_CompoundSelectKeyword.INTERSECT_ALL, *selects)
+
+    def _scalar_type(self) -> TypeEngine[Any]:
+        return self.selects[0]._scalar_type()
+
+    def self_group(
+        self, against: Optional[OperatorType] = None
+    ) -> GroupedElement:
+        return SelectStatementGrouping(self)
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        for s in self.selects:
+            if s.is_derived_from(fromclause):
+                return True
+        return False
+
+    def set_label_style(self, style: SelectLabelStyle) -> Self:
+        if self._label_style is not style:
+            self = self._generate()
+            select_0 = self.selects[0].set_label_style(style)
+            self.selects = [select_0] + self.selects[1:]
+
+        return self
+
+    def _ensure_disambiguated_names(self) -> Self:
+        new_select = self.selects[0]._ensure_disambiguated_names()
+        if new_select is not self.selects[0]:
+            self = self._generate()
+            self.selects = [new_select] + self.selects[1:]
+
+        return self
+
+    def _generate_fromclause_column_proxies(
+        self,
+        subquery: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+        *,
+        proxy_compound_columns: Optional[
+            Iterable[Sequence[ColumnElement[Any]]]
+        ] = None,
+    ) -> None:
+        # this is a slightly hacky thing - the union exports a
+        # column that resembles just that of the *first* selectable.
+        # to get at a "composite" column, particularly foreign keys,
+        # you have to dig through the proxies collection which we
+        # generate below.
+        select_0 = self.selects[0]
+
+        if self._label_style is not LABEL_STYLE_DEFAULT:
+            select_0 = select_0.set_label_style(self._label_style)
+
+        # hand-construct the "_proxies" collection to include all
+        # derived columns place a 'weight' annotation corresponding
+        # to how low in the list of select()s the column occurs, so
+        # that the corresponding_column() operation can resolve
+        # conflicts
+        extra_col_iterator = zip(
+            *[
+                [
+                    c._annotate(dd)
+                    for c in stmt._all_selected_columns
+                    if is_column_element(c)
+                ]
+                for dd, stmt in [
+                    ({"weight": i + 1}, stmt)
+                    for i, stmt in enumerate(self.selects)
+                ]
+            ]
+        )
+
+        # the incoming proxy_compound_columns can be present also if this is
+        # a compound embedded in a compound.  it's probably more appropriate
+        # that we generate new weights local to this nested compound, though
+        # i haven't tried to think what it means for compound nested in
+        # compound
+        select_0._generate_fromclause_column_proxies(
+            subquery,
+            columns,
+            proxy_compound_columns=extra_col_iterator,
+            primary_key=primary_key,
+            foreign_keys=foreign_keys,
+        )
+
+    def _refresh_for_new_column(self, column: ColumnElement[Any]) -> None:
+        super()._refresh_for_new_column(column)
+        for select in self.selects:
+            select._refresh_for_new_column(column)
+
+    @util.ro_non_memoized_property
+    def _all_selected_columns(self) -> _SelectIterable:
+        return self.selects[0]._all_selected_columns
+
+    @util.ro_non_memoized_property
+    def selected_columns(
+        self,
+    ) -> ColumnCollection[str, ColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        representing the columns that
+        this SELECT statement or similar construct returns in its result set,
+        not including :class:`_sql.TextClause` constructs.
+
+        For a :class:`_expression.CompoundSelect`, the
+        :attr:`_expression.CompoundSelect.selected_columns`
+        attribute returns the selected
+        columns of the first SELECT statement contained within the series of
+        statements within the set operation.
+
+        .. seealso::
+
+            :attr:`_sql.Select.selected_columns`
+
+        .. versionadded:: 1.4
+
+        """
+        return self.selects[0].selected_columns
+
+
+# backwards compat
+for elem in _CompoundSelectKeyword:
+    setattr(CompoundSelect, elem.name, elem)
+
+
+@CompileState.plugin_for("default", "select")
+class SelectState(util.MemoizedSlots, CompileState):
+    __slots__ = (
+        "from_clauses",
+        "froms",
+        "columns_plus_names",
+        "_label_resolve_dict",
+    )
+
+    if TYPE_CHECKING:
+        default_select_compile_options: CacheableOptions
+    else:
+
+        class default_select_compile_options(CacheableOptions):
+            _cache_key_traversal = []
+
+    if TYPE_CHECKING:
+
+        @classmethod
+        def get_plugin_class(
+            cls, statement: Executable
+        ) -> Type[SelectState]: ...
+
+    def __init__(
+        self,
+        statement: Select[Any],
+        compiler: SQLCompiler,
+        **kw: Any,
+    ):
+        self.statement = statement
+        self.from_clauses = statement._from_obj
+
+        for memoized_entities in statement._memoized_select_entities:
+            self._setup_joins(
+                memoized_entities._setup_joins, memoized_entities._raw_columns
+            )
+
+        if statement._setup_joins:
+            self._setup_joins(statement._setup_joins, statement._raw_columns)
+
+        self.froms = self._get_froms(statement)
+
+        self.columns_plus_names = statement._generate_columns_plus_names(True)
+
+    @classmethod
+    def _plugin_not_implemented(cls) -> NoReturn:
+        raise NotImplementedError(
+            "The default SELECT construct without plugins does not "
+            "implement this method."
+        )
+
+    @classmethod
+    def get_column_descriptions(
+        cls, statement: Select[Any]
+    ) -> List[Dict[str, Any]]:
+        return [
+            {
+                "name": name,
+                "type": element.type,
+                "expr": element,
+            }
+            for _, name, _, element, _ in (
+                statement._generate_columns_plus_names(False)
+            )
+        ]
+
+    @classmethod
+    def from_statement(
+        cls, statement: Select[Any], from_statement: roles.ReturnsRowsRole
+    ) -> ExecutableReturnsRows:
+        cls._plugin_not_implemented()
+
+    @classmethod
+    def get_columns_clause_froms(
+        cls, statement: Select[Any]
+    ) -> List[FromClause]:
+        return cls._normalize_froms(
+            itertools.chain.from_iterable(
+                element._from_objects for element in statement._raw_columns
+            )
+        )
+
+    @classmethod
+    def _column_naming_convention(
+        cls, label_style: SelectLabelStyle
+    ) -> _LabelConventionCallable:
+        table_qualified = label_style is LABEL_STYLE_TABLENAME_PLUS_COL
+
+        dedupe = label_style is not LABEL_STYLE_NONE
+
+        pa = prefix_anon_map()
+        names = set()
+
+        def go(
+            c: Union[ColumnElement[Any], TextClause],
+            col_name: Optional[str] = None,
+        ) -> Optional[str]:
+            if is_text_clause(c):
+                return None
+            elif TYPE_CHECKING:
+                assert is_column_element(c)
+
+            if not dedupe:
+                name = c._proxy_key
+                if name is None:
+                    name = "_no_label"
+                return name
+
+            name = c._tq_key_label if table_qualified else c._proxy_key
+
+            if name is None:
+                name = "_no_label"
+                if name in names:
+                    return c._anon_label(name) % pa
+                else:
+                    names.add(name)
+                    return name
+
+            elif name in names:
+                return (
+                    c._anon_tq_key_label % pa
+                    if table_qualified
+                    else c._anon_key_label % pa
+                )
+            else:
+                names.add(name)
+                return name
+
+        return go
+
+    def _get_froms(self, statement: Select[Any]) -> List[FromClause]:
+        ambiguous_table_name_map: _AmbiguousTableNameMap
+        self._ambiguous_table_name_map = ambiguous_table_name_map = {}
+
+        return self._normalize_froms(
+            itertools.chain(
+                self.from_clauses,
+                itertools.chain.from_iterable(
+                    [
+                        element._from_objects
+                        for element in statement._raw_columns
+                    ]
+                ),
+                itertools.chain.from_iterable(
+                    [
+                        element._from_objects
+                        for element in statement._where_criteria
+                    ]
+                ),
+            ),
+            check_statement=statement,
+            ambiguous_table_name_map=ambiguous_table_name_map,
+        )
+
+    @classmethod
+    def _normalize_froms(
+        cls,
+        iterable_of_froms: Iterable[FromClause],
+        check_statement: Optional[Select[Any]] = None,
+        ambiguous_table_name_map: Optional[_AmbiguousTableNameMap] = None,
+    ) -> List[FromClause]:
+        """given an iterable of things to select FROM, reduce them to what
+        would actually render in the FROM clause of a SELECT.
+
+        This does the job of checking for JOINs, tables, etc. that are in fact
+        overlapping due to cloning, adaption, present in overlapping joins,
+        etc.
+
+        """
+        seen: Set[FromClause] = set()
+        froms: List[FromClause] = []
+
+        for item in iterable_of_froms:
+            if is_subquery(item) and item.element is check_statement:
+                raise exc.InvalidRequestError(
+                    "select() construct refers to itself as a FROM"
+                )
+
+            if not seen.intersection(item._cloned_set):
+                froms.append(item)
+                seen.update(item._cloned_set)
+
+        if froms:
+            toremove = set(
+                itertools.chain.from_iterable(
+                    [_expand_cloned(f._hide_froms) for f in froms]
+                )
+            )
+            if toremove:
+                # filter out to FROM clauses not in the list,
+                # using a list to maintain ordering
+                froms = [f for f in froms if f not in toremove]
+
+            if ambiguous_table_name_map is not None:
+                ambiguous_table_name_map.update(
+                    (
+                        fr.name,
+                        _anonymous_label.safe_construct(
+                            hash(fr.name), fr.name
+                        ),
+                    )
+                    for item in froms
+                    for fr in item._from_objects
+                    if is_table(fr)
+                    and fr.schema
+                    and fr.name not in ambiguous_table_name_map
+                )
+
+        return froms
+
+    def _get_display_froms(
+        self,
+        explicit_correlate_froms: Optional[Sequence[FromClause]] = None,
+        implicit_correlate_froms: Optional[Sequence[FromClause]] = None,
+    ) -> List[FromClause]:
+        """Return the full list of 'from' clauses to be displayed.
+
+        Takes into account a set of existing froms which may be
+        rendered in the FROM clause of enclosing selects; this Select
+        may want to leave those absent if it is automatically
+        correlating.
+
+        """
+
+        froms = self.froms
+
+        if self.statement._correlate:
+            to_correlate = self.statement._correlate
+            if to_correlate:
+                froms = [
+                    f
+                    for f in froms
+                    if f
+                    not in _cloned_intersection(
+                        _cloned_intersection(
+                            froms, explicit_correlate_froms or ()
+                        ),
+                        to_correlate,
+                    )
+                ]
+
+        if self.statement._correlate_except is not None:
+            froms = [
+                f
+                for f in froms
+                if f
+                not in _cloned_difference(
+                    _cloned_intersection(
+                        froms, explicit_correlate_froms or ()
+                    ),
+                    self.statement._correlate_except,
+                )
+            ]
+
+        if (
+            self.statement._auto_correlate
+            and implicit_correlate_froms
+            and len(froms) > 1
+        ):
+            froms = [
+                f
+                for f in froms
+                if f
+                not in _cloned_intersection(froms, implicit_correlate_froms)
+            ]
+
+            if not len(froms):
+                raise exc.InvalidRequestError(
+                    "Select statement '%r"
+                    "' returned no FROM clauses "
+                    "due to auto-correlation; "
+                    "specify correlate(<tables>) "
+                    "to control correlation "
+                    "manually." % self.statement
+                )
+
+        return froms
+
+    def _memoized_attr__label_resolve_dict(
+        self,
+    ) -> Tuple[
+        Dict[str, ColumnElement[Any]],
+        Dict[str, ColumnElement[Any]],
+        Dict[str, ColumnElement[Any]],
+    ]:
+        with_cols: Dict[str, ColumnElement[Any]] = {
+            c._tq_label or c.key: c
+            for c in self.statement._all_selected_columns
+            if c._allow_label_resolve
+        }
+        only_froms: Dict[str, ColumnElement[Any]] = {
+            c.key: c  # type: ignore
+            for c in _select_iterables(self.froms)
+            if c._allow_label_resolve
+        }
+        only_cols: Dict[str, ColumnElement[Any]] = with_cols.copy()
+        for key, value in only_froms.items():
+            with_cols.setdefault(key, value)
+
+        return with_cols, only_froms, only_cols
+
+    @classmethod
+    def determine_last_joined_entity(
+        cls, stmt: Select[Any]
+    ) -> Optional[_JoinTargetElement]:
+        if stmt._setup_joins:
+            return stmt._setup_joins[-1][0]
+        else:
+            return None
+
+    @classmethod
+    def all_selected_columns(cls, statement: Select[Any]) -> _SelectIterable:
+        return [c for c in _select_iterables(statement._raw_columns)]
+
+    def _setup_joins(
+        self,
+        args: Tuple[_SetupJoinsElement, ...],
+        raw_columns: List[_ColumnsClauseElement],
+    ) -> None:
+        for right, onclause, left, flags in args:
+            if TYPE_CHECKING:
+                if onclause is not None:
+                    assert isinstance(onclause, ColumnElement)
+
+            isouter = flags["isouter"]
+            full = flags["full"]
+
+            if left is None:
+                (
+                    left,
+                    replace_from_obj_index,
+                ) = self._join_determine_implicit_left_side(
+                    raw_columns, left, right, onclause
+                )
+            else:
+                (replace_from_obj_index) = self._join_place_explicit_left_side(
+                    left
+                )
+
+            # these assertions can be made here, as if the right/onclause
+            # contained ORM elements, the select() statement would have been
+            # upgraded to an ORM select, and this method would not be called;
+            # orm.context.ORMSelectCompileState._join() would be
+            # used instead.
+            if TYPE_CHECKING:
+                assert isinstance(right, FromClause)
+                if onclause is not None:
+                    assert isinstance(onclause, ColumnElement)
+
+            if replace_from_obj_index is not None:
+                # splice into an existing element in the
+                # self._from_obj list
+                left_clause = self.from_clauses[replace_from_obj_index]
+
+                self.from_clauses = (
+                    self.from_clauses[:replace_from_obj_index]
+                    + (
+                        Join(
+                            left_clause,
+                            right,
+                            onclause,
+                            isouter=isouter,
+                            full=full,
+                        ),
+                    )
+                    + self.from_clauses[replace_from_obj_index + 1 :]
+                )
+            else:
+                assert left is not None
+                self.from_clauses = self.from_clauses + (
+                    Join(left, right, onclause, isouter=isouter, full=full),
+                )
+
+    @util.preload_module("sqlalchemy.sql.util")
+    def _join_determine_implicit_left_side(
+        self,
+        raw_columns: List[_ColumnsClauseElement],
+        left: Optional[FromClause],
+        right: _JoinTargetElement,
+        onclause: Optional[ColumnElement[Any]],
+    ) -> Tuple[Optional[FromClause], Optional[int]]:
+        """When join conditions don't express the left side explicitly,
+        determine if an existing FROM or entity in this query
+        can serve as the left hand side.
+
+        """
+
+        sql_util = util.preloaded.sql_util
+
+        replace_from_obj_index: Optional[int] = None
+
+        from_clauses = self.from_clauses
+
+        if from_clauses:
+            indexes: List[int] = sql_util.find_left_clause_to_join_from(
+                from_clauses, right, onclause
+            )
+
+            if len(indexes) == 1:
+                replace_from_obj_index = indexes[0]
+                left = from_clauses[replace_from_obj_index]
+        else:
+            potential = {}
+            statement = self.statement
+
+            for from_clause in itertools.chain(
+                itertools.chain.from_iterable(
+                    [element._from_objects for element in raw_columns]
+                ),
+                itertools.chain.from_iterable(
+                    [
+                        element._from_objects
+                        for element in statement._where_criteria
+                    ]
+                ),
+            ):
+                potential[from_clause] = ()
+
+            all_clauses = list(potential.keys())
+            indexes = sql_util.find_left_clause_to_join_from(
+                all_clauses, right, onclause
+            )
+
+            if len(indexes) == 1:
+                left = all_clauses[indexes[0]]
+
+        if len(indexes) > 1:
+            raise exc.InvalidRequestError(
+                "Can't determine which FROM clause to join "
+                "from, there are multiple FROMS which can "
+                "join to this entity. Please use the .select_from() "
+                "method to establish an explicit left side, as well as "
+                "providing an explicit ON clause if not present already to "
+                "help resolve the ambiguity."
+            )
+        elif not indexes:
+            raise exc.InvalidRequestError(
+                "Don't know how to join to %r. "
+                "Please use the .select_from() "
+                "method to establish an explicit left side, as well as "
+                "providing an explicit ON clause if not present already to "
+                "help resolve the ambiguity." % (right,)
+            )
+        return left, replace_from_obj_index
+
+    @util.preload_module("sqlalchemy.sql.util")
+    def _join_place_explicit_left_side(
+        self, left: FromClause
+    ) -> Optional[int]:
+        replace_from_obj_index: Optional[int] = None
+
+        sql_util = util.preloaded.sql_util
+
+        from_clauses = list(self.statement._iterate_from_elements())
+
+        if from_clauses:
+            indexes: List[int] = sql_util.find_left_clause_that_matches_given(
+                self.from_clauses, left
+            )
+        else:
+            indexes = []
+
+        if len(indexes) > 1:
+            raise exc.InvalidRequestError(
+                "Can't identify which entity in which to assign the "
+                "left side of this join.   Please use a more specific "
+                "ON clause."
+            )
+
+        # have an index, means the left side is already present in
+        # an existing FROM in the self._from_obj tuple
+        if indexes:
+            replace_from_obj_index = indexes[0]
+
+        # no index, means we need to add a new element to the
+        # self._from_obj tuple
+
+        return replace_from_obj_index
+
+
+class _SelectFromElements:
+    __slots__ = ()
+
+    _raw_columns: List[_ColumnsClauseElement]
+    _where_criteria: Tuple[ColumnElement[Any], ...]
+    _from_obj: Tuple[FromClause, ...]
+
+    def _iterate_from_elements(self) -> Iterator[FromClause]:
+        # note this does not include elements
+        # in _setup_joins
+
+        seen = set()
+        for element in self._raw_columns:
+            for fr in element._from_objects:
+                if fr in seen:
+                    continue
+                seen.add(fr)
+                yield fr
+        for element in self._where_criteria:
+            for fr in element._from_objects:
+                if fr in seen:
+                    continue
+                seen.add(fr)
+                yield fr
+        for element in self._from_obj:
+            if element in seen:
+                continue
+            seen.add(element)
+            yield element
+
+
+class _MemoizedSelectEntities(
+    cache_key.HasCacheKey, traversals.HasCopyInternals, visitors.Traversible
+):
+    """represents partial state from a Select object, for the case
+    where Select.columns() has redefined the set of columns/entities the
+    statement will be SELECTing from.  This object represents
+    the entities from the SELECT before that transformation was applied,
+    so that transformations that were made in terms of the SELECT at that
+    time, such as join() as well as options(), can access the correct context.
+
+    In previous SQLAlchemy versions, this wasn't needed because these
+    constructs calculated everything up front, like when you called join()
+    or options(), it did everything to figure out how that would translate
+    into specific SQL constructs that would be ready to send directly to the
+    SQL compiler when needed.  But as of
+    1.4, all of that stuff is done in the compilation phase, during the
+    "compile state" portion of the process, so that the work can all be
+    cached.  So it needs to be able to resolve joins/options2 based on what
+    the list of entities was when those methods were called.
+
+
+    """
+
+    __visit_name__ = "memoized_select_entities"
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("_raw_columns", InternalTraversal.dp_clauseelement_list),
+        ("_setup_joins", InternalTraversal.dp_setup_join_tuple),
+        ("_with_options", InternalTraversal.dp_executable_options),
+    ]
+
+    _is_clone_of: Optional[ClauseElement]
+    _raw_columns: List[_ColumnsClauseElement]
+    _setup_joins: Tuple[_SetupJoinsElement, ...]
+    _with_options: Tuple[ExecutableOption, ...]
+
+    _annotations = util.EMPTY_DICT
+
+    def _clone(self, **kw: Any) -> Self:
+        c = self.__class__.__new__(self.__class__)
+        c.__dict__ = {k: v for k, v in self.__dict__.items()}
+
+        c._is_clone_of = self.__dict__.get("_is_clone_of", self)
+        return c
+
+    @classmethod
+    def _generate_for_statement(cls, select_stmt: Select[Any]) -> None:
+        if select_stmt._setup_joins or select_stmt._with_options:
+            self = _MemoizedSelectEntities()
+            self._raw_columns = select_stmt._raw_columns
+            self._setup_joins = select_stmt._setup_joins
+            self._with_options = select_stmt._with_options
+
+            select_stmt._memoized_select_entities += (self,)
+            select_stmt._raw_columns = []
+            select_stmt._setup_joins = select_stmt._with_options = ()
+
+
+class Select(
+    HasPrefixes,
+    HasSuffixes,
+    HasHints,
+    HasCompileState,
+    _SelectFromElements,
+    GenerativeSelect,
+    TypedReturnsRows[_TP],
+):
+    """Represents a ``SELECT`` statement.
+
+    The :class:`_sql.Select` object is normally constructed using the
+    :func:`_sql.select` function.  See that function for details.
+
+    .. seealso::
+
+        :func:`_sql.select`
+
+        :ref:`tutorial_selecting_data` - in the 2.0 tutorial
+
+    """
+
+    __visit_name__ = "select"
+
+    _setup_joins: Tuple[_SetupJoinsElement, ...] = ()
+    _memoized_select_entities: Tuple[TODO_Any, ...] = ()
+
+    _raw_columns: List[_ColumnsClauseElement]
+
+    _distinct: bool = False
+    _distinct_on: Tuple[ColumnElement[Any], ...] = ()
+    _correlate: Tuple[FromClause, ...] = ()
+    _correlate_except: Optional[Tuple[FromClause, ...]] = None
+    _where_criteria: Tuple[ColumnElement[Any], ...] = ()
+    _having_criteria: Tuple[ColumnElement[Any], ...] = ()
+    _from_obj: Tuple[FromClause, ...] = ()
+    _auto_correlate = True
+    _is_select_statement = True
+    _compile_options: CacheableOptions = (
+        SelectState.default_select_compile_options
+    )
+
+    _traverse_internals: _TraverseInternalsType = (
+        [
+            ("_raw_columns", InternalTraversal.dp_clauseelement_list),
+            (
+                "_memoized_select_entities",
+                InternalTraversal.dp_memoized_select_entities,
+            ),
+            ("_from_obj", InternalTraversal.dp_clauseelement_list),
+            ("_where_criteria", InternalTraversal.dp_clauseelement_tuple),
+            ("_having_criteria", InternalTraversal.dp_clauseelement_tuple),
+            ("_order_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+            ("_group_by_clauses", InternalTraversal.dp_clauseelement_tuple),
+            ("_setup_joins", InternalTraversal.dp_setup_join_tuple),
+            ("_correlate", InternalTraversal.dp_clauseelement_tuple),
+            ("_correlate_except", InternalTraversal.dp_clauseelement_tuple),
+            ("_limit_clause", InternalTraversal.dp_clauseelement),
+            ("_offset_clause", InternalTraversal.dp_clauseelement),
+            ("_fetch_clause", InternalTraversal.dp_clauseelement),
+            ("_fetch_clause_options", InternalTraversal.dp_plain_dict),
+            ("_for_update_arg", InternalTraversal.dp_clauseelement),
+            ("_distinct", InternalTraversal.dp_boolean),
+            ("_distinct_on", InternalTraversal.dp_clauseelement_tuple),
+            ("_label_style", InternalTraversal.dp_plain_obj),
+        ]
+        + HasCTE._has_ctes_traverse_internals
+        + HasPrefixes._has_prefixes_traverse_internals
+        + HasSuffixes._has_suffixes_traverse_internals
+        + HasHints._has_hints_traverse_internals
+        + SupportsCloneAnnotations._clone_annotations_traverse_internals
+        + Executable._executable_traverse_internals
+    )
+
+    _cache_key_traversal: _CacheKeyTraversalType = _traverse_internals + [
+        ("_compile_options", InternalTraversal.dp_has_cache_key)
+    ]
+
+    _compile_state_factory: Type[SelectState]
+
+    @classmethod
+    def _create_raw_select(cls, **kw: Any) -> Select[Any]:
+        """Create a :class:`.Select` using raw ``__new__`` with no coercions.
+
+        Used internally to build up :class:`.Select` constructs with
+        pre-established state.
+
+        """
+
+        stmt = Select.__new__(Select)
+        stmt.__dict__.update(kw)
+        return stmt
+
+    def __init__(self, *entities: _ColumnsClauseArgument[Any]):
+        r"""Construct a new :class:`_expression.Select`.
+
+        The public constructor for :class:`_expression.Select` is the
+        :func:`_sql.select` function.
+
+        """
+        self._raw_columns = [
+            coercions.expect(
+                roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
+            )
+            for ent in entities
+        ]
+
+        GenerativeSelect.__init__(self)
+
+    def _scalar_type(self) -> TypeEngine[Any]:
+        if not self._raw_columns:
+            return NULLTYPE
+        elem = self._raw_columns[0]
+        cols = list(elem._select_iterable)
+        return cols[0].type
+
+    def filter(self, *criteria: _ColumnExpressionArgument[bool]) -> Self:
+        """A synonym for the :meth:`_sql.Select.where` method."""
+
+        return self.where(*criteria)
+
+    def _filter_by_zero(
+        self,
+    ) -> Union[
+        FromClause, _JoinTargetProtocol, ColumnElement[Any], TextClause
+    ]:
+        if self._setup_joins:
+            meth = SelectState.get_plugin_class(
+                self
+            ).determine_last_joined_entity
+            _last_joined_entity = meth(self)
+            if _last_joined_entity is not None:
+                return _last_joined_entity
+
+        if self._from_obj:
+            return self._from_obj[0]
+
+        return self._raw_columns[0]
+
+    if TYPE_CHECKING:
+
+        @overload
+        def scalar_subquery(
+            self: Select[Tuple[_MAYBE_ENTITY]],
+        ) -> ScalarSelect[Any]: ...
+
+        @overload
+        def scalar_subquery(
+            self: Select[Tuple[_NOT_ENTITY]],
+        ) -> ScalarSelect[_NOT_ENTITY]: ...
+
+        @overload
+        def scalar_subquery(self) -> ScalarSelect[Any]: ...
+
+        def scalar_subquery(self) -> ScalarSelect[Any]: ...
+
+    def filter_by(self, **kwargs: Any) -> Self:
+        r"""apply the given filtering criterion as a WHERE clause
+        to this select.
+
+        """
+        from_entity = self._filter_by_zero()
+
+        clauses = [
+            _entity_namespace_key(from_entity, key) == value
+            for key, value in kwargs.items()
+        ]
+        return self.filter(*clauses)
+
+    @property
+    def column_descriptions(self) -> Any:
+        """Return a :term:`plugin-enabled` 'column descriptions' structure
+        referring to the columns which are SELECTed by this statement.
+
+        This attribute is generally useful when using the ORM, as an
+        extended structure which includes information about mapped
+        entities is returned.  The section :ref:`queryguide_inspection`
+        contains more background.
+
+        For a Core-only statement, the structure returned by this accessor
+        is derived from the same objects that are returned by the
+        :attr:`.Select.selected_columns` accessor, formatted as a list of
+        dictionaries which contain the keys ``name``, ``type`` and ``expr``,
+        which indicate the column expressions to be selected::
+
+            >>> stmt = select(user_table)
+            >>> stmt.column_descriptions
+            [
+                {
+                    'name': 'id',
+                    'type': Integer(),
+                    'expr': Column('id', Integer(), ...)},
+                {
+                    'name': 'name',
+                    'type': String(length=30),
+                    'expr': Column('name', String(length=30), ...)}
+            ]
+
+        .. versionchanged:: 1.4.33 The :attr:`.Select.column_descriptions`
+           attribute returns a structure for a Core-only set of entities,
+           not just ORM-only entities.
+
+        .. seealso::
+
+            :attr:`.UpdateBase.entity_description` - entity information for
+            an :func:`.insert`, :func:`.update`, or :func:`.delete`
+
+            :ref:`queryguide_inspection` - ORM background
+
+        """
+        meth = SelectState.get_plugin_class(self).get_column_descriptions
+        return meth(self)
+
+    def from_statement(
+        self, statement: roles.ReturnsRowsRole
+    ) -> ExecutableReturnsRows:
+        """Apply the columns which this :class:`.Select` would select
+        onto another statement.
+
+        This operation is :term:`plugin-specific` and will raise a not
+        supported exception if this :class:`_sql.Select` does not select from
+        plugin-enabled entities.
+
+
+        The statement is typically either a :func:`_expression.text` or
+        :func:`_expression.select` construct, and should return the set of
+        columns appropriate to the entities represented by this
+        :class:`.Select`.
+
+        .. seealso::
+
+            :ref:`orm_queryguide_selecting_text` - usage examples in the
+            ORM Querying Guide
+
+        """
+        meth = SelectState.get_plugin_class(self).from_statement
+        return meth(self, statement)
+
+    @_generative
+    def join(
+        self,
+        target: _JoinTargetArgument,
+        onclause: Optional[_OnClauseArgument] = None,
+        *,
+        isouter: bool = False,
+        full: bool = False,
+    ) -> Self:
+        r"""Create a SQL JOIN against this :class:`_expression.Select`
+        object's criterion
+        and apply generatively, returning the newly resulting
+        :class:`_expression.Select`.
+
+        E.g.::
+
+            stmt = select(user_table).join(
+                address_table, user_table.c.id == address_table.c.user_id
+            )
+
+        The above statement generates SQL similar to:
+
+        .. sourcecode:: sql
+
+            SELECT user.id, user.name
+            FROM user
+            JOIN address ON user.id = address.user_id
+
+        .. versionchanged:: 1.4 :meth:`_expression.Select.join` now creates
+           a :class:`_sql.Join` object between a :class:`_sql.FromClause`
+           source that is within the FROM clause of the existing SELECT,
+           and a given target :class:`_sql.FromClause`, and then adds
+           this :class:`_sql.Join` to the FROM clause of the newly generated
+           SELECT statement.    This is completely reworked from the behavior
+           in 1.3, which would instead create a subquery of the entire
+           :class:`_expression.Select` and then join that subquery to the
+           target.
+
+           This is a **backwards incompatible change** as the previous behavior
+           was mostly useless, producing an unnamed subquery rejected by
+           most databases in any case.   The new behavior is modeled after
+           that of the very successful :meth:`_orm.Query.join` method in the
+           ORM, in order to support the functionality of :class:`_orm.Query`
+           being available by using a :class:`_sql.Select` object with an
+           :class:`_orm.Session`.
+
+           See the notes for this change at :ref:`change_select_join`.
+
+
+        :param target: target table to join towards
+
+        :param onclause: ON clause of the join.  If omitted, an ON clause
+         is generated automatically based on the :class:`_schema.ForeignKey`
+         linkages between the two tables, if one can be unambiguously
+         determined, otherwise an error is raised.
+
+        :param isouter: if True, generate LEFT OUTER join.  Same as
+         :meth:`_expression.Select.outerjoin`.
+
+        :param full: if True, generate FULL OUTER join.
+
+        .. seealso::
+
+            :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+            :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+            :meth:`_expression.Select.join_from`
+
+            :meth:`_expression.Select.outerjoin`
+
+        """  # noqa: E501
+        join_target = coercions.expect(
+            roles.JoinTargetRole, target, apply_propagate_attrs=self
+        )
+        if onclause is not None:
+            onclause_element = coercions.expect(roles.OnClauseRole, onclause)
+        else:
+            onclause_element = None
+
+        self._setup_joins += (
+            (
+                join_target,
+                onclause_element,
+                None,
+                {"isouter": isouter, "full": full},
+            ),
+        )
+        return self
+
+    def outerjoin_from(
+        self,
+        from_: _FromClauseArgument,
+        target: _JoinTargetArgument,
+        onclause: Optional[_OnClauseArgument] = None,
+        *,
+        full: bool = False,
+    ) -> Self:
+        r"""Create a SQL LEFT OUTER JOIN against this
+        :class:`_expression.Select` object's criterion and apply generatively,
+        returning the newly resulting :class:`_expression.Select`.
+
+        Usage is the same as that of :meth:`_selectable.Select.join_from`.
+
+        """
+        return self.join_from(
+            from_, target, onclause=onclause, isouter=True, full=full
+        )
+
+    @_generative
+    def join_from(
+        self,
+        from_: _FromClauseArgument,
+        target: _JoinTargetArgument,
+        onclause: Optional[_OnClauseArgument] = None,
+        *,
+        isouter: bool = False,
+        full: bool = False,
+    ) -> Self:
+        r"""Create a SQL JOIN against this :class:`_expression.Select`
+        object's criterion
+        and apply generatively, returning the newly resulting
+        :class:`_expression.Select`.
+
+        E.g.::
+
+            stmt = select(user_table, address_table).join_from(
+                user_table, address_table, user_table.c.id == address_table.c.user_id
+            )
+
+        The above statement generates SQL similar to:
+
+        .. sourcecode:: sql
+
+            SELECT user.id, user.name, address.id, address.email, address.user_id
+            FROM user JOIN address ON user.id = address.user_id
+
+        .. versionadded:: 1.4
+
+        :param from\_: the left side of the join, will be rendered in the
+         FROM clause and is roughly equivalent to using the
+         :meth:`.Select.select_from` method.
+
+        :param target: target table to join towards
+
+        :param onclause: ON clause of the join.
+
+        :param isouter: if True, generate LEFT OUTER join.  Same as
+         :meth:`_expression.Select.outerjoin`.
+
+        :param full: if True, generate FULL OUTER join.
+
+        .. seealso::
+
+            :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+            :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+            :meth:`_expression.Select.join`
+
+        """  # noqa: E501
+
+        # note the order of parsing from vs. target is important here, as we
+        # are also deriving the source of the plugin (i.e. the subject mapper
+        # in an ORM query) which should favor the "from_" over the "target"
+
+        from_ = coercions.expect(
+            roles.FromClauseRole, from_, apply_propagate_attrs=self
+        )
+        join_target = coercions.expect(
+            roles.JoinTargetRole, target, apply_propagate_attrs=self
+        )
+        if onclause is not None:
+            onclause_element = coercions.expect(roles.OnClauseRole, onclause)
+        else:
+            onclause_element = None
+
+        self._setup_joins += (
+            (
+                join_target,
+                onclause_element,
+                from_,
+                {"isouter": isouter, "full": full},
+            ),
+        )
+        return self
+
+    def outerjoin(
+        self,
+        target: _JoinTargetArgument,
+        onclause: Optional[_OnClauseArgument] = None,
+        *,
+        full: bool = False,
+    ) -> Self:
+        """Create a left outer join.
+
+        Parameters are the same as that of :meth:`_expression.Select.join`.
+
+        .. versionchanged:: 1.4 :meth:`_expression.Select.outerjoin` now
+           creates a :class:`_sql.Join` object between a
+           :class:`_sql.FromClause` source that is within the FROM clause of
+           the existing SELECT, and a given target :class:`_sql.FromClause`,
+           and then adds this :class:`_sql.Join` to the FROM clause of the
+           newly generated SELECT statement.    This is completely reworked
+           from the behavior in 1.3, which would instead create a subquery of
+           the entire
+           :class:`_expression.Select` and then join that subquery to the
+           target.
+
+           This is a **backwards incompatible change** as the previous behavior
+           was mostly useless, producing an unnamed subquery rejected by
+           most databases in any case.   The new behavior is modeled after
+           that of the very successful :meth:`_orm.Query.join` method in the
+           ORM, in order to support the functionality of :class:`_orm.Query`
+           being available by using a :class:`_sql.Select` object with an
+           :class:`_orm.Session`.
+
+           See the notes for this change at :ref:`change_select_join`.
+
+        .. seealso::
+
+            :ref:`tutorial_select_join` - in the :doc:`/tutorial/index`
+
+            :ref:`orm_queryguide_joins` - in the :ref:`queryguide_toplevel`
+
+            :meth:`_expression.Select.join`
+
+        """
+        return self.join(target, onclause=onclause, isouter=True, full=full)
+
+    def get_final_froms(self) -> Sequence[FromClause]:
+        """Compute the final displayed list of :class:`_expression.FromClause`
+        elements.
+
+        This method will run through the full computation required to
+        determine what FROM elements will be displayed in the resulting
+        SELECT statement, including shadowing individual tables with
+        JOIN objects, as well as full computation for ORM use cases including
+        eager loading clauses.
+
+        For ORM use, this accessor returns the **post compilation**
+        list of FROM objects; this collection will include elements such as
+        eagerly loaded tables and joins.  The objects will **not** be
+        ORM enabled and not work as a replacement for the
+        :meth:`_sql.Select.select_froms` collection; additionally, the
+        method is not well performing for an ORM enabled statement as it
+        will incur the full ORM construction process.
+
+        To retrieve the FROM list that's implied by the "columns" collection
+        passed to the :class:`_sql.Select` originally, use the
+        :attr:`_sql.Select.columns_clause_froms` accessor.
+
+        To select from an alternative set of columns while maintaining the
+        FROM list, use the :meth:`_sql.Select.with_only_columns` method and
+        pass the
+        :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+        parameter.
+
+        .. versionadded:: 1.4.23 - the :meth:`_sql.Select.get_final_froms`
+           method replaces the previous :attr:`_sql.Select.froms` accessor,
+           which is deprecated.
+
+        .. seealso::
+
+            :attr:`_sql.Select.columns_clause_froms`
+
+        """
+        compiler = self._default_compiler()
+
+        return self._compile_state_factory(self, compiler)._get_display_froms()
+
+    @property
+    @util.deprecated(
+        "1.4.23",
+        "The :attr:`_expression.Select.froms` attribute is moved to "
+        "the :meth:`_expression.Select.get_final_froms` method.",
+    )
+    def froms(self) -> Sequence[FromClause]:
+        """Return the displayed list of :class:`_expression.FromClause`
+        elements.
+
+
+        """
+        return self.get_final_froms()
+
+    @property
+    def columns_clause_froms(self) -> List[FromClause]:
+        """Return the set of :class:`_expression.FromClause` objects implied
+        by the columns clause of this SELECT statement.
+
+        .. versionadded:: 1.4.23
+
+        .. seealso::
+
+            :attr:`_sql.Select.froms` - "final" FROM list taking the full
+            statement into account
+
+            :meth:`_sql.Select.with_only_columns` - makes use of this
+            collection to set up a new FROM list
+
+        """
+
+        return SelectState.get_plugin_class(self).get_columns_clause_froms(
+            self
+        )
+
+    @property
+    def inner_columns(self) -> _SelectIterable:
+        """An iterator of all :class:`_expression.ColumnElement`
+        expressions which would
+        be rendered into the columns clause of the resulting SELECT statement.
+
+        This method is legacy as of 1.4 and is superseded by the
+        :attr:`_expression.Select.exported_columns` collection.
+
+        """
+
+        return iter(self._all_selected_columns)
+
+    def is_derived_from(self, fromclause: Optional[FromClause]) -> bool:
+        if fromclause is not None and self in fromclause._cloned_set:
+            return True
+
+        for f in self._iterate_from_elements():
+            if f.is_derived_from(fromclause):
+                return True
+        return False
+
+    def _copy_internals(
+        self, clone: _CloneCallableType = _clone, **kw: Any
+    ) -> None:
+        # Select() object has been cloned and probably adapted by the
+        # given clone function.  Apply the cloning function to internal
+        # objects
+
+        # 1. keep a dictionary of the froms we've cloned, and what
+        # they've become.  This allows us to ensure the same cloned from
+        # is used when other items such as columns are "cloned"
+
+        all_the_froms = set(
+            itertools.chain(
+                _from_objects(*self._raw_columns),
+                _from_objects(*self._where_criteria),
+                _from_objects(*[elem[0] for elem in self._setup_joins]),
+            )
+        )
+
+        # do a clone for the froms we've gathered.  what is important here
+        # is if any of the things we are selecting from, like tables,
+        # were converted into Join objects.   if so, these need to be
+        # added to _from_obj explicitly, because otherwise they won't be
+        # part of the new state, as they don't associate themselves with
+        # their columns.
+        new_froms = {f: clone(f, **kw) for f in all_the_froms}
+
+        # 2. copy FROM collections, adding in joins that we've created.
+        existing_from_obj = [clone(f, **kw) for f in self._from_obj]
+        add_froms = (
+            {f for f in new_froms.values() if isinstance(f, Join)}
+            .difference(all_the_froms)
+            .difference(existing_from_obj)
+        )
+
+        self._from_obj = tuple(existing_from_obj) + tuple(add_froms)
+
+        # 3. clone everything else, making sure we use columns
+        # corresponding to the froms we just made.
+        def replace(
+            obj: Union[BinaryExpression[Any], ColumnClause[Any]],
+            **kw: Any,
+        ) -> Optional[KeyedColumnElement[Any]]:
+            if isinstance(obj, ColumnClause) and obj.table in new_froms:
+                newelem = new_froms[obj.table].corresponding_column(obj)
+                return newelem
+            return None
+
+        kw["replace"] = replace
+
+        # copy everything else.   for table-ish things like correlate,
+        # correlate_except, setup_joins, these clone normally.  For
+        # column-expression oriented things like raw_columns, where_criteria,
+        # order by, we get this from the new froms.
+        super()._copy_internals(clone=clone, omit_attrs=("_from_obj",), **kw)
+
+        self._reset_memoizations()
+
+    def get_children(self, **kw: Any) -> Iterable[ClauseElement]:
+        return itertools.chain(
+            super().get_children(
+                omit_attrs=("_from_obj", "_correlate", "_correlate_except"),
+                **kw,
+            ),
+            self._iterate_from_elements(),
+        )
+
+    @_generative
+    def add_columns(
+        self, *entities: _ColumnsClauseArgument[Any]
+    ) -> Select[Any]:
+        r"""Return a new :func:`_expression.select` construct with
+        the given entities appended to its columns clause.
+
+        E.g.::
+
+            my_select = my_select.add_columns(table.c.new_column)
+
+        The original expressions in the columns clause remain in place.
+        To replace the original expressions with new ones, see the method
+        :meth:`_expression.Select.with_only_columns`.
+
+        :param \*entities: column, table, or other entity expressions to be
+         added to the columns clause
+
+        .. seealso::
+
+            :meth:`_expression.Select.with_only_columns` - replaces existing
+            expressions rather than appending.
+
+            :ref:`orm_queryguide_select_multiple_entities` - ORM-centric
+            example
+
+        """
+        self._reset_memoizations()
+
+        self._raw_columns = self._raw_columns + [
+            coercions.expect(
+                roles.ColumnsClauseRole, column, apply_propagate_attrs=self
+            )
+            for column in entities
+        ]
+        return self
+
+    def _set_entities(
+        self, entities: Iterable[_ColumnsClauseArgument[Any]]
+    ) -> None:
+        self._raw_columns = [
+            coercions.expect(
+                roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
+            )
+            for ent in util.to_list(entities)
+        ]
+
+    @util.deprecated(
+        "1.4",
+        "The :meth:`_expression.Select.column` method is deprecated and will "
+        "be removed in a future release.  Please use "
+        ":meth:`_expression.Select.add_columns`",
+    )
+    def column(self, column: _ColumnsClauseArgument[Any]) -> Select[Any]:
+        """Return a new :func:`_expression.select` construct with
+        the given column expression added to its columns clause.
+
+        E.g.::
+
+            my_select = my_select.column(table.c.new_column)
+
+        See the documentation for
+        :meth:`_expression.Select.with_only_columns`
+        for guidelines on adding /replacing the columns of a
+        :class:`_expression.Select` object.
+
+        """
+        return self.add_columns(column)
+
+    @util.preload_module("sqlalchemy.sql.util")
+    def reduce_columns(self, only_synonyms: bool = True) -> Select[Any]:
+        """Return a new :func:`_expression.select` construct with redundantly
+        named, equivalently-valued columns removed from the columns clause.
+
+        "Redundant" here means two columns where one refers to the
+        other either based on foreign key, or via a simple equality
+        comparison in the WHERE clause of the statement.   The primary purpose
+        of this method is to automatically construct a select statement
+        with all uniquely-named columns, without the need to use
+        table-qualified labels as
+        :meth:`_expression.Select.set_label_style`
+        does.
+
+        When columns are omitted based on foreign key, the referred-to
+        column is the one that's kept.  When columns are omitted based on
+        WHERE equivalence, the first column in the columns clause is the
+        one that's kept.
+
+        :param only_synonyms: when True, limit the removal of columns
+         to those which have the same name as the equivalent.   Otherwise,
+         all columns that are equivalent to another are removed.
+
+        """
+        woc: Select[Any]
+        woc = self.with_only_columns(
+            *util.preloaded.sql_util.reduce_columns(
+                self._all_selected_columns,
+                only_synonyms=only_synonyms,
+                *(self._where_criteria + self._from_obj),
+            )
+        )
+        return woc
+
+    # START OVERLOADED FUNCTIONS self.with_only_columns Select 1-8 ", *, maintain_column_froms: bool =..." # noqa: E501
+
+    # code within this block is **programmatically,
+    # statically generated** by tools/generate_tuple_map_overloads.py
+
+    @overload
+    def with_only_columns(
+        self, __ent0: _TCCA[_T0], *, maintain_column_froms: bool = ...
+    ) -> Select[Tuple[_T0]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        __ent3: _TCCA[_T3],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2, _T3]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        __ent3: _TCCA[_T3],
+        __ent4: _TCCA[_T4],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        __ent3: _TCCA[_T3],
+        __ent4: _TCCA[_T4],
+        __ent5: _TCCA[_T5],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        __ent3: _TCCA[_T3],
+        __ent4: _TCCA[_T4],
+        __ent5: _TCCA[_T5],
+        __ent6: _TCCA[_T6],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6]]: ...
+
+    @overload
+    def with_only_columns(
+        self,
+        __ent0: _TCCA[_T0],
+        __ent1: _TCCA[_T1],
+        __ent2: _TCCA[_T2],
+        __ent3: _TCCA[_T3],
+        __ent4: _TCCA[_T4],
+        __ent5: _TCCA[_T5],
+        __ent6: _TCCA[_T6],
+        __ent7: _TCCA[_T7],
+        *,
+        maintain_column_froms: bool = ...,
+    ) -> Select[Tuple[_T0, _T1, _T2, _T3, _T4, _T5, _T6, _T7]]: ...
+
+    # END OVERLOADED FUNCTIONS self.with_only_columns
+
+    @overload
+    def with_only_columns(
+        self,
+        *entities: _ColumnsClauseArgument[Any],
+        maintain_column_froms: bool = False,
+        **__kw: Any,
+    ) -> Select[Any]: ...
+
+    @_generative
+    def with_only_columns(
+        self,
+        *entities: _ColumnsClauseArgument[Any],
+        maintain_column_froms: bool = False,
+        **__kw: Any,
+    ) -> Select[Any]:
+        r"""Return a new :func:`_expression.select` construct with its columns
+        clause replaced with the given entities.
+
+        By default, this method is exactly equivalent to as if the original
+        :func:`_expression.select` had been called with the given entities.
+        E.g. a statement::
+
+            s = select(table1.c.a, table1.c.b)
+            s = s.with_only_columns(table1.c.b)
+
+        should be exactly equivalent to::
+
+            s = select(table1.c.b)
+
+        In this mode of operation, :meth:`_sql.Select.with_only_columns`
+        will also dynamically alter the FROM clause of the
+        statement if it is not explicitly stated.
+        To maintain the existing set of FROMs including those implied by the
+        current columns clause, add the
+        :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+        parameter::
+
+            s = select(table1.c.a, table2.c.b)
+            s = s.with_only_columns(table1.c.a, maintain_column_froms=True)
+
+        The above parameter performs a transfer of the effective FROMs
+        in the columns collection to the :meth:`_sql.Select.select_from`
+        method, as though the following were invoked::
+
+            s = select(table1.c.a, table2.c.b)
+            s = s.select_from(table1, table2).with_only_columns(table1.c.a)
+
+        The :paramref:`_sql.Select.with_only_columns.maintain_column_froms`
+        parameter makes use of the :attr:`_sql.Select.columns_clause_froms`
+        collection and performs an operation equivalent to the following::
+
+            s = select(table1.c.a, table2.c.b)
+            s = s.select_from(*s.columns_clause_froms).with_only_columns(table1.c.a)
+
+        :param \*entities: column expressions to be used.
+
+        :param maintain_column_froms: boolean parameter that will ensure the
+         FROM list implied from the current columns clause will be transferred
+         to the :meth:`_sql.Select.select_from` method first.
+
+         .. versionadded:: 1.4.23
+
+        """  # noqa: E501
+
+        if __kw:
+            raise _no_kw()
+
+        # memoizations should be cleared here as of
+        # I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
+        # is the case for now.
+        self._assert_no_memoizations()
+
+        if maintain_column_froms:
+            self.select_from.non_generative(  # type: ignore
+                self, *self.columns_clause_froms
+            )
+
+        # then memoize the FROMs etc.
+        _MemoizedSelectEntities._generate_for_statement(self)
+
+        self._raw_columns = [
+            coercions.expect(roles.ColumnsClauseRole, c)
+            for c in coercions._expression_collection_was_a_list(
+                "entities", "Select.with_only_columns", entities
+            )
+        ]
+        return self
+
+    @property
+    def whereclause(self) -> Optional[ColumnElement[Any]]:
+        """Return the completed WHERE clause for this
+        :class:`_expression.Select` statement.
+
+        This assembles the current collection of WHERE criteria
+        into a single :class:`_expression.BooleanClauseList` construct.
+
+
+        .. versionadded:: 1.4
+
+        """
+
+        return BooleanClauseList._construct_for_whereclause(
+            self._where_criteria
+        )
+
+    _whereclause = whereclause
+
+    @_generative
+    def where(self, *whereclause: _ColumnExpressionArgument[bool]) -> Self:
+        """Return a new :func:`_expression.select` construct with
+        the given expression added to
+        its WHERE clause, joined to the existing clause via AND, if any.
+
+        """
+
+        assert isinstance(self._where_criteria, tuple)
+
+        for criterion in whereclause:
+            where_criteria: ColumnElement[Any] = coercions.expect(
+                roles.WhereHavingRole, criterion, apply_propagate_attrs=self
+            )
+            self._where_criteria += (where_criteria,)
+        return self
+
+    @_generative
+    def having(self, *having: _ColumnExpressionArgument[bool]) -> Self:
+        """Return a new :func:`_expression.select` construct with
+        the given expression added to
+        its HAVING clause, joined to the existing clause via AND, if any.
+
+        """
+
+        for criterion in having:
+            having_criteria = coercions.expect(
+                roles.WhereHavingRole, criterion, apply_propagate_attrs=self
+            )
+            self._having_criteria += (having_criteria,)
+        return self
+
+    @_generative
+    def distinct(self, *expr: _ColumnExpressionArgument[Any]) -> Self:
+        r"""Return a new :func:`_expression.select` construct which
+        will apply DISTINCT to the SELECT statement overall.
+
+        E.g.::
+
+            from sqlalchemy import select
+
+            stmt = select(users_table.c.id, users_table.c.name).distinct()
+
+        The above would produce an statement resembling:
+
+        .. sourcecode:: sql
+
+            SELECT DISTINCT user.id, user.name FROM user
+
+        The method also accepts an ``*expr`` parameter which produces the
+        PostgreSQL dialect-specific ``DISTINCT ON`` expression.  Using this
+        parameter on other backends which don't support this syntax will
+        raise an error.
+
+        :param \*expr: optional column expressions.  When present,
+         the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>)``
+         construct.  A deprecation warning and/or :class:`_exc.CompileError`
+         will be raised on other backends.
+
+         .. deprecated:: 1.4 Using \*expr in other dialects is deprecated
+            and will raise :class:`_exc.CompileError` in a future version.
+
+        """
+        if expr:
+            self._distinct = True
+            self._distinct_on = self._distinct_on + tuple(
+                coercions.expect(roles.ByOfRole, e, apply_propagate_attrs=self)
+                for e in expr
+            )
+        else:
+            self._distinct = True
+        return self
+
+    @_generative
+    def select_from(self, *froms: _FromClauseArgument) -> Self:
+        r"""Return a new :func:`_expression.select` construct with the
+        given FROM expression(s)
+        merged into its list of FROM objects.
+
+        E.g.::
+
+            table1 = table("t1", column("a"))
+            table2 = table("t2", column("b"))
+            s = select(table1.c.a).select_from(
+                table1.join(table2, table1.c.a == table2.c.b)
+            )
+
+        The "from" list is a unique set on the identity of each element,
+        so adding an already present :class:`_schema.Table`
+        or other selectable
+        will have no effect.   Passing a :class:`_expression.Join` that refers
+        to an already present :class:`_schema.Table`
+        or other selectable will have
+        the effect of concealing the presence of that selectable as
+        an individual element in the rendered FROM list, instead
+        rendering it into a JOIN clause.
+
+        While the typical purpose of :meth:`_expression.Select.select_from`
+        is to
+        replace the default, derived FROM clause with a join, it can
+        also be called with individual table elements, multiple times
+        if desired, in the case that the FROM clause cannot be fully
+        derived from the columns clause::
+
+            select(func.count("*")).select_from(table1)
+
+        """
+
+        self._from_obj += tuple(
+            coercions.expect(
+                roles.FromClauseRole, fromclause, apply_propagate_attrs=self
+            )
+            for fromclause in froms
+        )
+        return self
+
+    @_generative
+    def correlate(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        r"""Return a new :class:`_expression.Select`
+        which will correlate the given FROM
+        clauses to that of an enclosing :class:`_expression.Select`.
+
+        Calling this method turns off the :class:`_expression.Select` object's
+        default behavior of "auto-correlation".  Normally, FROM elements
+        which appear in a :class:`_expression.Select`
+        that encloses this one via
+        its :term:`WHERE clause`, ORDER BY, HAVING or
+        :term:`columns clause` will be omitted from this
+        :class:`_expression.Select`
+        object's :term:`FROM clause`.
+        Setting an explicit correlation collection using the
+        :meth:`_expression.Select.correlate`
+        method provides a fixed list of FROM objects
+        that can potentially take place in this process.
+
+        When :meth:`_expression.Select.correlate`
+        is used to apply specific FROM clauses
+        for correlation, the FROM elements become candidates for
+        correlation regardless of how deeply nested this
+        :class:`_expression.Select`
+        object is, relative to an enclosing :class:`_expression.Select`
+        which refers to
+        the same FROM object.  This is in contrast to the behavior of
+        "auto-correlation" which only correlates to an immediate enclosing
+        :class:`_expression.Select`.
+        Multi-level correlation ensures that the link
+        between enclosed and enclosing :class:`_expression.Select`
+        is always via
+        at least one WHERE/ORDER BY/HAVING/columns clause in order for
+        correlation to take place.
+
+        If ``None`` is passed, the :class:`_expression.Select`
+        object will correlate
+        none of its FROM entries, and all will render unconditionally
+        in the local FROM clause.
+
+        :param \*fromclauses: one or more :class:`.FromClause` or other
+         FROM-compatible construct such as an ORM mapped entity to become part
+         of the correlate collection; alternatively pass a single value
+         ``None`` to remove all existing correlations.
+
+        .. seealso::
+
+            :meth:`_expression.Select.correlate_except`
+
+            :ref:`tutorial_scalar_subquery`
+
+        """
+
+        # tests failing when we try to change how these
+        # arguments are passed
+
+        self._auto_correlate = False
+        if not fromclauses or fromclauses[0] in {None, False}:
+            if len(fromclauses) > 1:
+                raise exc.ArgumentError(
+                    "additional FROM objects not accepted when "
+                    "passing None/False to correlate()"
+                )
+            self._correlate = ()
+        else:
+            self._correlate = self._correlate + tuple(
+                coercions.expect(roles.FromClauseRole, f) for f in fromclauses
+            )
+        return self
+
+    @_generative
+    def correlate_except(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        r"""Return a new :class:`_expression.Select`
+        which will omit the given FROM
+        clauses from the auto-correlation process.
+
+        Calling :meth:`_expression.Select.correlate_except` turns off the
+        :class:`_expression.Select` object's default behavior of
+        "auto-correlation" for the given FROM elements.  An element
+        specified here will unconditionally appear in the FROM list, while
+        all other FROM elements remain subject to normal auto-correlation
+        behaviors.
+
+        If ``None`` is passed, or no arguments are passed,
+        the :class:`_expression.Select` object will correlate all of its
+        FROM entries.
+
+        :param \*fromclauses: a list of one or more
+         :class:`_expression.FromClause`
+         constructs, or other compatible constructs (i.e. ORM-mapped
+         classes) to become part of the correlate-exception collection.
+
+        .. seealso::
+
+            :meth:`_expression.Select.correlate`
+
+            :ref:`tutorial_scalar_subquery`
+
+        """
+
+        self._auto_correlate = False
+        if not fromclauses or fromclauses[0] in {None, False}:
+            if len(fromclauses) > 1:
+                raise exc.ArgumentError(
+                    "additional FROM objects not accepted when "
+                    "passing None/False to correlate_except()"
+                )
+            self._correlate_except = ()
+        else:
+            self._correlate_except = (self._correlate_except or ()) + tuple(
+                coercions.expect(roles.FromClauseRole, f) for f in fromclauses
+            )
+
+        return self
+
+    @HasMemoized_ro_memoized_attribute
+    def selected_columns(
+        self,
+    ) -> ColumnCollection[str, ColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        representing the columns that
+        this SELECT statement or similar construct returns in its result set,
+        not including :class:`_sql.TextClause` constructs.
+
+        This collection differs from the :attr:`_expression.FromClause.columns`
+        collection of a :class:`_expression.FromClause` in that the columns
+        within this collection cannot be directly nested inside another SELECT
+        statement; a subquery must be applied first which provides for the
+        necessary parenthesization required by SQL.
+
+        For a :func:`_expression.select` construct, the collection here is
+        exactly what would be rendered inside the "SELECT" statement, and the
+        :class:`_expression.ColumnElement` objects are directly present as they
+        were given, e.g.::
+
+            col1 = column("q", Integer)
+            col2 = column("p", Integer)
+            stmt = select(col1, col2)
+
+        Above, ``stmt.selected_columns`` would be a collection that contains
+        the ``col1`` and ``col2`` objects directly. For a statement that is
+        against a :class:`_schema.Table` or other
+        :class:`_expression.FromClause`, the collection will use the
+        :class:`_expression.ColumnElement` objects that are in the
+        :attr:`_expression.FromClause.c` collection of the from element.
+
+        A use case for the :attr:`_sql.Select.selected_columns` collection is
+        to allow the existing columns to be referenced when adding additional
+        criteria, e.g.::
+
+            def filter_on_id(my_select, id):
+                return my_select.where(my_select.selected_columns["id"] == id)
+
+
+            stmt = select(MyModel)
+
+            # adds "WHERE id=:param" to the statement
+            stmt = filter_on_id(stmt, 42)
+
+        .. note::
+
+            The :attr:`_sql.Select.selected_columns` collection does not
+            include expressions established in the columns clause using the
+            :func:`_sql.text` construct; these are silently omitted from the
+            collection. To use plain textual column expressions inside of a
+            :class:`_sql.Select` construct, use the :func:`_sql.literal_column`
+            construct.
+
+
+        .. versionadded:: 1.4
+
+        """
+
+        # compare to SelectState._generate_columns_plus_names, which
+        # generates the actual names used in the SELECT string.  that
+        # method is more complex because it also renders columns that are
+        # fully ambiguous, e.g. same column more than once.
+        conv = cast(
+            "Callable[[Any], str]",
+            SelectState._column_naming_convention(self._label_style),
+        )
+
+        cc: ColumnCollection[str, ColumnElement[Any]] = ColumnCollection(
+            [
+                (conv(c), c)
+                for c in self._all_selected_columns
+                if is_column_element(c)
+            ]
+        )
+        return cc.as_readonly()
+
+    @HasMemoized_ro_memoized_attribute
+    def _all_selected_columns(self) -> _SelectIterable:
+        meth = SelectState.get_plugin_class(self).all_selected_columns
+        return list(meth(self))
+
+    def _ensure_disambiguated_names(self) -> Select[Any]:
+        if self._label_style is LABEL_STYLE_NONE:
+            self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
+        return self
+
+    def _generate_fromclause_column_proxies(
+        self,
+        subquery: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+        *,
+        proxy_compound_columns: Optional[
+            Iterable[Sequence[ColumnElement[Any]]]
+        ] = None,
+    ) -> None:
+        """Generate column proxies to place in the exported ``.c``
+        collection of a subquery."""
+
+        if proxy_compound_columns:
+            extra_col_iterator = proxy_compound_columns
+            prox = [
+                c._make_proxy(
+                    subquery,
+                    key=proxy_key,
+                    name=required_label_name,
+                    name_is_truncatable=True,
+                    compound_select_cols=extra_cols,
+                    primary_key=primary_key,
+                    foreign_keys=foreign_keys,
+                )
+                for (
+                    (
+                        required_label_name,
+                        proxy_key,
+                        fallback_label_name,
+                        c,
+                        repeated,
+                    ),
+                    extra_cols,
+                ) in (
+                    zip(
+                        self._generate_columns_plus_names(False),
+                        extra_col_iterator,
+                    )
+                )
+                if is_column_element(c)
+            ]
+        else:
+            prox = [
+                c._make_proxy(
+                    subquery,
+                    key=proxy_key,
+                    name=required_label_name,
+                    name_is_truncatable=True,
+                    primary_key=primary_key,
+                    foreign_keys=foreign_keys,
+                )
+                for (
+                    required_label_name,
+                    proxy_key,
+                    fallback_label_name,
+                    c,
+                    repeated,
+                ) in (self._generate_columns_plus_names(False))
+                if is_column_element(c)
+            ]
+
+        columns._populate_separate_keys(prox)
+
+    def _needs_parens_for_grouping(self) -> bool:
+        return self._has_row_limiting_clause or bool(
+            self._order_by_clause.clauses
+        )
+
+    def self_group(
+        self, against: Optional[OperatorType] = None
+    ) -> Union[SelectStatementGrouping[Self], Self]:
+        """Return a 'grouping' construct as per the
+        :class:`_expression.ClauseElement` specification.
+
+        This produces an element that can be embedded in an expression. Note
+        that this method is called automatically as needed when constructing
+        expressions and should not require explicit use.
+
+        """
+        if (
+            isinstance(against, CompoundSelect)
+            and not self._needs_parens_for_grouping()
+        ):
+            return self
+        else:
+            return SelectStatementGrouping(self)
+
+    def union(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``UNION`` of this select() construct against
+        the given selectables provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        :param \**kwargs: keyword arguments are forwarded to the constructor
+         for the newly created :class:`_sql.CompoundSelect` object.
+
+        """
+        return CompoundSelect._create_union(self, *other)
+
+    def union_all(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``UNION ALL`` of this select() construct against
+        the given selectables provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        :param \**kwargs: keyword arguments are forwarded to the constructor
+         for the newly created :class:`_sql.CompoundSelect` object.
+
+        """
+        return CompoundSelect._create_union_all(self, *other)
+
+    def except_(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``EXCEPT`` of this select() construct against
+        the given selectable provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        """
+        return CompoundSelect._create_except(self, *other)
+
+    def except_all(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``EXCEPT ALL`` of this select() construct against
+        the given selectables provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        """
+        return CompoundSelect._create_except_all(self, *other)
+
+    def intersect(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``INTERSECT`` of this select() construct against
+        the given selectables provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        :param \**kwargs: keyword arguments are forwarded to the constructor
+         for the newly created :class:`_sql.CompoundSelect` object.
+
+        """
+        return CompoundSelect._create_intersect(self, *other)
+
+    def intersect_all(
+        self, *other: _SelectStatementForCompoundArgument[_TP]
+    ) -> CompoundSelect[_TP]:
+        r"""Return a SQL ``INTERSECT ALL`` of this select() construct
+        against the given selectables provided as positional arguments.
+
+        :param \*other: one or more elements with which to create a
+         UNION.
+
+         .. versionchanged:: 1.4.28
+
+            multiple elements are now accepted.
+
+        :param \**kwargs: keyword arguments are forwarded to the constructor
+         for the newly created :class:`_sql.CompoundSelect` object.
+
+        """
+        return CompoundSelect._create_intersect_all(self, *other)
+
+
+class ScalarSelect(
+    roles.InElementRole, Generative, GroupedElement, ColumnElement[_T]
+):
+    """Represent a scalar subquery.
+
+
+    A :class:`_sql.ScalarSelect` is created by invoking the
+    :meth:`_sql.SelectBase.scalar_subquery` method.   The object
+    then participates in other SQL expressions as a SQL column expression
+    within the :class:`_sql.ColumnElement` hierarchy.
+
+    .. seealso::
+
+        :meth:`_sql.SelectBase.scalar_subquery`
+
+        :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+    """
+
+    _traverse_internals: _TraverseInternalsType = [
+        ("element", InternalTraversal.dp_clauseelement),
+        ("type", InternalTraversal.dp_type),
+    ]
+
+    _from_objects: List[FromClause] = []
+    _is_from_container = True
+    if not TYPE_CHECKING:
+        _is_implicitly_boolean = False
+    inherit_cache = True
+
+    element: SelectBase
+
+    def __init__(self, element: SelectBase) -> None:
+        self.element = element
+        self.type = element._scalar_type()
+        self._propagate_attrs = element._propagate_attrs
+
+    def __getattr__(self, attr: str) -> Any:
+        return getattr(self.element, attr)
+
+    def __getstate__(self) -> Dict[str, Any]:
+        return {"element": self.element, "type": self.type}
+
+    def __setstate__(self, state: Dict[str, Any]) -> None:
+        self.element = state["element"]
+        self.type = state["type"]
+
+    @property
+    def columns(self) -> NoReturn:
+        raise exc.InvalidRequestError(
+            "Scalar Select expression has no "
+            "columns; use this object directly "
+            "within a column-level expression."
+        )
+
+    c = columns
+
+    @_generative
+    def where(self, crit: _ColumnExpressionArgument[bool]) -> Self:
+        """Apply a WHERE clause to the SELECT statement referred to
+        by this :class:`_expression.ScalarSelect`.
+
+        """
+        self.element = cast("Select[Any]", self.element).where(crit)
+        return self
+
+    def self_group(self, against: Optional[OperatorType] = None) -> Self:
+        return self
+
+    if TYPE_CHECKING:
+
+        def _ungroup(self) -> Select[Any]: ...
+
+    @_generative
+    def correlate(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        r"""Return a new :class:`_expression.ScalarSelect`
+        which will correlate the given FROM
+        clauses to that of an enclosing :class:`_expression.Select`.
+
+        This method is mirrored from the :meth:`_sql.Select.correlate` method
+        of the underlying :class:`_sql.Select`.  The method applies the
+        :meth:_sql.Select.correlate` method, then returns a new
+        :class:`_sql.ScalarSelect` against that statement.
+
+        .. versionadded:: 1.4 Previously, the
+           :meth:`_sql.ScalarSelect.correlate`
+           method was only available from :class:`_sql.Select`.
+
+        :param \*fromclauses: a list of one or more
+         :class:`_expression.FromClause`
+         constructs, or other compatible constructs (i.e. ORM-mapped
+         classes) to become part of the correlate collection.
+
+        .. seealso::
+
+            :meth:`_expression.ScalarSelect.correlate_except`
+
+            :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+
+        """
+        self.element = cast("Select[Any]", self.element).correlate(
+            *fromclauses
+        )
+        return self
+
+    @_generative
+    def correlate_except(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        r"""Return a new :class:`_expression.ScalarSelect`
+        which will omit the given FROM
+        clauses from the auto-correlation process.
+
+        This method is mirrored from the
+        :meth:`_sql.Select.correlate_except` method of the underlying
+        :class:`_sql.Select`.  The method applies the
+        :meth:_sql.Select.correlate_except` method, then returns a new
+        :class:`_sql.ScalarSelect` against that statement.
+
+        .. versionadded:: 1.4 Previously, the
+           :meth:`_sql.ScalarSelect.correlate_except`
+           method was only available from :class:`_sql.Select`.
+
+        :param \*fromclauses: a list of one or more
+         :class:`_expression.FromClause`
+         constructs, or other compatible constructs (i.e. ORM-mapped
+         classes) to become part of the correlate-exception collection.
+
+        .. seealso::
+
+            :meth:`_expression.ScalarSelect.correlate`
+
+            :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial
+
+
+        """
+
+        self.element = cast("Select[Any]", self.element).correlate_except(
+            *fromclauses
+        )
+        return self
+
+
+class Exists(UnaryExpression[bool]):
+    """Represent an ``EXISTS`` clause.
+
+    See :func:`_sql.exists` for a description of usage.
+
+    An ``EXISTS`` clause can also be constructed from a :func:`_sql.select`
+    instance by calling :meth:`_sql.SelectBase.exists`.
+
+    """
+
+    inherit_cache = True
+    element: Union[SelectStatementGrouping[Select[Any]], ScalarSelect[Any]]
+
+    def __init__(
+        self,
+        __argument: Optional[
+            Union[_ColumnsClauseArgument[Any], SelectBase, ScalarSelect[Any]]
+        ] = None,
+    ):
+        s: ScalarSelect[Any]
+
+        # TODO: this seems like we should be using coercions for this
+        if __argument is None:
+            s = Select(literal_column("*")).scalar_subquery()
+        elif isinstance(__argument, SelectBase):
+            s = __argument.scalar_subquery()
+            s._propagate_attrs = __argument._propagate_attrs
+        elif isinstance(__argument, ScalarSelect):
+            s = __argument
+        else:
+            s = Select(__argument).scalar_subquery()
+
+        UnaryExpression.__init__(
+            self,
+            s,
+            operator=operators.exists,
+            type_=type_api.BOOLEANTYPE,
+            wraps_column_expression=True,
+        )
+
+    @util.ro_non_memoized_property
+    def _from_objects(self) -> List[FromClause]:
+        return []
+
+    def _regroup(
+        self, fn: Callable[[Select[Any]], Select[Any]]
+    ) -> SelectStatementGrouping[Select[Any]]:
+        element = self.element._ungroup()
+        new_element = fn(element)
+
+        return_value = new_element.self_group(against=operators.exists)
+        assert isinstance(return_value, SelectStatementGrouping)
+        return return_value
+
+    def select(self) -> Select[Tuple[bool]]:
+        r"""Return a SELECT of this :class:`_expression.Exists`.
+
+        e.g.::
+
+            stmt = exists(some_table.c.id).where(some_table.c.id == 5).select()
+
+        This will produce a statement resembling:
+
+        .. sourcecode:: sql
+
+            SELECT EXISTS (SELECT id FROM some_table WHERE some_table = :param) AS anon_1
+
+        .. seealso::
+
+            :func:`_expression.select` - general purpose
+            method which allows for arbitrary column lists.
+
+        """  # noqa
+
+        return Select(self)
+
+    def correlate(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        """Apply correlation to the subquery noted by this
+        :class:`_sql.Exists`.
+
+        .. seealso::
+
+            :meth:`_sql.ScalarSelect.correlate`
+
+        """
+        e = self._clone()
+        e.element = self._regroup(
+            lambda element: element.correlate(*fromclauses)
+        )
+        return e
+
+    def correlate_except(
+        self,
+        *fromclauses: Union[Literal[None, False], _FromClauseArgument],
+    ) -> Self:
+        """Apply correlation to the subquery noted by this
+        :class:`_sql.Exists`.
+
+        .. seealso::
+
+            :meth:`_sql.ScalarSelect.correlate_except`
+
+        """
+
+        e = self._clone()
+        e.element = self._regroup(
+            lambda element: element.correlate_except(*fromclauses)
+        )
+        return e
+
+    def select_from(self, *froms: _FromClauseArgument) -> Self:
+        """Return a new :class:`_expression.Exists` construct,
+        applying the given
+        expression to the :meth:`_expression.Select.select_from`
+        method of the select
+        statement contained.
+
+        .. note:: it is typically preferable to build a :class:`_sql.Select`
+           statement first, including the desired WHERE clause, then use the
+           :meth:`_sql.SelectBase.exists` method to produce an
+           :class:`_sql.Exists` object at once.
+
+        """
+        e = self._clone()
+        e.element = self._regroup(lambda element: element.select_from(*froms))
+        return e
+
+    def where(self, *clause: _ColumnExpressionArgument[bool]) -> Self:
+        """Return a new :func:`_expression.exists` construct with the
+        given expression added to
+        its WHERE clause, joined to the existing clause via AND, if any.
+
+
+        .. note:: it is typically preferable to build a :class:`_sql.Select`
+           statement first, including the desired WHERE clause, then use the
+           :meth:`_sql.SelectBase.exists` method to produce an
+           :class:`_sql.Exists` object at once.
+
+        """
+        e = self._clone()
+        e.element = self._regroup(lambda element: element.where(*clause))
+        return e
+
+
+class TextualSelect(SelectBase, ExecutableReturnsRows, Generative):
+    """Wrap a :class:`_expression.TextClause` construct within a
+    :class:`_expression.SelectBase`
+    interface.
+
+    This allows the :class:`_expression.TextClause` object to gain a
+    ``.c`` collection
+    and other FROM-like capabilities such as
+    :meth:`_expression.FromClause.alias`,
+    :meth:`_expression.SelectBase.cte`, etc.
+
+    The :class:`_expression.TextualSelect` construct is produced via the
+    :meth:`_expression.TextClause.columns`
+    method - see that method for details.
+
+    .. versionchanged:: 1.4 the :class:`_expression.TextualSelect`
+       class was renamed
+       from ``TextAsFrom``, to more correctly suit its role as a
+       SELECT-oriented object and not a FROM clause.
+
+    .. seealso::
+
+        :func:`_expression.text`
+
+        :meth:`_expression.TextClause.columns` - primary creation interface.
+
+    """
+
+    __visit_name__ = "textual_select"
+
+    _label_style = LABEL_STYLE_NONE
+
+    _traverse_internals: _TraverseInternalsType = (
+        [
+            ("element", InternalTraversal.dp_clauseelement),
+            ("column_args", InternalTraversal.dp_clauseelement_list),
+        ]
+        + SupportsCloneAnnotations._clone_annotations_traverse_internals
+        + HasCTE._has_ctes_traverse_internals
+    )
+
+    _is_textual = True
+
+    is_text = True
+    is_select = True
+
+    def __init__(
+        self,
+        text: TextClause,
+        columns: List[_ColumnExpressionArgument[Any]],
+        positional: bool = False,
+    ) -> None:
+        self._init(
+            text,
+            # convert for ORM attributes->columns, etc
+            [
+                coercions.expect(roles.LabeledColumnExprRole, c)
+                for c in columns
+            ],
+            positional,
+        )
+
+    def _init(
+        self,
+        text: TextClause,
+        columns: List[NamedColumn[Any]],
+        positional: bool = False,
+    ) -> None:
+        self.element = text
+        self.column_args = columns
+        self.positional = positional
+
+    @HasMemoized_ro_memoized_attribute
+    def selected_columns(
+        self,
+    ) -> ColumnCollection[str, KeyedColumnElement[Any]]:
+        """A :class:`_expression.ColumnCollection`
+        representing the columns that
+        this SELECT statement or similar construct returns in its result set,
+        not including :class:`_sql.TextClause` constructs.
+
+        This collection differs from the :attr:`_expression.FromClause.columns`
+        collection of a :class:`_expression.FromClause` in that the columns
+        within this collection cannot be directly nested inside another SELECT
+        statement; a subquery must be applied first which provides for the
+        necessary parenthesization required by SQL.
+
+        For a :class:`_expression.TextualSelect` construct, the collection
+        contains the :class:`_expression.ColumnElement` objects that were
+        passed to the constructor, typically via the
+        :meth:`_expression.TextClause.columns` method.
+
+
+        .. versionadded:: 1.4
+
+        """
+        return ColumnCollection(
+            (c.key, c) for c in self.column_args
+        ).as_readonly()
+
+    @util.ro_non_memoized_property
+    def _all_selected_columns(self) -> _SelectIterable:
+        return self.column_args
+
+    def set_label_style(self, style: SelectLabelStyle) -> TextualSelect:
+        return self
+
+    def _ensure_disambiguated_names(self) -> TextualSelect:
+        return self
+
+    @_generative
+    def bindparams(
+        self,
+        *binds: BindParameter[Any],
+        **bind_as_values: Any,
+    ) -> Self:
+        self.element = self.element.bindparams(*binds, **bind_as_values)
+        return self
+
+    def _generate_fromclause_column_proxies(
+        self,
+        fromclause: FromClause,
+        columns: ColumnCollection[str, KeyedColumnElement[Any]],
+        primary_key: ColumnSet,
+        foreign_keys: Set[KeyedColumnElement[Any]],
+        *,
+        proxy_compound_columns: Optional[
+            Iterable[Sequence[ColumnElement[Any]]]
+        ] = None,
+    ) -> None:
+        if TYPE_CHECKING:
+            assert isinstance(fromclause, Subquery)
+
+        if proxy_compound_columns:
+            columns._populate_separate_keys(
+                c._make_proxy(
+                    fromclause,
+                    compound_select_cols=extra_cols,
+                    primary_key=primary_key,
+                    foreign_keys=foreign_keys,
+                )
+                for c, extra_cols in zip(
+                    self.column_args, proxy_compound_columns
+                )
+            )
+        else:
+            columns._populate_separate_keys(
+                c._make_proxy(
+                    fromclause,
+                    primary_key=primary_key,
+                    foreign_keys=foreign_keys,
+                )
+                for c in self.column_args
+            )
+
+    def _scalar_type(self) -> Union[TypeEngine[Any], Any]:
+        return self.column_args[0].type
+
+
+TextAsFrom = TextualSelect
+"""Backwards compatibility with the previous name"""
+
+
+class AnnotatedFromClause(Annotated):
+    def _copy_internals(self, **kw: Any) -> None:
+        super()._copy_internals(**kw)
+        if kw.get("ind_cols_on_fromclause", False):
+            ee = self._Annotated__element  # type: ignore
+
+            self.c = ee.__class__.c.fget(self)  # type: ignore
+
+    @util.ro_memoized_property
+    def c(self) -> ReadOnlyColumnCollection[str, KeyedColumnElement[Any]]:
+        """proxy the .c collection of the underlying FromClause.
+
+        Originally implemented in 2008 as a simple load of the .c collection
+        when the annotated construct was created (see d3621ae961a), in modern
+        SQLAlchemy versions this can be expensive for statements constructed
+        with ORM aliases.   So for #8796 SQLAlchemy 2.0 we instead proxy
+        it, which works just as well.
+
+        Two different use cases seem to require the collection either copied
+        from the underlying one, or unique to this AnnotatedFromClause.
+
+        See test_selectable->test_annotated_corresponding_column
+
+        """
+        ee = self._Annotated__element  # type: ignore
+        return ee.c  # type: ignore