You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6948 lines
232 KiB
Python
6948 lines
232 KiB
Python
3 months ago
|
# sql/selectable.py
|
||
|
# Copyright (C) 2005-2022 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.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import collections
|
||
|
import itertools
|
||
|
from operator import attrgetter
|
||
|
|
||
|
from . import coercions
|
||
|
from . import operators
|
||
|
from . import roles
|
||
|
from . import traversals
|
||
|
from . import type_api
|
||
|
from . import visitors
|
||
|
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 _expand_cloned
|
||
|
from .base import _from_objects
|
||
|
from .base import _generative
|
||
|
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 .base import prefix_anon_map
|
||
|
from .coercions import _document_text_coercion
|
||
|
from .elements import _anonymous_label
|
||
|
from .elements import and_
|
||
|
from .elements import BindParameter
|
||
|
from .elements import BooleanClauseList
|
||
|
from .elements import ClauseElement
|
||
|
from .elements import ClauseList
|
||
|
from .elements import ColumnClause
|
||
|
from .elements import GroupedElement
|
||
|
from .elements import Grouping
|
||
|
from .elements import literal_column
|
||
|
from .elements import TableValuedColumn
|
||
|
from .elements import UnaryExpression
|
||
|
from .visitors import InternalTraversal
|
||
|
from .. import exc
|
||
|
from .. import util
|
||
|
from ..inspection import inspect
|
||
|
|
||
|
|
||
|
class _OffsetLimitParam(BindParameter):
|
||
|
inherit_cache = True
|
||
|
|
||
|
@property
|
||
|
def _limit_offset_value(self):
|
||
|
return self.effective_value
|
||
|
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The standalone :func:`.subquery` function is deprecated "
|
||
|
"and will be removed in a future release. Use select().subquery().",
|
||
|
)
|
||
|
def subquery(alias, *args, **kwargs):
|
||
|
r"""Return an :class:`.Subquery` object derived
|
||
|
from a :class:`_expression.Select`.
|
||
|
|
||
|
:param alias: the alias name for the subquery
|
||
|
|
||
|
:param \*args, \**kwargs: all other arguments are passed through to the
|
||
|
:func:`_expression.select` function.
|
||
|
|
||
|
"""
|
||
|
return Select.create_legacy_select(*args, **kwargs).subquery(alias)
|
||
|
|
||
|
|
||
|
class ReturnsRows(roles.ReturnsRowsRole, ClauseElement):
|
||
|
"""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_statement = False
|
||
|
_is_lateral = False
|
||
|
|
||
|
@property
|
||
|
def selectable(self):
|
||
|
return self
|
||
|
|
||
|
@property
|
||
|
def _all_selected_columns(self):
|
||
|
"""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()
|
||
|
|
||
|
@property
|
||
|
def exported_columns(self):
|
||
|
"""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 Selectable(ReturnsRows):
|
||
|
"""Mark a class as being selectable."""
|
||
|
|
||
|
__visit_name__ = "selectable"
|
||
|
|
||
|
is_selectable = True
|
||
|
|
||
|
def _refresh_for_new_column(self, column):
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def lateral(self, name=None):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
||
|
|
||
|
"""
|
||
|
return Lateral._construct(self, 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, alias):
|
||
|
"""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, require_embedded=False):
|
||
|
"""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(object):
|
||
|
_prefixes = ()
|
||
|
|
||
|
_has_prefixes_traverse_internals = [
|
||
|
("_prefixes", InternalTraversal.dp_prefix_sequence)
|
||
|
]
|
||
|
|
||
|
@_generative
|
||
|
@_document_text_coercion(
|
||
|
"expr",
|
||
|
":meth:`_expression.HasPrefixes.prefix_with`",
|
||
|
":paramref:`.HasPrefixes.prefix_with.*expr`",
|
||
|
)
|
||
|
def prefix_with(self, *expr, **kw):
|
||
|
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 \*expr: textual or :class:`_expression.ClauseElement`
|
||
|
construct which
|
||
|
will be rendered following the INSERT, UPDATE, or DELETE
|
||
|
keyword.
|
||
|
:param \**kw: A single keyword 'dialect' is accepted. This is an
|
||
|
optional string dialect name which will
|
||
|
limit rendering of this prefix to only that dialect.
|
||
|
|
||
|
"""
|
||
|
dialect = kw.pop("dialect", None)
|
||
|
if kw:
|
||
|
raise exc.ArgumentError(
|
||
|
"Unsupported argument(s): %s" % ",".join(kw)
|
||
|
)
|
||
|
self._setup_prefixes(expr, dialect)
|
||
|
|
||
|
def _setup_prefixes(self, prefixes, dialect=None):
|
||
|
self._prefixes = self._prefixes + tuple(
|
||
|
[
|
||
|
(coercions.expect(roles.StatementOptionRole, p), dialect)
|
||
|
for p in prefixes
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
class HasSuffixes(object):
|
||
|
_suffixes = ()
|
||
|
|
||
|
_has_suffixes_traverse_internals = [
|
||
|
("_suffixes", InternalTraversal.dp_prefix_sequence)
|
||
|
]
|
||
|
|
||
|
@_generative
|
||
|
@_document_text_coercion(
|
||
|
"expr",
|
||
|
":meth:`_expression.HasSuffixes.suffix_with`",
|
||
|
":paramref:`.HasSuffixes.suffix_with.*expr`",
|
||
|
)
|
||
|
def suffix_with(self, *expr, **kw):
|
||
|
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 \*expr: textual or :class:`_expression.ClauseElement`
|
||
|
construct which
|
||
|
will be rendered following the target clause.
|
||
|
:param \**kw: A single keyword 'dialect' is accepted. This is an
|
||
|
optional string dialect name which will
|
||
|
limit rendering of this suffix to only that dialect.
|
||
|
|
||
|
"""
|
||
|
dialect = kw.pop("dialect", None)
|
||
|
if kw:
|
||
|
raise exc.ArgumentError(
|
||
|
"Unsupported argument(s): %s" % ",".join(kw)
|
||
|
)
|
||
|
self._setup_suffixes(expr, dialect)
|
||
|
|
||
|
def _setup_suffixes(self, suffixes, dialect=None):
|
||
|
self._suffixes = self._suffixes + tuple(
|
||
|
[
|
||
|
(coercions.expect(roles.StatementOptionRole, p), dialect)
|
||
|
for p in suffixes
|
||
|
]
|
||
|
)
|
||
|
|
||
|
|
||
|
class HasHints(object):
|
||
|
_hints = util.immutabledict()
|
||
|
_statement_hints = ()
|
||
|
|
||
|
_has_hints_traverse_internals = [
|
||
|
("_statement_hints", InternalTraversal.dp_statement_hint_list),
|
||
|
("_hints", InternalTraversal.dp_table_hint_list),
|
||
|
]
|
||
|
|
||
|
def with_statement_hint(self, text, dialect_name="*"):
|
||
|
"""Add a statement hint to this :class:`_expression.Select` or
|
||
|
other selectable object.
|
||
|
|
||
|
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.
|
||
|
|
||
|
.. versionadded:: 1.0.0
|
||
|
|
||
|
.. 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 optimizer hints
|
||
|
|
||
|
"""
|
||
|
return self.with_hint(None, text, dialect_name)
|
||
|
|
||
|
@_generative
|
||
|
def with_hint(self, selectable, text, dialect_name="*"):
|
||
|
r"""Add an indexing or other executional context hint for the given
|
||
|
selectable to this :class:`_expression.Select` or other selectable
|
||
|
object.
|
||
|
|
||
|
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, the
|
||
|
following::
|
||
|
|
||
|
select(mytable).\
|
||
|
with_hint(mytable, "index(%(name)s ix_mytable)")
|
||
|
|
||
|
Would render SQL as::
|
||
|
|
||
|
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
|
||
|
and Sybase simultaneously::
|
||
|
|
||
|
select(mytable).\
|
||
|
with_hint(mytable, "index(%(name)s ix_mytable)", 'oracle').\
|
||
|
with_hint(mytable, "WITH INDEX ix_mytable", 'sybase')
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.with_statement_hint`
|
||
|
|
||
|
"""
|
||
|
if selectable is None:
|
||
|
self._statement_hints += ((dialect_name, text),)
|
||
|
else:
|
||
|
self._hints = self._hints.union(
|
||
|
{
|
||
|
(
|
||
|
coercions.expect(roles.FromClauseRole, selectable),
|
||
|
dialect_name,
|
||
|
): text
|
||
|
}
|
||
|
)
|
||
|
|
||
|
|
||
|
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
|
||
|
_hide_froms = []
|
||
|
|
||
|
schema = 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
|
||
|
|
||
|
@util.deprecated_params(
|
||
|
whereclause=(
|
||
|
"2.0",
|
||
|
"The :paramref:`_sql.FromClause.select().whereclause` parameter "
|
||
|
"is deprecated and will be removed in version 2.0. "
|
||
|
"Please make use of "
|
||
|
"the :meth:`.Select.where` "
|
||
|
"method to add WHERE criteria to the SELECT statement.",
|
||
|
),
|
||
|
kwargs=(
|
||
|
"2.0",
|
||
|
"The :meth:`_sql.FromClause.select` method will no longer accept "
|
||
|
"keyword arguments in version 2.0. Please use generative methods "
|
||
|
"from the "
|
||
|
":class:`_sql.Select` construct in order to apply additional "
|
||
|
"modifications.",
|
||
|
),
|
||
|
)
|
||
|
def select(self, whereclause=None, **kwargs):
|
||
|
r"""Return a SELECT of this :class:`_expression.FromClause`.
|
||
|
|
||
|
|
||
|
e.g.::
|
||
|
|
||
|
stmt = some_table.select().where(some_table.c.id == 5)
|
||
|
|
||
|
:param whereclause: a WHERE clause, equivalent to calling the
|
||
|
:meth:`_sql.Select.where` method.
|
||
|
|
||
|
:param \**kwargs: additional keyword arguments are passed to the
|
||
|
legacy constructor for :class:`_sql.Select` described at
|
||
|
:meth:`_sql.Select.create_legacy_select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`_expression.select` - general purpose
|
||
|
method which allows for arbitrary column lists.
|
||
|
|
||
|
"""
|
||
|
if whereclause is not None:
|
||
|
kwargs["whereclause"] = whereclause
|
||
|
return Select._create_select_from_fromclause(self, [self], **kwargs)
|
||
|
|
||
|
def join(self, right, onclause=None, isouter=False, full=False):
|
||
|
"""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::
|
||
|
|
||
|
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`.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. 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, onclause=None, full=False):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.FromClause.join`
|
||
|
|
||
|
:class:`_expression.Join`
|
||
|
|
||
|
"""
|
||
|
|
||
|
return Join(self, right, onclause, True, full)
|
||
|
|
||
|
def alias(self, name=None, flat=False):
|
||
|
"""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)
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.sqltypes")
|
||
|
def table_valued(self):
|
||
|
"""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 and SQL Server.
|
||
|
|
||
|
E.g.::
|
||
|
|
||
|
>>> 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)
|
||
|
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)
|
||
|
|
||
|
def tablesample(self, sampling, name=None, seed=None):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`_expression.tablesample` - usage guidelines and parameters
|
||
|
|
||
|
"""
|
||
|
return TableSample._construct(self, sampling, name, seed)
|
||
|
|
||
|
def is_derived_from(self, fromclause):
|
||
|
"""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):
|
||
|
"""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 self._cloned_set.intersection(other._cloned_set)
|
||
|
|
||
|
@property
|
||
|
def description(self):
|
||
|
"""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._populate_separate_keys(
|
||
|
col._make_proxy(fromclause) for col in self.c
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def exported_columns(self):
|
||
|
"""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.columns
|
||
|
|
||
|
@util.memoized_property
|
||
|
def columns(self):
|
||
|
"""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.
|
||
|
|
||
|
"""
|
||
|
|
||
|
if "_columns" not in self.__dict__:
|
||
|
self._init_collections()
|
||
|
self._populate_column_collection()
|
||
|
return self._columns.as_immutable()
|
||
|
|
||
|
@property
|
||
|
def entity_namespace(self):
|
||
|
"""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.columns
|
||
|
|
||
|
@util.memoized_property
|
||
|
def primary_key(self):
|
||
|
"""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._init_collections()
|
||
|
self._populate_column_collection()
|
||
|
return self.primary_key
|
||
|
|
||
|
@util.memoized_property
|
||
|
def foreign_keys(self):
|
||
|
"""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._init_collections()
|
||
|
self._populate_column_collection()
|
||
|
return self.foreign_keys
|
||
|
|
||
|
def _reset_column_collection(self):
|
||
|
"""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", "primary_key", "foreign_keys"]:
|
||
|
self.__dict__.pop(key, None)
|
||
|
|
||
|
c = property(
|
||
|
attrgetter("columns"),
|
||
|
doc="""
|
||
|
A named-based collection of :class:`_expression.ColumnElement`
|
||
|
objects maintained by this :class:`_expression.FromClause`.
|
||
|
|
||
|
The :attr:`_sql.FromClause.c` attribute is an alias for the
|
||
|
:attr:`_sql.FromClause.columns` attribute.
|
||
|
|
||
|
:return: a :class:`.ColumnCollection`
|
||
|
|
||
|
""",
|
||
|
)
|
||
|
_select_iterable = property(attrgetter("columns"))
|
||
|
|
||
|
def _init_collections(self):
|
||
|
assert "_columns" not in self.__dict__
|
||
|
assert "primary_key" not in self.__dict__
|
||
|
assert "foreign_keys" not in self.__dict__
|
||
|
|
||
|
self._columns = ColumnCollection()
|
||
|
self.primary_key = ColumnSet()
|
||
|
self.foreign_keys = set()
|
||
|
|
||
|
@property
|
||
|
def _cols_populated(self):
|
||
|
return "_columns" in self.__dict__
|
||
|
|
||
|
def _populate_column_collection(self):
|
||
|
"""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):
|
||
|
"""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=None, flat=False):
|
||
|
return self.alias(name=name)
|
||
|
|
||
|
|
||
|
LABEL_STYLE_NONE = util.symbol(
|
||
|
"LABEL_STYLE_NONE",
|
||
|
"""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::
|
||
|
|
||
|
>>> 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))
|
||
|
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 = util.symbol(
|
||
|
"LABEL_STYLE_TABLENAME_PLUS_COL",
|
||
|
"""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``::
|
||
|
|
||
|
>>> 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))
|
||
|
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 = util.symbol(
|
||
|
"LABEL_STYLE_DISAMBIGUATE_ONLY",
|
||
|
"""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``::
|
||
|
|
||
|
>>> 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))
|
||
|
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
|
||
|
|
||
|
"""
|
||
|
|
||
|
|
||
|
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 = [
|
||
|
("left", InternalTraversal.dp_clauseelement),
|
||
|
("right", InternalTraversal.dp_clauseelement),
|
||
|
("onclause", InternalTraversal.dp_clauseelement),
|
||
|
("isouter", InternalTraversal.dp_boolean),
|
||
|
("full", InternalTraversal.dp_boolean),
|
||
|
]
|
||
|
|
||
|
_is_join = True
|
||
|
|
||
|
def __init__(self, left, right, onclause=None, isouter=False, full=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.
|
||
|
|
||
|
"""
|
||
|
self.left = coercions.expect(
|
||
|
roles.FromClauseRole, left, deannotate=True
|
||
|
)
|
||
|
self.right = coercions.expect(
|
||
|
roles.FromClauseRole, right, deannotate=True
|
||
|
).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
|
||
|
|
||
|
@classmethod
|
||
|
def _create_outerjoin(cls, left, right, onclause=None, full=False):
|
||
|
"""Return an ``OUTER JOIN`` clause element.
|
||
|
|
||
|
The returned object is an instance of :class:`_expression.Join`.
|
||
|
|
||
|
Similar functionality is also available via the
|
||
|
:meth:`_expression.FromClause.outerjoin` method on any
|
||
|
:class:`_expression.FromClause`.
|
||
|
|
||
|
:param left: The left side of the join.
|
||
|
|
||
|
:param right: The right side of the join.
|
||
|
|
||
|
:param onclause: Optional criterion for the ``ON`` clause, is
|
||
|
derived from foreign key relationships established between
|
||
|
left and right otherwise.
|
||
|
|
||
|
To chain joins together, use the :meth:`_expression.FromClause.join`
|
||
|
or
|
||
|
:meth:`_expression.FromClause.outerjoin` methods on the resulting
|
||
|
:class:`_expression.Join` object.
|
||
|
|
||
|
"""
|
||
|
return cls(left, right, onclause, isouter=True, full=full)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_join(
|
||
|
cls, left, right, onclause=None, isouter=False, full=False
|
||
|
):
|
||
|
"""Produce a :class:`_expression.Join` object, given two
|
||
|
:class:`_expression.FromClause`
|
||
|
expressions.
|
||
|
|
||
|
E.g.::
|
||
|
|
||
|
j = join(user_table, 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::
|
||
|
|
||
|
SELECT user.id, user.name FROM user
|
||
|
JOIN address ON user.id = address.user_id
|
||
|
|
||
|
Similar functionality is available given any
|
||
|
:class:`_expression.FromClause` object (e.g. such as a
|
||
|
:class:`_schema.Table`) using
|
||
|
the :meth:`_expression.FromClause.join` method.
|
||
|
|
||
|
:param left: The left side of the join.
|
||
|
|
||
|
: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 JOIN.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.FromClause.join` - method form,
|
||
|
based on a given left side.
|
||
|
|
||
|
:class:`_expression.Join` - the type of object produced.
|
||
|
|
||
|
"""
|
||
|
|
||
|
return cls(left, right, onclause, isouter, full)
|
||
|
|
||
|
@property
|
||
|
def description(self):
|
||
|
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):
|
||
|
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=None):
|
||
|
return FromGrouping(self)
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.util")
|
||
|
def _populate_column_collection(self):
|
||
|
sqlutil = util.preloaded.sql_util
|
||
|
columns = [c for c in self.left.columns] + [
|
||
|
c for c in self.right.columns
|
||
|
]
|
||
|
|
||
|
self.primary_key.extend(
|
||
|
sqlutil.reduce_columns(
|
||
|
(c for c in columns if c.primary_key), self.onclause
|
||
|
)
|
||
|
)
|
||
|
self._columns._populate_separate_keys(
|
||
|
(col._tq_key_label, col) for col in columns
|
||
|
)
|
||
|
self.foreign_keys.update(
|
||
|
itertools.chain(*[col.foreign_keys for col in columns])
|
||
|
)
|
||
|
|
||
|
def _copy_internals(self, clone=_clone, **kw):
|
||
|
# 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, **kw):
|
||
|
if isinstance(obj, ColumnClause) and obj.table in new_froms:
|
||
|
newelem = new_froms[obj.table].corresponding_column(obj)
|
||
|
return newelem
|
||
|
|
||
|
kw["replace"] = replace
|
||
|
|
||
|
# run normal _copy_internals. the clones for
|
||
|
# left and right will come from the clone function's
|
||
|
# cache
|
||
|
super(Join, self)._copy_internals(clone=clone, **kw)
|
||
|
|
||
|
self._reset_memoizations()
|
||
|
|
||
|
def _refresh_for_new_column(self, column):
|
||
|
super(Join, self)._refresh_for_new_column(column)
|
||
|
self.left._refresh_for_new_column(column)
|
||
|
self.right._refresh_for_new_column(column)
|
||
|
|
||
|
def _match_primaries(self, left, right):
|
||
|
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, b, a_subset=None, consider_as_foreign_keys=None
|
||
|
):
|
||
|
"""Create a join condition between two tables or selectables.
|
||
|
|
||
|
e.g.::
|
||
|
|
||
|
join_condition(tablea, tableb)
|
||
|
|
||
|
would produce an expression along the lines of::
|
||
|
|
||
|
tablea.c.id==tableb.c.tablea_id
|
||
|
|
||
|
The join is determined based on the foreign key relationships
|
||
|
between the two selectables. If there are multiple ways
|
||
|
to join, or no way to join, an error is raised.
|
||
|
|
||
|
:param a_subset: An optional expression that is a sub-component
|
||
|
of ``a``. An attempt will be made to join to just this sub-component
|
||
|
first before looking at the full ``a`` construct, and if found
|
||
|
will be successful even if there are other ways to join to ``a``.
|
||
|
This allows the "right side" of a join to be passed thereby
|
||
|
providing a "natural join".
|
||
|
|
||
|
"""
|
||
|
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, right, consider_as_foreign_keys=None):
|
||
|
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, a_subset, b, consider_as_foreign_keys
|
||
|
):
|
||
|
sql_util = util.preloaded.sql_util
|
||
|
|
||
|
a = coercions.expect(roles.FromClauseRole, a)
|
||
|
b = coercions.expect(roles.FromClauseRole, b)
|
||
|
|
||
|
constraints = 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, b, constraints, consider_as_foreign_keys
|
||
|
):
|
||
|
# 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 set(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 = set(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)
|
||
|
)
|
||
|
|
||
|
@util.deprecated_params(
|
||
|
whereclause=(
|
||
|
"2.0",
|
||
|
"The :paramref:`_sql.Join.select().whereclause` parameter "
|
||
|
"is deprecated and will be removed in version 2.0. "
|
||
|
"Please make use of "
|
||
|
"the :meth:`.Select.where` "
|
||
|
"method to add WHERE criteria to the SELECT statement.",
|
||
|
),
|
||
|
kwargs=(
|
||
|
"2.0",
|
||
|
"The :meth:`_sql.Join.select` method will no longer accept "
|
||
|
"keyword arguments in version 2.0. Please use generative "
|
||
|
"methods from the "
|
||
|
":class:`_sql.Select` construct in order to apply additional "
|
||
|
"modifications.",
|
||
|
),
|
||
|
)
|
||
|
def select(self, whereclause=None, **kwargs):
|
||
|
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::
|
||
|
|
||
|
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
|
||
|
|
||
|
:param whereclause: WHERE criteria, same as calling
|
||
|
:meth:`_sql.Select.where` on the resulting statement
|
||
|
|
||
|
:param \**kwargs: additional keyword arguments are passed to the
|
||
|
legacy constructor for :class:`_sql.Select` described at
|
||
|
:meth:`_sql.Select.create_legacy_select`.
|
||
|
|
||
|
"""
|
||
|
collist = [self.left, self.right]
|
||
|
|
||
|
if whereclause is not None:
|
||
|
kwargs["whereclause"] = whereclause
|
||
|
return Select._create_select_from_fromclause(
|
||
|
self, collist, **kwargs
|
||
|
).select_from(self)
|
||
|
|
||
|
@property
|
||
|
@util.deprecated_20(
|
||
|
":attr:`.Executable.bind`",
|
||
|
alternative="Bound metadata is being removed as of SQLAlchemy 2.0.",
|
||
|
enable_warnings=False,
|
||
|
)
|
||
|
def bind(self):
|
||
|
"""Return the bound engine associated with either the left or right
|
||
|
side of this :class:`_sql.Join`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
return self.left.bind or self.right.bind
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.util")
|
||
|
def _anonymous_fromclause(self, name=None, flat=False):
|
||
|
sqlutil = util.preloaded.sql_util
|
||
|
if flat:
|
||
|
if name is not None:
|
||
|
raise exc.ArgumentError("Can't send name argument with flat")
|
||
|
left_a, right_a = (
|
||
|
self.left._anonymous_fromclause(flat=True),
|
||
|
self.right._anonymous_fromclause(flat=True),
|
||
|
)
|
||
|
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.deprecated_20(
|
||
|
":meth:`_sql.Join.alias`",
|
||
|
alternative="Create a select + subquery, or alias the "
|
||
|
"individual tables inside the join, instead.",
|
||
|
)
|
||
|
def alias(self, name=None, flat=False):
|
||
|
r"""Return an alias of this :class:`_expression.Join`.
|
||
|
|
||
|
The default behavior here is to first produce a SELECT
|
||
|
construct from this :class:`_expression.Join`, then to produce an
|
||
|
:class:`_expression.Alias` from that. So given a join of the form::
|
||
|
|
||
|
j = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
|
||
|
|
||
|
The JOIN by itself would look like::
|
||
|
|
||
|
table_a JOIN table_b ON table_a.id = table_b.a_id
|
||
|
|
||
|
Whereas the alias of the above, ``j.alias()``, would in a
|
||
|
SELECT context look like::
|
||
|
|
||
|
(SELECT table_a.id AS table_a_id, table_b.id AS table_b_id,
|
||
|
table_b.a_id AS table_b_a_id
|
||
|
FROM table_a
|
||
|
JOIN table_b ON table_a.id = table_b.a_id) AS anon_1
|
||
|
|
||
|
The equivalent long-hand form, given a :class:`_expression.Join`
|
||
|
object ``j``, is::
|
||
|
|
||
|
from sqlalchemy import select, alias
|
||
|
j = alias(
|
||
|
select(j.left, j.right).\
|
||
|
select_from(j).\
|
||
|
set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL).\
|
||
|
correlate(False),
|
||
|
name=name
|
||
|
)
|
||
|
|
||
|
The selectable produced by :meth:`_expression.Join.alias`
|
||
|
features the same
|
||
|
columns as that of the two individual selectables presented under
|
||
|
a single name - the individual columns are "auto-labeled", meaning
|
||
|
the ``.c.`` collection of the resulting :class:`_expression.Alias`
|
||
|
represents
|
||
|
the names of the individual columns using a
|
||
|
``<tablename>_<columname>`` scheme::
|
||
|
|
||
|
j.c.table_a_id
|
||
|
j.c.table_b_a_id
|
||
|
|
||
|
:meth:`_expression.Join.alias` also features an alternate
|
||
|
option for aliasing joins which produces no enclosing SELECT and
|
||
|
does not normally apply labels to the column names. The
|
||
|
``flat=True`` option will call :meth:`_expression.FromClause.alias`
|
||
|
against the left and right sides individually.
|
||
|
Using this option, no new ``SELECT`` is produced;
|
||
|
we instead, from a construct as below::
|
||
|
|
||
|
j = table_a.join(table_b, table_a.c.id == table_b.c.a_id)
|
||
|
j = j.alias(flat=True)
|
||
|
|
||
|
we get a result like this::
|
||
|
|
||
|
table_a AS table_a_1 JOIN table_b AS table_b_1 ON
|
||
|
table_a_1.id = table_b_1.a_id
|
||
|
|
||
|
The ``flat=True`` argument is also propagated to the contained
|
||
|
selectables, so that a composite join such as::
|
||
|
|
||
|
j = table_a.join(
|
||
|
table_b.join(table_c,
|
||
|
table_b.c.id == table_c.c.b_id),
|
||
|
table_b.c.a_id == table_a.c.id
|
||
|
).alias(flat=True)
|
||
|
|
||
|
Will produce an expression like::
|
||
|
|
||
|
table_a AS table_a_1 JOIN (
|
||
|
table_b AS table_b_1 JOIN table_c AS table_c_1
|
||
|
ON table_b_1.id = table_c_1.b_id
|
||
|
) ON table_a_1.id = table_b_1.a_id
|
||
|
|
||
|
The standalone :func:`_expression.alias` function as well as the
|
||
|
base :meth:`_expression.FromClause.alias`
|
||
|
method also support the ``flat=True``
|
||
|
argument as a no-op, so that the argument can be passed to the
|
||
|
``alias()`` method of any selectable.
|
||
|
|
||
|
:param name: name given to the alias.
|
||
|
|
||
|
:param flat: if True, produce an alias of the left and right
|
||
|
sides of this :class:`_expression.Join` and return the join of those
|
||
|
two selectables. This produces join expression that does not
|
||
|
include an enclosing SELECT.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`core_tutorial_aliases`
|
||
|
|
||
|
:func:`_expression.alias`
|
||
|
|
||
|
"""
|
||
|
return self._anonymous_fromclause(flat=flat, name=name)
|
||
|
|
||
|
@property
|
||
|
def _hide_froms(self):
|
||
|
return itertools.chain(
|
||
|
*[_from_objects(x.left, x.right) for x in self._cloned_set]
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return [self] + self.left._from_objects + self.right._from_objects
|
||
|
|
||
|
|
||
|
class NoInit(object):
|
||
|
def __init__(self, *arg, **kw):
|
||
|
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(),
|
||
|
)
|
||
|
)
|
||
|
|
||
|
|
||
|
# 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, FromClause):
|
||
|
"""Base class of aliases against tables, subqueries, and other
|
||
|
selectables."""
|
||
|
|
||
|
_is_from_container = True
|
||
|
named_with_column = True
|
||
|
|
||
|
_supports_derived_columns = False
|
||
|
|
||
|
_traverse_internals = [
|
||
|
("element", InternalTraversal.dp_clauseelement),
|
||
|
("name", InternalTraversal.dp_anon_name),
|
||
|
]
|
||
|
|
||
|
@classmethod
|
||
|
def _construct(cls, *arg, **kw):
|
||
|
obj = cls.__new__(cls)
|
||
|
obj._init(*arg, **kw)
|
||
|
return obj
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, returnsrows, name=None):
|
||
|
"""Base factory method. Subclasses need to provide this."""
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def _init(self, selectable, name=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):
|
||
|
super(AliasedReturnsRows, self)._refresh_for_new_column(column)
|
||
|
self.element._refresh_for_new_column(column)
|
||
|
|
||
|
@property
|
||
|
def description(self):
|
||
|
name = self.name
|
||
|
if isinstance(name, _anonymous_label):
|
||
|
name = "anon_1"
|
||
|
|
||
|
if util.py3k:
|
||
|
return name
|
||
|
else:
|
||
|
return name.encode("ascii", "backslashreplace")
|
||
|
|
||
|
@property
|
||
|
def original(self):
|
||
|
"""Legacy for dialects that are referring to Alias.original."""
|
||
|
return self.element
|
||
|
|
||
|
def is_derived_from(self, fromclause):
|
||
|
if fromclause in self._cloned_set:
|
||
|
return True
|
||
|
return self.element.is_derived_from(fromclause)
|
||
|
|
||
|
def _populate_column_collection(self):
|
||
|
self.element._generate_fromclause_column_proxies(self)
|
||
|
|
||
|
def _copy_internals(self, clone=_clone, **kw):
|
||
|
existing_element = self.element
|
||
|
|
||
|
super(AliasedReturnsRows, self)._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):
|
||
|
return [self]
|
||
|
|
||
|
@property
|
||
|
def bind(self):
|
||
|
return self.element.bind
|
||
|
|
||
|
|
||
|
class Alias(roles.DMLTableRole, AliasedReturnsRows):
|
||
|
"""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).
|
||
|
|
||
|
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
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, selectable, name=None, flat=False):
|
||
|
"""Return an :class:`_expression.Alias` object.
|
||
|
|
||
|
An :class:`_expression.Alias` represents any
|
||
|
:class:`_expression.FromClause`
|
||
|
with an alternate name assigned within SQL, typically using the ``AS``
|
||
|
clause when generated, e.g. ``SELECT * FROM table AS aliasname``.
|
||
|
|
||
|
Similar functionality is available via the
|
||
|
:meth:`_expression.FromClause.alias`
|
||
|
method available on all :class:`_expression.FromClause` subclasses.
|
||
|
In terms of
|
||
|
a SELECT object as generated from the :func:`_expression.select`
|
||
|
function, the :meth:`_expression.SelectBase.alias` method returns an
|
||
|
:class:`_expression.Alias` or similar object which represents a named,
|
||
|
parenthesized subquery.
|
||
|
|
||
|
When an :class:`_expression.Alias` is created from a
|
||
|
:class:`_schema.Table` object,
|
||
|
this has the effect of the table being rendered
|
||
|
as ``tablename AS aliasname`` in a SELECT statement.
|
||
|
|
||
|
For :func:`_expression.select` objects, the effect is that of
|
||
|
creating a named subquery, i.e. ``(select ...) AS aliasname``.
|
||
|
|
||
|
The ``name`` parameter is optional, and provides the name
|
||
|
to use in the rendered SQL. If blank, an "anonymous" name
|
||
|
will be deterministically generated at compile time.
|
||
|
Deterministic means the name is guaranteed to be unique against
|
||
|
other constructs used in the same statement, and will also be the
|
||
|
same name for each successive compilation of the same statement
|
||
|
object.
|
||
|
|
||
|
:param selectable: any :class:`_expression.FromClause` subclass,
|
||
|
such as a table, select statement, etc.
|
||
|
|
||
|
:param name: string name to be assigned as the alias.
|
||
|
If ``None``, a name will be deterministically generated
|
||
|
at compile time.
|
||
|
|
||
|
:param flat: Will be passed through to if the given selectable
|
||
|
is an instance of :class:`_expression.Join` - see
|
||
|
:meth:`_expression.Join.alias`
|
||
|
for details.
|
||
|
|
||
|
"""
|
||
|
return coercions.expect(
|
||
|
roles.FromClauseRole, selectable, allow_select=True
|
||
|
).alias(name=name, flat=flat)
|
||
|
|
||
|
|
||
|
class TableValuedAlias(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.::
|
||
|
|
||
|
>>> from sqlalchemy import select, func
|
||
|
>>> fn = func.json_array_elements_text('["one", "two", "three"]').table_valued("value")
|
||
|
>>> print(select(fn.c.value))
|
||
|
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 = [
|
||
|
("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,
|
||
|
name=None,
|
||
|
table_value_type=None,
|
||
|
joins_implicitly=False,
|
||
|
):
|
||
|
super(TableValuedAlias, self)._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):
|
||
|
"""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.::
|
||
|
|
||
|
>>> print(select(func.some_func().table_valued("value").column))
|
||
|
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=None):
|
||
|
"""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._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=None):
|
||
|
"""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=None, with_types=False):
|
||
|
"""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.::
|
||
|
|
||
|
>>> print(
|
||
|
... select(
|
||
|
... func.unnest(array(["one", "two", "three"])).
|
||
|
table_valued("x", with_ordinality="o").render_derived()
|
||
|
... )
|
||
|
... )
|
||
|
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)::
|
||
|
|
||
|
>>> 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)
|
||
|
... )
|
||
|
... )
|
||
|
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._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(AliasedReturnsRows):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
||
|
|
||
|
"""
|
||
|
|
||
|
__visit_name__ = "lateral"
|
||
|
_is_lateral = True
|
||
|
|
||
|
inherit_cache = True
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, selectable, name=None):
|
||
|
"""Return a :class:`_expression.Lateral` object.
|
||
|
|
||
|
:class:`_expression.Lateral` is an :class:`_expression.Alias`
|
||
|
subclass that represents
|
||
|
a subquery with the LATERAL keyword applied to it.
|
||
|
|
||
|
The special behavior of a LATERAL subquery is that it appears in the
|
||
|
FROM clause of an enclosing SELECT, but may correlate to other
|
||
|
FROM clauses of that SELECT. It is a special case of subquery
|
||
|
only supported by a small number of backends, currently more recent
|
||
|
PostgreSQL versions.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
||
|
|
||
|
|
||
|
"""
|
||
|
return coercions.expect(
|
||
|
roles.FromClauseRole, selectable, explicit_subquery=True
|
||
|
).lateral(name=name)
|
||
|
|
||
|
|
||
|
class TableSample(AliasedReturnsRows):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`_expression.tablesample`
|
||
|
|
||
|
"""
|
||
|
|
||
|
__visit_name__ = "tablesample"
|
||
|
|
||
|
_traverse_internals = AliasedReturnsRows._traverse_internals + [
|
||
|
("sampling", InternalTraversal.dp_clauseelement),
|
||
|
("seed", InternalTraversal.dp_clauseelement),
|
||
|
]
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, selectable, sampling, name=None, seed=None):
|
||
|
"""Return a :class:`_expression.TableSample` object.
|
||
|
|
||
|
:class:`_expression.TableSample` is an :class:`_expression.Alias`
|
||
|
subclass that represents
|
||
|
a table with the TABLESAMPLE clause applied to it.
|
||
|
:func:`_expression.tablesample`
|
||
|
is also available from the :class:`_expression.FromClause`
|
||
|
class via the
|
||
|
:meth:`_expression.FromClause.tablesample` method.
|
||
|
|
||
|
The TABLESAMPLE clause allows selecting a randomly selected approximate
|
||
|
percentage of rows from a table. It supports multiple sampling methods,
|
||
|
most commonly BERNOULLI and SYSTEM.
|
||
|
|
||
|
e.g.::
|
||
|
|
||
|
from sqlalchemy import func
|
||
|
|
||
|
selectable = people.tablesample(
|
||
|
func.bernoulli(1),
|
||
|
name='alias',
|
||
|
seed=func.random())
|
||
|
stmt = select(selectable.c.people_id)
|
||
|
|
||
|
Assuming ``people`` with a column ``people_id``, the above
|
||
|
statement would render as::
|
||
|
|
||
|
SELECT alias.people_id FROM
|
||
|
people AS alias TABLESAMPLE bernoulli(:bernoulli_1)
|
||
|
REPEATABLE (random())
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
:param sampling: a ``float`` percentage between 0 and 100 or
|
||
|
:class:`_functions.Function`.
|
||
|
|
||
|
:param name: optional alias name
|
||
|
|
||
|
:param seed: any real-valued SQL expression. When specified, the
|
||
|
REPEATABLE sub-clause is also rendered.
|
||
|
|
||
|
"""
|
||
|
return coercions.expect(roles.FromClauseRole, selectable).tablesample(
|
||
|
sampling, name=name, seed=seed
|
||
|
)
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.functions")
|
||
|
def _init(self, selectable, sampling, name=None, seed=None):
|
||
|
functions = util.preloaded.sql_functions
|
||
|
if not isinstance(sampling, functions.Function):
|
||
|
sampling = functions.func.system(sampling)
|
||
|
|
||
|
self.sampling = sampling
|
||
|
self.seed = seed
|
||
|
super(TableSample, self)._init(selectable, name=name)
|
||
|
|
||
|
def _get_method(self):
|
||
|
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 = (
|
||
|
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
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, selectable, name=None, recursive=False):
|
||
|
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,
|
||
|
name=None,
|
||
|
recursive=False,
|
||
|
nesting=False,
|
||
|
_cte_alias=None,
|
||
|
_restates=None,
|
||
|
_prefixes=None,
|
||
|
_suffixes=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(CTE, self)._init(selectable, name=name)
|
||
|
|
||
|
def _populate_column_collection(self):
|
||
|
if self._cte_alias is not None:
|
||
|
self._cte_alias._generate_fromclause_column_proxies(self)
|
||
|
else:
|
||
|
self.element._generate_fromclause_column_proxies(self)
|
||
|
|
||
|
def alias(self, name=None, flat=False):
|
||
|
"""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):
|
||
|
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
|
||
|
|
||
|
"""
|
||
|
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):
|
||
|
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
|
||
|
|
||
|
"""
|
||
|
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):
|
||
|
"""
|
||
|
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 HasCTE(roles.HasCTERole):
|
||
|
"""Mixin that declares a class to include CTE support.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
"""
|
||
|
|
||
|
_has_ctes_traverse_internals = [
|
||
|
("_independent_ctes", InternalTraversal.dp_clauseelement_list),
|
||
|
]
|
||
|
|
||
|
_independent_ctes = ()
|
||
|
|
||
|
@_generative
|
||
|
def add_cte(self, cte):
|
||
|
"""Add a :class:`_sql.CTE` to this statement object that will be
|
||
|
independently rendered even if not referenced in the statement
|
||
|
otherwise.
|
||
|
|
||
|
This feature is useful for 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; while PostgreSQL is known
|
||
|
to support this usage, it may not be supported by other backends.
|
||
|
|
||
|
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::
|
||
|
|
||
|
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 referred towards 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::
|
||
|
|
||
|
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
|
||
|
|
||
|
"""
|
||
|
cte = coercions.expect(roles.IsCTERole, cte)
|
||
|
self._independent_ctes += (cte,)
|
||
|
|
||
|
def cte(self, name=None, recursive=False, nesting=False):
|
||
|
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.
|
||
|
|
||
|
.. versionchanged:: 1.1 Added support for UPDATE/INSERT/DELETE as
|
||
|
CTE, CTEs added to UPDATE/INSERT/DELETE.
|
||
|
|
||
|
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
|
||
|
actual statement.
|
||
|
|
||
|
.. versionadded:: 1.4.24
|
||
|
|
||
|
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::
|
||
|
|
||
|
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
|
||
|
|
||
|
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::
|
||
|
|
||
|
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`.
|
||
|
|
||
|
"""
|
||
|
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
|
||
|
|
||
|
@classmethod
|
||
|
def _factory(cls, selectable, name=None):
|
||
|
"""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):
|
||
|
return self.element.set_label_style(LABEL_STYLE_NONE).scalar_subquery()
|
||
|
|
||
|
def _execute_on_connection(
|
||
|
self,
|
||
|
connection,
|
||
|
multiparams,
|
||
|
params,
|
||
|
execution_options,
|
||
|
):
|
||
|
util.warn_deprecated(
|
||
|
"Executing a subquery object is deprecated and will raise "
|
||
|
"ObjectNotExecutableError in an upcoming release. Please "
|
||
|
"execute the underlying select() statement directly.",
|
||
|
"1.4",
|
||
|
)
|
||
|
return self.element._execute_on_connection(
|
||
|
connection, multiparams, params, execution_options, _force=True
|
||
|
)
|
||
|
|
||
|
|
||
|
class FromGrouping(GroupedElement, FromClause):
|
||
|
"""Represent a grouping of a FROM clause"""
|
||
|
|
||
|
_traverse_internals = [("element", InternalTraversal.dp_clauseelement)]
|
||
|
|
||
|
def __init__(self, element):
|
||
|
self.element = coercions.expect(roles.FromClauseRole, element)
|
||
|
|
||
|
def _init_collections(self):
|
||
|
pass
|
||
|
|
||
|
@property
|
||
|
def columns(self):
|
||
|
return self.element.columns
|
||
|
|
||
|
@property
|
||
|
def primary_key(self):
|
||
|
return self.element.primary_key
|
||
|
|
||
|
@property
|
||
|
def foreign_keys(self):
|
||
|
return self.element.foreign_keys
|
||
|
|
||
|
def is_derived_from(self, element):
|
||
|
return self.element.is_derived_from(element)
|
||
|
|
||
|
def alias(self, **kw):
|
||
|
return FromGrouping(self.element.alias(**kw))
|
||
|
|
||
|
def _anonymous_fromclause(self, **kw):
|
||
|
return FromGrouping(self.element._anonymous_fromclause(**kw))
|
||
|
|
||
|
@property
|
||
|
def _hide_froms(self):
|
||
|
return self.element._hide_froms
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return self.element._from_objects
|
||
|
|
||
|
def __getstate__(self):
|
||
|
return {"element": self.element}
|
||
|
|
||
|
def __setstate__(self, state):
|
||
|
self.element = state["element"]
|
||
|
|
||
|
|
||
|
class TableClause(roles.DMLTableRole, Immutable, FromClause):
|
||
|
"""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 = [
|
||
|
(
|
||
|
"columns",
|
||
|
InternalTraversal.dp_fromclause_canonical_column_collection,
|
||
|
),
|
||
|
("name", InternalTraversal.dp_string),
|
||
|
("schema", InternalTraversal.dp_string),
|
||
|
]
|
||
|
|
||
|
named_with_column = True
|
||
|
|
||
|
implicit_returning = False
|
||
|
""":class:`_expression.TableClause`
|
||
|
doesn't support having a primary key or column
|
||
|
-level defaults, so implicit returning doesn't apply."""
|
||
|
|
||
|
_autoincrement_column = None
|
||
|
"""No PK or default support so no autoincrement column."""
|
||
|
|
||
|
def __init__(self, name, *columns, **kw):
|
||
|
"""Produce a new :class:`_expression.TableClause`.
|
||
|
|
||
|
The object returned is an instance of
|
||
|
:class:`_expression.TableClause`, which
|
||
|
represents the "syntactical" portion of the schema-level
|
||
|
:class:`_schema.Table` object.
|
||
|
It may be used to construct lightweight table constructs.
|
||
|
|
||
|
.. versionchanged:: 1.0.0 :func:`_expression.table` can now
|
||
|
be imported from the plain ``sqlalchemy`` namespace like any
|
||
|
other SQL element.
|
||
|
|
||
|
|
||
|
:param name: Name of the table.
|
||
|
|
||
|
:param columns: A collection of :func:`_expression.column` constructs.
|
||
|
|
||
|
:param schema: The schema name for this table.
|
||
|
|
||
|
.. versionadded:: 1.3.18 :func:`_expression.table` can now
|
||
|
accept a ``schema`` argument.
|
||
|
"""
|
||
|
|
||
|
super(TableClause, self).__init__()
|
||
|
self.name = name
|
||
|
self._columns = DedupeColumnCollection()
|
||
|
self.primary_key = ColumnSet()
|
||
|
self.foreign_keys = set()
|
||
|
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))
|
||
|
|
||
|
def __str__(self):
|
||
|
if self.schema is not None:
|
||
|
return self.schema + "." + self.name
|
||
|
else:
|
||
|
return self.name
|
||
|
|
||
|
def _refresh_for_new_column(self, column):
|
||
|
pass
|
||
|
|
||
|
def _init_collections(self):
|
||
|
pass
|
||
|
|
||
|
@util.memoized_property
|
||
|
def description(self):
|
||
|
if util.py3k:
|
||
|
return self.name
|
||
|
else:
|
||
|
return self.name.encode("ascii", "backslashreplace")
|
||
|
|
||
|
def append_column(self, c, **kw):
|
||
|
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, values=None, inline=False, **kwargs):
|
||
|
"""Generate an :func:`_expression.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, values=values, inline=inline, **kwargs
|
||
|
)
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.dml")
|
||
|
def update(self, whereclause=None, values=None, inline=False, **kwargs):
|
||
|
"""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,
|
||
|
whereclause=whereclause,
|
||
|
values=values,
|
||
|
inline=inline,
|
||
|
**kwargs
|
||
|
)
|
||
|
|
||
|
@util.preload_module("sqlalchemy.sql.dml")
|
||
|
def delete(self, whereclause=None, **kwargs):
|
||
|
"""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, whereclause, **kwargs)
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return [self]
|
||
|
|
||
|
|
||
|
class ForUpdateArg(ClauseElement):
|
||
|
_traverse_internals = [
|
||
|
("of", InternalTraversal.dp_clauseelement_list),
|
||
|
("nowait", InternalTraversal.dp_boolean),
|
||
|
("read", InternalTraversal.dp_boolean),
|
||
|
("skip_locked", InternalTraversal.dp_boolean),
|
||
|
]
|
||
|
|
||
|
@classmethod
|
||
|
def _from_argument(cls, with_for_update):
|
||
|
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(**with_for_update)
|
||
|
|
||
|
def __eq__(self, other):
|
||
|
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):
|
||
|
return not self.__eq__(other)
|
||
|
|
||
|
def __hash__(self):
|
||
|
return id(self)
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
nowait=False,
|
||
|
read=False,
|
||
|
of=None,
|
||
|
skip_locked=False,
|
||
|
key_share=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(Generative, FromClause):
|
||
|
"""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
|
||
|
|
||
|
"""
|
||
|
|
||
|
named_with_column = True
|
||
|
__visit_name__ = "values"
|
||
|
|
||
|
_data = ()
|
||
|
|
||
|
_traverse_internals = [
|
||
|
("_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, **kw):
|
||
|
r"""Construct a :class:`_expression.Values` construct.
|
||
|
|
||
|
The column expressions and the actual data for
|
||
|
:class:`_expression.Values` are given in two separate steps. The
|
||
|
constructor receives the column expressions typically as
|
||
|
:func:`_expression.column` constructs,
|
||
|
and the data is then passed via the
|
||
|
:meth:`_expression.Values.data` method as a list,
|
||
|
which can be called multiple
|
||
|
times to add more data, e.g.::
|
||
|
|
||
|
from sqlalchemy import column
|
||
|
from sqlalchemy import values
|
||
|
|
||
|
value_expr = values(
|
||
|
column('id', Integer),
|
||
|
column('name', String),
|
||
|
name="my_values"
|
||
|
).data(
|
||
|
[(1, 'name1'), (2, 'name2'), (3, 'name3')]
|
||
|
)
|
||
|
|
||
|
:param \*columns: column expressions, typically composed using
|
||
|
:func:`_expression.column` objects.
|
||
|
|
||
|
:param name: the name for this VALUES construct. If omitted, the
|
||
|
VALUES construct will be unnamed in a SQL expression. Different
|
||
|
backends may have different requirements here.
|
||
|
|
||
|
:param literal_binds: Defaults to False. Whether or not to render
|
||
|
the data values inline in the SQL output, rather than using bound
|
||
|
parameters.
|
||
|
|
||
|
"""
|
||
|
|
||
|
super(Values, self).__init__()
|
||
|
self._column_args = columns
|
||
|
self.name = kw.pop("name", None)
|
||
|
self.literal_binds = kw.pop("literal_binds", False)
|
||
|
self.named_with_column = self.name is not None
|
||
|
|
||
|
@property
|
||
|
def _column_types(self):
|
||
|
return [col.type for col in self._column_args]
|
||
|
|
||
|
@_generative
|
||
|
def alias(self, name, **kw):
|
||
|
"""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`
|
||
|
|
||
|
"""
|
||
|
self.name = name
|
||
|
self.named_with_column = self.name is not None
|
||
|
|
||
|
@_generative
|
||
|
def lateral(self, name=None):
|
||
|
"""Return a new :class:`_expression.Values` with the lateral flag set,
|
||
|
so that
|
||
|
it renders as LATERAL.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`_expression.lateral`
|
||
|
|
||
|
"""
|
||
|
self._is_lateral = True
|
||
|
if name is not None:
|
||
|
self.name = name
|
||
|
|
||
|
@_generative
|
||
|
def data(self, values):
|
||
|
"""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,)
|
||
|
|
||
|
def _populate_column_collection(self):
|
||
|
for c in self._column_args:
|
||
|
self._columns.add(c)
|
||
|
c.table = self
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return [self]
|
||
|
|
||
|
|
||
|
class SelectBase(
|
||
|
roles.SelectStatementRole,
|
||
|
roles.DMLSelectRole,
|
||
|
roles.CompoundElementRole,
|
||
|
roles.InElementRole,
|
||
|
HasCTE,
|
||
|
Executable,
|
||
|
SupportsCloneAnnotations,
|
||
|
Selectable,
|
||
|
):
|
||
|
"""Base class for SELECT statements.
|
||
|
|
||
|
|
||
|
This includes :class:`_expression.Select`,
|
||
|
:class:`_expression.CompoundSelect` and
|
||
|
:class:`_expression.TextualSelect`.
|
||
|
|
||
|
|
||
|
"""
|
||
|
|
||
|
_is_select_statement = True
|
||
|
is_select = True
|
||
|
|
||
|
def _generate_fromclause_column_proxies(self, fromclause):
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def _refresh_for_new_column(self, column):
|
||
|
self._reset_memoizations()
|
||
|
|
||
|
@property
|
||
|
def selected_columns(self):
|
||
|
"""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()
|
||
|
|
||
|
@property
|
||
|
def _all_selected_columns(self):
|
||
|
"""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):
|
||
|
"""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
|
||
|
|
||
|
@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):
|
||
|
return self._implicit_subquery.columns
|
||
|
|
||
|
@property
|
||
|
def columns(self):
|
||
|
return self.c
|
||
|
|
||
|
@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, **kw):
|
||
|
return self._implicit_subquery.select(*arg, **kw)
|
||
|
|
||
|
@HasMemoized.memoized_attribute
|
||
|
def _implicit_subquery(self):
|
||
|
return self.subquery()
|
||
|
|
||
|
@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):
|
||
|
return self.scalar_subquery()
|
||
|
|
||
|
def exists(self):
|
||
|
"""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):
|
||
|
"""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):
|
||
|
"""Return a 'scalar' representation of this selectable, embedded as a
|
||
|
subquery with a label.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.SelectBase.as_scalar`.
|
||
|
|
||
|
"""
|
||
|
return self.scalar_subquery().label(name)
|
||
|
|
||
|
def lateral(self, name=None):
|
||
|
"""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.
|
||
|
|
||
|
.. versionadded:: 1.1
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_lateral_correlation` - overview of usage.
|
||
|
|
||
|
"""
|
||
|
return Lateral._factory(self, name)
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return [self]
|
||
|
|
||
|
def subquery(self, name=None):
|
||
|
"""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::
|
||
|
|
||
|
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::
|
||
|
|
||
|
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)
|
||
|
|
||
|
def _ensure_disambiguated_names(self):
|
||
|
"""Ensure that the names generated by this selectbase will be
|
||
|
disambiguated in some way, if possible.
|
||
|
|
||
|
"""
|
||
|
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def alias(self, name=None, flat=False):
|
||
|
"""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)
|
||
|
|
||
|
|
||
|
class SelectStatementGrouping(GroupedElement, SelectBase):
|
||
|
"""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 = [("element", InternalTraversal.dp_clauseelement)]
|
||
|
|
||
|
_is_select_container = True
|
||
|
|
||
|
def __init__(self, element):
|
||
|
self.element = coercions.expect(roles.SelectStatementRole, element)
|
||
|
|
||
|
def _ensure_disambiguated_names(self):
|
||
|
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):
|
||
|
return self._label_style
|
||
|
|
||
|
def set_label_style(self, label_style):
|
||
|
return SelectStatementGrouping(
|
||
|
self.element.set_label_style(label_style)
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def _label_style(self):
|
||
|
return self.element._label_style
|
||
|
|
||
|
@property
|
||
|
def select_statement(self):
|
||
|
return self.element
|
||
|
|
||
|
def self_group(self, against=None):
|
||
|
return self
|
||
|
|
||
|
def _generate_columns_plus_names(self, anon_for_dupe_key):
|
||
|
return self.element._generate_columns_plus_names(anon_for_dupe_key)
|
||
|
|
||
|
def _generate_fromclause_column_proxies(self, subquery):
|
||
|
self.element._generate_fromclause_column_proxies(subquery)
|
||
|
|
||
|
def _generate_proxy_for_new_column(self, column, subquery):
|
||
|
return self.element._generate_proxy_for_new_column(subquery)
|
||
|
|
||
|
@property
|
||
|
def _all_selected_columns(self):
|
||
|
return self.element._all_selected_columns
|
||
|
|
||
|
@property
|
||
|
def selected_columns(self):
|
||
|
"""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
|
||
|
|
||
|
@property
|
||
|
def _from_objects(self):
|
||
|
return self.element._from_objects
|
||
|
|
||
|
|
||
|
class DeprecatedSelectBaseGenerations(object):
|
||
|
"""A collection of methods available on :class:`_sql.Select` and
|
||
|
:class:`_sql.CompoundSelect`, these are all **deprecated** methods as they
|
||
|
modify the object in-place.
|
||
|
|
||
|
"""
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.GenerativeSelect.append_order_by` "
|
||
|
"method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative method "
|
||
|
":meth:`_expression.GenerativeSelect.order_by`.",
|
||
|
)
|
||
|
def append_order_by(self, *clauses):
|
||
|
"""Append the given ORDER BY criterion applied to this selectable.
|
||
|
|
||
|
The criterion will be appended to any pre-existing ORDER BY criterion.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.GenerativeSelect.order_by` method is preferred,
|
||
|
as it
|
||
|
provides standard :term:`method chaining`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.GenerativeSelect.order_by`
|
||
|
|
||
|
"""
|
||
|
self.order_by.non_generative(self, *clauses)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.GenerativeSelect.append_group_by` "
|
||
|
"method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative method "
|
||
|
":meth:`_expression.GenerativeSelect.group_by`.",
|
||
|
)
|
||
|
def append_group_by(self, *clauses):
|
||
|
"""Append the given GROUP BY criterion applied to this selectable.
|
||
|
|
||
|
The criterion will be appended to any pre-existing GROUP BY criterion.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.GenerativeSelect.group_by` method is preferred,
|
||
|
as it
|
||
|
provides standard :term:`method chaining`.
|
||
|
|
||
|
|
||
|
"""
|
||
|
self.group_by.non_generative(self, *clauses)
|
||
|
|
||
|
|
||
|
class GenerativeSelect(DeprecatedSelectBaseGenerations, SelectBase):
|
||
|
"""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 = ()
|
||
|
_group_by_clauses = ()
|
||
|
_limit_clause = None
|
||
|
_offset_clause = None
|
||
|
_fetch_clause = None
|
||
|
_fetch_clause_options = None
|
||
|
_for_update_arg = None
|
||
|
|
||
|
@util.deprecated_params(
|
||
|
bind=(
|
||
|
"2.0",
|
||
|
"The :paramref:`_sql.select.bind` argument is deprecated and "
|
||
|
"will be removed in SQLAlchemy 2.0.",
|
||
|
),
|
||
|
)
|
||
|
def __init__(
|
||
|
self,
|
||
|
_label_style=LABEL_STYLE_DEFAULT,
|
||
|
use_labels=False,
|
||
|
limit=None,
|
||
|
offset=None,
|
||
|
order_by=None,
|
||
|
group_by=None,
|
||
|
bind=None,
|
||
|
):
|
||
|
if use_labels:
|
||
|
if util.SQLALCHEMY_WARN_20:
|
||
|
util.warn_deprecated_20(
|
||
|
"The use_labels=True keyword argument to GenerativeSelect "
|
||
|
"is deprecated and will be removed in version 2.0. Please "
|
||
|
"use "
|
||
|
"select.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) "
|
||
|
"if you need to replicate this legacy behavior.",
|
||
|
stacklevel=4,
|
||
|
)
|
||
|
_label_style = LABEL_STYLE_TABLENAME_PLUS_COL
|
||
|
|
||
|
self._label_style = _label_style
|
||
|
|
||
|
if limit is not None:
|
||
|
self.limit.non_generative(self, limit)
|
||
|
if offset is not None:
|
||
|
self.offset.non_generative(self, offset)
|
||
|
|
||
|
if order_by is not None:
|
||
|
self.order_by.non_generative(self, *util.to_list(order_by))
|
||
|
if group_by is not None:
|
||
|
self.group_by.non_generative(self, *util.to_list(group_by))
|
||
|
|
||
|
self._bind = bind
|
||
|
|
||
|
@_generative
|
||
|
def with_for_update(
|
||
|
self,
|
||
|
nowait=False,
|
||
|
read=False,
|
||
|
of=None,
|
||
|
skip_locked=False,
|
||
|
key_share=False,
|
||
|
):
|
||
|
"""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, the above would render a
|
||
|
statement like::
|
||
|
|
||
|
SELECT table.a, table.b FROM table FOR UPDATE NOWAIT
|
||
|
|
||
|
on other backends, the ``nowait`` option is ignored and instead
|
||
|
would produce::
|
||
|
|
||
|
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
|
||
|
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) which
|
||
|
will render into a ``FOR UPDATE OF`` clause; supported by PostgreSQL
|
||
|
and Oracle. May render as a table or as a column depending on
|
||
|
backend.
|
||
|
|
||
|
:param skip_locked: boolean, will render ``FOR UPDATE SKIP LOCKED``
|
||
|
on Oracle 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,
|
||
|
)
|
||
|
|
||
|
def get_label_style(self):
|
||
|
"""
|
||
|
Retrieve the current label style.
|
||
|
|
||
|
.. versionadded:: 1.4
|
||
|
|
||
|
"""
|
||
|
return self._label_style
|
||
|
|
||
|
def set_label_style(self, style):
|
||
|
"""Return a new selectable with the specified label style.
|
||
|
|
||
|
There are three "label styles" available,
|
||
|
:data:`_sql.LABEL_STYLE_DISAMBIGUATE_ONLY`,
|
||
|
:data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`, and
|
||
|
:data:`_sql.LABEL_STYLE_NONE`. The default style is
|
||
|
:data:`_sql.LABEL_STYLE_TABLENAME_PLUS_COL`.
|
||
|
|
||
|
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
|
||
|
|
||
|
@util.deprecated_20(
|
||
|
":meth:`_sql.GenerativeSelect.apply_labels`",
|
||
|
alternative="Use set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL) "
|
||
|
"instead.",
|
||
|
)
|
||
|
def apply_labels(self):
|
||
|
return self.set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
|
||
|
|
||
|
@property
|
||
|
def _group_by_clause(self):
|
||
|
"""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 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, name=None, type_=None):
|
||
|
"""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_
|
||
|
)
|
||
|
|
||
|
def _offset_or_limit_clause_asint(self, clause, attrname):
|
||
|
"""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:
|
||
|
util.raise_(
|
||
|
exc.CompileError(
|
||
|
"This SELECT structure does not use a simple "
|
||
|
"integer value for %s" % attrname
|
||
|
),
|
||
|
replace_context=err,
|
||
|
)
|
||
|
else:
|
||
|
return util.asint(value)
|
||
|
|
||
|
@property
|
||
|
def _limit(self):
|
||
|
"""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):
|
||
|
"""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):
|
||
|
"""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):
|
||
|
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):
|
||
|
"""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`.
|
||
|
|
||
|
.. versionchanged:: 1.0.0 - :meth:`_expression.Select.limit` can now
|
||
|
accept arbitrary SQL expressions as well as integer values.
|
||
|
|
||
|
: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)
|
||
|
|
||
|
@_generative
|
||
|
def fetch(self, count, with_ties=False, percent=False):
|
||
|
"""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, 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,
|
||
|
}
|
||
|
|
||
|
@_generative
|
||
|
def offset(self, offset):
|
||
|
"""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.
|
||
|
|
||
|
|
||
|
.. versionchanged:: 1.0.0 - :meth:`_expression.Select.offset` can now
|
||
|
accept arbitrary SQL expressions as well as integer values.
|
||
|
|
||
|
: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)
|
||
|
|
||
|
@_generative
|
||
|
@util.preload_module("sqlalchemy.sql.util")
|
||
|
def slice(self, start, stop):
|
||
|
"""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
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def order_by(self, *clauses):
|
||
|
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)
|
||
|
|
||
|
All existing ORDER BY criteria may be cancelled by passing
|
||
|
``None`` by itself. New ORDER BY criteria may then be added by
|
||
|
invoking :meth:`_sql.Select.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 len(clauses) == 1 and clauses[0] is None:
|
||
|
self._order_by_clauses = ()
|
||
|
else:
|
||
|
self._order_by_clauses += tuple(
|
||
|
coercions.expect(roles.OrderByRole, clause)
|
||
|
for clause in clauses
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def group_by(self, *clauses):
|
||
|
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`
|
||
|
|
||
|
"""
|
||
|
|
||
|
if len(clauses) == 1 and clauses[0] is None:
|
||
|
self._group_by_clauses = ()
|
||
|
else:
|
||
|
self._group_by_clauses += tuple(
|
||
|
coercions.expect(roles.GroupByRole, clause)
|
||
|
for clause in clauses
|
||
|
)
|
||
|
|
||
|
|
||
|
@CompileState.plugin_for("default", "compound_select")
|
||
|
class CompoundSelectState(CompileState):
|
||
|
@util.memoized_property
|
||
|
def _label_resolve_dict(self):
|
||
|
# TODO: this is hacky and slow
|
||
|
hacky_subquery = self.statement.subquery()
|
||
|
hacky_subquery.named_with_column = False
|
||
|
d = dict((c.key, c) for c in hacky_subquery.c)
|
||
|
return d, d, d
|
||
|
|
||
|
|
||
|
class CompoundSelect(HasCompileState, GenerativeSelect):
|
||
|
"""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 = [
|
||
|
("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
|
||
|
|
||
|
UNION = util.symbol("UNION")
|
||
|
UNION_ALL = util.symbol("UNION ALL")
|
||
|
EXCEPT = util.symbol("EXCEPT")
|
||
|
EXCEPT_ALL = util.symbol("EXCEPT ALL")
|
||
|
INTERSECT = util.symbol("INTERSECT")
|
||
|
INTERSECT_ALL = util.symbol("INTERSECT ALL")
|
||
|
|
||
|
_is_from_container = True
|
||
|
|
||
|
def __init__(self, keyword, *selects, **kwargs):
|
||
|
self._auto_correlate = kwargs.pop("correlate", False)
|
||
|
self.keyword = keyword
|
||
|
self.selects = [
|
||
|
coercions.expect(roles.CompoundElementRole, s).self_group(
|
||
|
against=self
|
||
|
)
|
||
|
for s in selects
|
||
|
]
|
||
|
|
||
|
if kwargs and util.SQLALCHEMY_WARN_20:
|
||
|
util.warn_deprecated_20(
|
||
|
"Set functions such as union(), union_all(), extract(), etc. "
|
||
|
"in SQLAlchemy 2.0 will accept a "
|
||
|
"series of SELECT statements only. "
|
||
|
"Please use generative methods such as order_by() for "
|
||
|
"additional modifications to this CompoundSelect.",
|
||
|
stacklevel=4,
|
||
|
)
|
||
|
|
||
|
GenerativeSelect.__init__(self, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_union(cls, *selects, **kwargs):
|
||
|
r"""Return a ``UNION`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
A similar :func:`union()` method is available on all
|
||
|
:class:`_expression.FromClause` subclasses.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.UNION, *selects, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_union_all(cls, *selects, **kwargs):
|
||
|
r"""Return a ``UNION ALL`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
A similar :func:`union_all()` method is available on all
|
||
|
:class:`_expression.FromClause` subclasses.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.UNION_ALL, *selects, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_except(cls, *selects, **kwargs):
|
||
|
r"""Return an ``EXCEPT`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.EXCEPT, *selects, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_except_all(cls, *selects, **kwargs):
|
||
|
r"""Return an ``EXCEPT ALL`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.EXCEPT_ALL, *selects, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_intersect(cls, *selects, **kwargs):
|
||
|
r"""Return an ``INTERSECT`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.INTERSECT, *selects, **kwargs)
|
||
|
|
||
|
@classmethod
|
||
|
def _create_intersect_all(cls, *selects, **kwargs):
|
||
|
r"""Return an ``INTERSECT ALL`` of multiple selectables.
|
||
|
|
||
|
The returned object is an instance of
|
||
|
:class:`_expression.CompoundSelect`.
|
||
|
|
||
|
:param \*selects:
|
||
|
a list of :class:`_expression.Select` instances.
|
||
|
|
||
|
:param \**kwargs:
|
||
|
available keyword arguments are the same as those of
|
||
|
:func:`select`.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect(CompoundSelect.INTERSECT_ALL, *selects, **kwargs)
|
||
|
|
||
|
def _scalar_type(self):
|
||
|
return self.selects[0]._scalar_type()
|
||
|
|
||
|
def self_group(self, against=None):
|
||
|
return SelectStatementGrouping(self)
|
||
|
|
||
|
def is_derived_from(self, fromclause):
|
||
|
for s in self.selects:
|
||
|
if s.is_derived_from(fromclause):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def _set_label_style(self, style):
|
||
|
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):
|
||
|
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):
|
||
|
|
||
|
# 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. We may want to improve upon this, such as
|
||
|
# perhaps _make_proxy can accept a list of other columns
|
||
|
# that are "shared" - schema.column can then copy all the
|
||
|
# ForeignKeys in. this would allow the union() to have all
|
||
|
# those fks too.
|
||
|
select_0 = self.selects[0]
|
||
|
|
||
|
if self._label_style is not LABEL_STYLE_DEFAULT:
|
||
|
select_0 = select_0.set_label_style(self._label_style)
|
||
|
select_0._generate_fromclause_column_proxies(subquery)
|
||
|
|
||
|
# 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
|
||
|
|
||
|
for subq_col, select_cols in zip(
|
||
|
subquery.c._all_columns,
|
||
|
zip(*[s.selected_columns for s in self.selects]),
|
||
|
):
|
||
|
subq_col._proxies = [
|
||
|
c._annotate({"weight": i + 1})
|
||
|
for (i, c) in enumerate(select_cols)
|
||
|
]
|
||
|
|
||
|
def _refresh_for_new_column(self, column):
|
||
|
super(CompoundSelect, self)._refresh_for_new_column(column)
|
||
|
for select in self.selects:
|
||
|
select._refresh_for_new_column(column)
|
||
|
|
||
|
@property
|
||
|
def _all_selected_columns(self):
|
||
|
return self.selects[0]._all_selected_columns
|
||
|
|
||
|
@property
|
||
|
def selected_columns(self):
|
||
|
"""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
|
||
|
|
||
|
@property
|
||
|
@util.deprecated_20(
|
||
|
":attr:`.Executable.bind`",
|
||
|
alternative="Bound metadata is being removed as of SQLAlchemy 2.0.",
|
||
|
enable_warnings=False,
|
||
|
)
|
||
|
def bind(self):
|
||
|
"""Returns the :class:`_engine.Engine` or :class:`_engine.Connection`
|
||
|
to which this :class:`.Executable` is bound, or None if none found.
|
||
|
|
||
|
"""
|
||
|
if self._bind:
|
||
|
return self._bind
|
||
|
for s in self.selects:
|
||
|
e = s.bind
|
||
|
if e:
|
||
|
return e
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
@bind.setter
|
||
|
def bind(self, bind):
|
||
|
self._bind = bind
|
||
|
|
||
|
|
||
|
class DeprecatedSelectGenerations(object):
|
||
|
"""A collection of methods available on :class:`_sql.Select`, these
|
||
|
are all **deprecated** methods as they modify the :class:`_sql.Select`
|
||
|
object in -place.
|
||
|
|
||
|
"""
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_correlation` "
|
||
|
"method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.correlate`.",
|
||
|
)
|
||
|
def append_correlation(self, fromclause):
|
||
|
"""Append the given correlation expression to this select()
|
||
|
construct.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.correlate` method is preferred,
|
||
|
as it provides
|
||
|
standard :term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
self.correlate.non_generative(self, fromclause)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_column` method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.add_columns`.",
|
||
|
)
|
||
|
def append_column(self, column):
|
||
|
"""Append the given column expression to the columns clause of this
|
||
|
select() construct.
|
||
|
|
||
|
E.g.::
|
||
|
|
||
|
my_select.append_column(some_table.c.new_column)
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.add_columns` method is preferred,
|
||
|
as it provides standard
|
||
|
:term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
self.add_columns.non_generative(self, column)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_prefix` method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.prefix_with`.",
|
||
|
)
|
||
|
def append_prefix(self, clause):
|
||
|
"""Append the given columns clause prefix expression to this select()
|
||
|
construct.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.prefix_with` method is preferred,
|
||
|
as it provides
|
||
|
standard :term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
self.prefix_with.non_generative(self, clause)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_whereclause` "
|
||
|
"method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.where`.",
|
||
|
)
|
||
|
def append_whereclause(self, whereclause):
|
||
|
"""Append the given expression to this select() construct's WHERE
|
||
|
criterion.
|
||
|
|
||
|
The expression will be joined to existing WHERE criterion via AND.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.where` method is preferred,
|
||
|
as it provides standard
|
||
|
:term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
self.where.non_generative(self, whereclause)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_having` method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.having`.",
|
||
|
)
|
||
|
def append_having(self, having):
|
||
|
"""Append the given expression to this select() construct's HAVING
|
||
|
criterion.
|
||
|
|
||
|
The expression will be joined to existing HAVING criterion via AND.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.having` method is preferred,
|
||
|
as it provides standard
|
||
|
:term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
self.having.non_generative(self, having)
|
||
|
|
||
|
@util.deprecated(
|
||
|
"1.4",
|
||
|
"The :meth:`_expression.Select.append_from` method is deprecated "
|
||
|
"and will be removed in a future release. Use the generative "
|
||
|
"method :meth:`_expression.Select.select_from`.",
|
||
|
)
|
||
|
def append_from(self, fromclause):
|
||
|
"""Append the given :class:`_expression.FromClause` expression
|
||
|
to this select() construct's FROM clause.
|
||
|
|
||
|
This is an **in-place** mutation method; the
|
||
|
:meth:`_expression.Select.select_from` method is preferred,
|
||
|
as it provides
|
||
|
standard :term:`method chaining`.
|
||
|
|
||
|
"""
|
||
|
self.select_from.non_generative(self, fromclause)
|
||
|
|
||
|
|
||
|
@CompileState.plugin_for("default", "select")
|
||
|
class SelectState(util.MemoizedSlots, CompileState):
|
||
|
__slots__ = (
|
||
|
"from_clauses",
|
||
|
"froms",
|
||
|
"columns_plus_names",
|
||
|
"_label_resolve_dict",
|
||
|
)
|
||
|
|
||
|
class default_select_compile_options(CacheableOptions):
|
||
|
_cache_key_traversal = []
|
||
|
|
||
|
def __init__(self, statement, compiler, **kw):
|
||
|
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):
|
||
|
raise NotImplementedError(
|
||
|
"The default SELECT construct without plugins does not "
|
||
|
"implement this method."
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def get_column_descriptions(cls, statement):
|
||
|
return [
|
||
|
{
|
||
|
"name": name,
|
||
|
"type": element.type,
|
||
|
"expr": element,
|
||
|
}
|
||
|
for _, name, _, element, _ in (
|
||
|
statement._generate_columns_plus_names(False)
|
||
|
)
|
||
|
]
|
||
|
|
||
|
@classmethod
|
||
|
def from_statement(cls, statement, from_statement):
|
||
|
cls._plugin_not_implemented()
|
||
|
|
||
|
@classmethod
|
||
|
def get_columns_clause_froms(cls, statement):
|
||
|
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):
|
||
|
|
||
|
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, col_name=None):
|
||
|
if c._is_text_clause:
|
||
|
return None
|
||
|
|
||
|
elif 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):
|
||
|
return self._normalize_froms(
|
||
|
itertools.chain(
|
||
|
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
|
||
|
]
|
||
|
),
|
||
|
self.from_clauses,
|
||
|
),
|
||
|
check_statement=statement,
|
||
|
)
|
||
|
|
||
|
@classmethod
|
||
|
def _normalize_froms(cls, iterable_of_froms, check_statement=None):
|
||
|
"""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()
|
||
|
froms = []
|
||
|
|
||
|
for item in iterable_of_froms:
|
||
|
if item._is_subquery 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]
|
||
|
|
||
|
return froms
|
||
|
|
||
|
def _get_display_froms(
|
||
|
self, explicit_correlate_froms=None, implicit_correlate_froms=None
|
||
|
):
|
||
|
"""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):
|
||
|
with_cols = dict(
|
||
|
(c._tq_label or c.key, c)
|
||
|
for c in self.statement._all_selected_columns
|
||
|
if c._allow_label_resolve
|
||
|
)
|
||
|
only_froms = dict(
|
||
|
(c.key, c)
|
||
|
for c in _select_iterables(self.froms)
|
||
|
if c._allow_label_resolve
|
||
|
)
|
||
|
only_cols = 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):
|
||
|
if stmt._setup_joins:
|
||
|
return stmt._setup_joins[-1][0]
|
||
|
else:
|
||
|
return None
|
||
|
|
||
|
@classmethod
|
||
|
def all_selected_columns(cls, statement):
|
||
|
return [c for c in _select_iterables(statement._raw_columns)]
|
||
|
|
||
|
def _setup_joins(self, args, raw_columns):
|
||
|
for (right, onclause, left, flags) in args:
|
||
|
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
|
||
|
)
|
||
|
|
||
|
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:
|
||
|
|
||
|
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, left, right, onclause
|
||
|
):
|
||
|
"""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 = None
|
||
|
|
||
|
from_clauses = self.from_clauses
|
||
|
|
||
|
if from_clauses:
|
||
|
|
||
|
indexes = 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):
|
||
|
replace_from_obj_index = None
|
||
|
|
||
|
sql_util = util.preloaded.sql_util
|
||
|
|
||
|
from_clauses = list(self.statement._iterate_from_elements())
|
||
|
|
||
|
if from_clauses:
|
||
|
indexes = 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(object):
|
||
|
def _iterate_from_elements(self):
|
||
|
# note this does not include elements
|
||
|
# in _setup_joins or _legacy_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(
|
||
|
traversals.HasCacheKey, traversals.HasCopyInternals, visitors.Traversible
|
||
|
):
|
||
|
__visit_name__ = "memoized_select_entities"
|
||
|
|
||
|
_traverse_internals = [
|
||
|
("_raw_columns", InternalTraversal.dp_clauseelement_list),
|
||
|
("_setup_joins", InternalTraversal.dp_setup_join_tuple),
|
||
|
("_legacy_setup_joins", InternalTraversal.dp_setup_join_tuple),
|
||
|
("_with_options", InternalTraversal.dp_executable_options),
|
||
|
]
|
||
|
|
||
|
_annotations = util.EMPTY_DICT
|
||
|
|
||
|
def _clone(self, **kw):
|
||
|
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):
|
||
|
if (
|
||
|
select_stmt._setup_joins
|
||
|
or select_stmt._legacy_setup_joins
|
||
|
or select_stmt._with_options
|
||
|
):
|
||
|
self = _MemoizedSelectEntities()
|
||
|
self._raw_columns = select_stmt._raw_columns
|
||
|
self._setup_joins = select_stmt._setup_joins
|
||
|
self._legacy_setup_joins = select_stmt._legacy_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._legacy_setup_joins
|
||
|
) = select_stmt._with_options = ()
|
||
|
|
||
|
|
||
|
class Select(
|
||
|
HasPrefixes,
|
||
|
HasSuffixes,
|
||
|
HasHints,
|
||
|
HasCompileState,
|
||
|
DeprecatedSelectGenerations,
|
||
|
_SelectFromElements,
|
||
|
GenerativeSelect,
|
||
|
):
|
||
|
"""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 = ()
|
||
|
_legacy_setup_joins = ()
|
||
|
_memoized_select_entities = ()
|
||
|
|
||
|
_distinct = False
|
||
|
_distinct_on = ()
|
||
|
_correlate = ()
|
||
|
_correlate_except = None
|
||
|
_where_criteria = ()
|
||
|
_having_criteria = ()
|
||
|
_from_obj = ()
|
||
|
_auto_correlate = True
|
||
|
|
||
|
_compile_options = SelectState.default_select_compile_options
|
||
|
|
||
|
_traverse_internals = (
|
||
|
[
|
||
|
("_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),
|
||
|
("_legacy_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 = _traverse_internals + [
|
||
|
("_compile_options", InternalTraversal.dp_has_cache_key)
|
||
|
]
|
||
|
|
||
|
@classmethod
|
||
|
def _create_select_from_fromclause(cls, target, entities, *arg, **kw):
|
||
|
if arg or kw:
|
||
|
return Select.create_legacy_select(entities, *arg, **kw)
|
||
|
else:
|
||
|
return Select._create_select(*entities)
|
||
|
|
||
|
@classmethod
|
||
|
@util.deprecated(
|
||
|
"2.0",
|
||
|
"The legacy calling style of :func:`_sql.select` is deprecated and "
|
||
|
"will be removed in SQLAlchemy 2.0. Please use the new calling "
|
||
|
"style described at :func:`_sql.select`.",
|
||
|
)
|
||
|
def create_legacy_select(
|
||
|
cls,
|
||
|
columns=None,
|
||
|
whereclause=None,
|
||
|
from_obj=None,
|
||
|
distinct=False,
|
||
|
having=None,
|
||
|
correlate=True,
|
||
|
prefixes=None,
|
||
|
suffixes=None,
|
||
|
**kwargs
|
||
|
):
|
||
|
"""Construct a new :class:`_expression.Select` using the 1.x style API.
|
||
|
|
||
|
This method is called implicitly when the :func:`_expression.select`
|
||
|
construct is used and the first argument is a Python list or other
|
||
|
plain sequence object, which is taken to refer to the columns
|
||
|
collection.
|
||
|
|
||
|
.. versionchanged:: 1.4 Added the :meth:`.Select.create_legacy_select`
|
||
|
constructor which documents the calling style in use when the
|
||
|
:func:`.select` construct is invoked using 1.x-style arguments.
|
||
|
|
||
|
Similar functionality is also available via the
|
||
|
:meth:`_expression.FromClause.select` method on any
|
||
|
:class:`_expression.FromClause`.
|
||
|
|
||
|
All arguments which accept :class:`_expression.ClauseElement` arguments
|
||
|
also accept string arguments, which will be converted as appropriate
|
||
|
into either :func:`_expression.text()` or
|
||
|
:func:`_expression.literal_column()` constructs.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_selecting_data` - in the :ref:`unified_tutorial`
|
||
|
|
||
|
:param columns:
|
||
|
A list of :class:`_expression.ColumnElement` or
|
||
|
:class:`_expression.FromClause`
|
||
|
objects which will form the columns clause of the resulting
|
||
|
statement. For those objects that are instances of
|
||
|
:class:`_expression.FromClause` (typically :class:`_schema.Table`
|
||
|
or :class:`_expression.Alias`
|
||
|
objects), the :attr:`_expression.FromClause.c`
|
||
|
collection is extracted
|
||
|
to form a collection of :class:`_expression.ColumnElement` objects.
|
||
|
|
||
|
This parameter will also accept :class:`_expression.TextClause`
|
||
|
constructs as
|
||
|
given, as well as ORM-mapped classes.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
The :paramref:`_expression.select.columns`
|
||
|
parameter is not available
|
||
|
in the method form of :func:`_expression.select`, e.g.
|
||
|
:meth:`_expression.FromClause.select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.column`
|
||
|
|
||
|
:meth:`_expression.Select.with_only_columns`
|
||
|
|
||
|
:param whereclause:
|
||
|
A :class:`_expression.ClauseElement`
|
||
|
expression which will be used to form the
|
||
|
``WHERE`` clause. It is typically preferable to add WHERE
|
||
|
criterion to an existing :class:`_expression.Select`
|
||
|
using method chaining
|
||
|
with :meth:`_expression.Select.where`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.where`
|
||
|
|
||
|
:param from_obj:
|
||
|
A list of :class:`_expression.ClauseElement`
|
||
|
objects which will be added to the
|
||
|
``FROM`` clause of the resulting statement. This is equivalent
|
||
|
to calling :meth:`_expression.Select.select_from`
|
||
|
using method chaining on
|
||
|
an existing :class:`_expression.Select` object.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.select_from`
|
||
|
- full description of explicit
|
||
|
FROM clause specification.
|
||
|
|
||
|
:param bind=None:
|
||
|
an :class:`_engine.Engine` or :class:`_engine.Connection` instance
|
||
|
to which the
|
||
|
resulting :class:`_expression.Select` object will be bound. The
|
||
|
:class:`_expression.Select`
|
||
|
object will otherwise automatically bind to
|
||
|
whatever :class:`~.base.Connectable` instances can be located within
|
||
|
its contained :class:`_expression.ClauseElement` members.
|
||
|
|
||
|
:param correlate=True:
|
||
|
indicates that this :class:`_expression.Select`
|
||
|
object should have its
|
||
|
contained :class:`_expression.FromClause`
|
||
|
elements "correlated" to an enclosing
|
||
|
:class:`_expression.Select` object.
|
||
|
It is typically preferable to specify
|
||
|
correlations on an existing :class:`_expression.Select`
|
||
|
construct using
|
||
|
:meth:`_expression.Select.correlate`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.correlate`
|
||
|
- full description of correlation.
|
||
|
|
||
|
:param distinct=False:
|
||
|
when ``True``, applies a ``DISTINCT`` qualifier to the columns
|
||
|
clause of the resulting statement.
|
||
|
|
||
|
The boolean argument may also be a column expression or list
|
||
|
of column expressions - this is a special calling form which
|
||
|
is understood by the PostgreSQL dialect to render the
|
||
|
``DISTINCT ON (<columns>)`` syntax.
|
||
|
|
||
|
``distinct`` is also available on an existing
|
||
|
:class:`_expression.Select`
|
||
|
object via the :meth:`_expression.Select.distinct` method.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.distinct`
|
||
|
|
||
|
:param group_by:
|
||
|
a list of :class:`_expression.ClauseElement`
|
||
|
objects which will comprise the
|
||
|
``GROUP BY`` clause of the resulting select. This parameter
|
||
|
is typically specified more naturally using the
|
||
|
:meth:`_expression.Select.group_by` method on an existing
|
||
|
:class:`_expression.Select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.group_by`
|
||
|
|
||
|
:param having:
|
||
|
a :class:`_expression.ClauseElement`
|
||
|
that will comprise the ``HAVING`` clause
|
||
|
of the resulting select when ``GROUP BY`` is used. This parameter
|
||
|
is typically specified more naturally using the
|
||
|
:meth:`_expression.Select.having` method on an existing
|
||
|
:class:`_expression.Select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.having`
|
||
|
|
||
|
:param limit=None:
|
||
|
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. This parameter is typically specified more
|
||
|
naturally using the :meth:`_expression.Select.limit`
|
||
|
method on an existing
|
||
|
:class:`_expression.Select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.limit`
|
||
|
|
||
|
:param offset=None:
|
||
|
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. This parameter is typically specified more naturally
|
||
|
using the :meth:`_expression.Select.offset` method on an existing
|
||
|
:class:`_expression.Select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.offset`
|
||
|
|
||
|
:param order_by:
|
||
|
a scalar or list of :class:`_expression.ClauseElement`
|
||
|
objects which will
|
||
|
comprise the ``ORDER BY`` clause of the resulting select.
|
||
|
This parameter is typically specified more naturally using the
|
||
|
:meth:`_expression.Select.order_by` method on an existing
|
||
|
:class:`_expression.Select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.order_by`
|
||
|
|
||
|
:param use_labels=False:
|
||
|
when ``True``, the statement will be generated using labels
|
||
|
for each column in the columns clause, which qualify each
|
||
|
column with its parent table's (or aliases) name so that name
|
||
|
conflicts between columns in different tables don't occur.
|
||
|
The format of the label is ``<tablename>_<column>``. The "c"
|
||
|
collection of a :class:`_expression.Subquery` created
|
||
|
against this :class:`_expression.Select`
|
||
|
object, as well as the :attr:`_expression.Select.selected_columns`
|
||
|
collection of the :class:`_expression.Select` itself, will use these
|
||
|
names for targeting column members.
|
||
|
|
||
|
This parameter can also be specified on an existing
|
||
|
:class:`_expression.Select` object using the
|
||
|
:meth:`_expression.Select.set_label_style`
|
||
|
method.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:meth:`_expression.Select.set_label_style`
|
||
|
|
||
|
"""
|
||
|
self = cls.__new__(cls)
|
||
|
|
||
|
self._auto_correlate = correlate
|
||
|
|
||
|
if distinct is not False:
|
||
|
if distinct is True:
|
||
|
self.distinct.non_generative(self)
|
||
|
else:
|
||
|
self.distinct.non_generative(self, *util.to_list(distinct))
|
||
|
|
||
|
if from_obj is not None:
|
||
|
self.select_from.non_generative(self, *util.to_list(from_obj))
|
||
|
|
||
|
try:
|
||
|
cols_present = bool(columns)
|
||
|
except TypeError as err:
|
||
|
util.raise_(
|
||
|
exc.ArgumentError(
|
||
|
"select() construct created in legacy mode, i.e. with "
|
||
|
"keyword arguments, must provide the columns argument as "
|
||
|
"a Python list or other iterable.",
|
||
|
code="c9ae",
|
||
|
),
|
||
|
from_=err,
|
||
|
)
|
||
|
|
||
|
if cols_present:
|
||
|
self._raw_columns = [
|
||
|
coercions.expect(
|
||
|
roles.ColumnsClauseRole, c, apply_propagate_attrs=self
|
||
|
)
|
||
|
for c in columns
|
||
|
]
|
||
|
else:
|
||
|
self._raw_columns = []
|
||
|
|
||
|
if whereclause is not None:
|
||
|
self.where.non_generative(self, whereclause)
|
||
|
|
||
|
if having is not None:
|
||
|
self.having.non_generative(self, having)
|
||
|
|
||
|
if prefixes:
|
||
|
self._setup_prefixes(prefixes)
|
||
|
|
||
|
if suffixes:
|
||
|
self._setup_suffixes(suffixes)
|
||
|
|
||
|
GenerativeSelect.__init__(self, **kwargs)
|
||
|
return self
|
||
|
|
||
|
@classmethod
|
||
|
def _create_future_select(cls, *entities):
|
||
|
r"""Construct a new :class:`_expression.Select` using the 2.
|
||
|
x style API.
|
||
|
|
||
|
.. versionadded:: 1.4 - The :func:`_sql.select` function now accepts
|
||
|
column arguments positionally. The top-level :func:`_sql.select`
|
||
|
function will automatically use the 1.x or 2.x style API based on
|
||
|
the incoming arguments; using :func:`_future.select` from the
|
||
|
``sqlalchemy.future`` module will enforce that only the 2.x style
|
||
|
constructor is used.
|
||
|
|
||
|
Similar functionality is also available via the
|
||
|
:meth:`_expression.FromClause.select` method on any
|
||
|
:class:`_expression.FromClause`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`coretutorial_selecting` - Core Tutorial description of
|
||
|
:func:`_expression.select`.
|
||
|
|
||
|
:param \*entities:
|
||
|
Entities to SELECT from. For Core usage, this is typically a series
|
||
|
of :class:`_expression.ColumnElement` and / or
|
||
|
:class:`_expression.FromClause`
|
||
|
objects which will form the columns clause of the resulting
|
||
|
statement. For those objects that are instances of
|
||
|
:class:`_expression.FromClause` (typically :class:`_schema.Table`
|
||
|
or :class:`_expression.Alias`
|
||
|
objects), the :attr:`_expression.FromClause.c`
|
||
|
collection is extracted
|
||
|
to form a collection of :class:`_expression.ColumnElement` objects.
|
||
|
|
||
|
This parameter will also accept :class:`_expression.TextClause`
|
||
|
constructs as
|
||
|
given, as well as ORM-mapped classes.
|
||
|
|
||
|
"""
|
||
|
|
||
|
self = cls.__new__(cls)
|
||
|
self._raw_columns = [
|
||
|
coercions.expect(
|
||
|
roles.ColumnsClauseRole, ent, apply_propagate_attrs=self
|
||
|
)
|
||
|
for ent in entities
|
||
|
]
|
||
|
|
||
|
GenerativeSelect.__init__(self)
|
||
|
|
||
|
return self
|
||
|
|
||
|
_create_select = _create_future_select
|
||
|
|
||
|
@classmethod
|
||
|
def _create_raw_select(cls, **kw):
|
||
|
"""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
|
||
|
|
||
|
@classmethod
|
||
|
def _create(cls, *args, **kw):
|
||
|
r"""Create a :class:`.Select` using either the 1.x or 2.0 constructor
|
||
|
style.
|
||
|
|
||
|
For the legacy calling style, see :meth:`.Select.create_legacy_select`.
|
||
|
If the first argument passed is a Python sequence or if keyword
|
||
|
arguments are present, this style is used.
|
||
|
|
||
|
.. versionadded:: 2.0 - the :func:`_future.select` construct is
|
||
|
the same construct as the one returned by
|
||
|
:func:`_expression.select`, except that the function only
|
||
|
accepts the "columns clause" entities up front; the rest of the
|
||
|
state of the SELECT should be built up using generative methods.
|
||
|
|
||
|
Similar functionality is also available via the
|
||
|
:meth:`_expression.FromClause.select` method on any
|
||
|
:class:`_expression.FromClause`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`coretutorial_selecting` - Core Tutorial description of
|
||
|
:func:`_expression.select`.
|
||
|
|
||
|
:param \*entities:
|
||
|
Entities to SELECT from. For Core usage, this is typically a series
|
||
|
of :class:`_expression.ColumnElement` and / or
|
||
|
:class:`_expression.FromClause`
|
||
|
objects which will form the columns clause of the resulting
|
||
|
statement. For those objects that are instances of
|
||
|
:class:`_expression.FromClause` (typically :class:`_schema.Table`
|
||
|
or :class:`_expression.Alias`
|
||
|
objects), the :attr:`_expression.FromClause.c`
|
||
|
collection is extracted
|
||
|
to form a collection of :class:`_expression.ColumnElement` objects.
|
||
|
|
||
|
This parameter will also accept :class:`_expression.TextClause`
|
||
|
constructs as given, as well as ORM-mapped classes.
|
||
|
|
||
|
"""
|
||
|
if (
|
||
|
args
|
||
|
and (
|
||
|
isinstance(args[0], list)
|
||
|
or (
|
||
|
hasattr(args[0], "__iter__")
|
||
|
and not isinstance(
|
||
|
args[0], util.string_types + (ClauseElement,)
|
||
|
)
|
||
|
and inspect(args[0], raiseerr=False) is None
|
||
|
and not hasattr(args[0], "__clause_element__")
|
||
|
)
|
||
|
)
|
||
|
) or kw:
|
||
|
return cls.create_legacy_select(*args, **kw)
|
||
|
else:
|
||
|
return cls._create_future_select(*args)
|
||
|
|
||
|
def __init__(self):
|
||
|
raise NotImplementedError()
|
||
|
|
||
|
def _scalar_type(self):
|
||
|
elem = self._raw_columns[0]
|
||
|
cols = list(elem._select_iterable)
|
||
|
return cols[0].type
|
||
|
|
||
|
def filter(self, *criteria):
|
||
|
"""A synonym for the :meth:`_future.Select.where` method."""
|
||
|
|
||
|
return self.where(*criteria)
|
||
|
|
||
|
def _filter_by_zero(self):
|
||
|
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]
|
||
|
|
||
|
def filter_by(self, **kwargs):
|
||
|
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):
|
||
|
"""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):
|
||
|
"""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, onclause=None, isouter=False, full=False):
|
||
|
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::
|
||
|
|
||
|
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
|
||
|
target = coercions.expect(
|
||
|
roles.JoinTargetRole, target, apply_propagate_attrs=self
|
||
|
)
|
||
|
if onclause is not None:
|
||
|
onclause = coercions.expect(roles.OnClauseRole, onclause)
|
||
|
self._setup_joins += (
|
||
|
(target, onclause, None, {"isouter": isouter, "full": full}),
|
||
|
)
|
||
|
|
||
|
def outerjoin_from(self, from_, target, onclause=None, full=False):
|
||
|
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_, target, onclause=None, isouter=False, full=False
|
||
|
):
|
||
|
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::
|
||
|
|
||
|
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
|
||
|
)
|
||
|
target = coercions.expect(
|
||
|
roles.JoinTargetRole, target, apply_propagate_attrs=self
|
||
|
)
|
||
|
if onclause is not None:
|
||
|
onclause = coercions.expect(roles.OnClauseRole, onclause)
|
||
|
|
||
|
self._setup_joins += (
|
||
|
(target, onclause, from_, {"isouter": isouter, "full": full}),
|
||
|
)
|
||
|
|
||
|
def outerjoin(self, target, onclause=None, full=False):
|
||
|
"""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):
|
||
|
"""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`
|
||
|
|
||
|
"""
|
||
|
return self._compile_state_factory(self, None)._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):
|
||
|
"""Return the displayed list of :class:`_expression.FromClause`
|
||
|
elements.
|
||
|
|
||
|
|
||
|
"""
|
||
|
return self.get_final_froms()
|
||
|
|
||
|
@property
|
||
|
def columns_clause_froms(self):
|
||
|
"""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):
|
||
|
"""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):
|
||
|
if 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=_clone, **kw):
|
||
|
# 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 = (
|
||
|
set(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, **kw):
|
||
|
if isinstance(obj, ColumnClause) and obj.table in new_froms:
|
||
|
newelem = new_froms[obj.table].corresponding_column(obj)
|
||
|
return newelem
|
||
|
|
||
|
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(Select, self)._copy_internals(
|
||
|
clone=clone, omit_attrs=("_from_obj",), **kw
|
||
|
)
|
||
|
|
||
|
self._reset_memoizations()
|
||
|
|
||
|
def get_children(self, **kwargs):
|
||
|
return itertools.chain(
|
||
|
super(Select, self).get_children(
|
||
|
omit_attrs=["_from_obj", "_correlate", "_correlate_except"]
|
||
|
),
|
||
|
self._iterate_from_elements(),
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def add_columns(self, *columns):
|
||
|
"""Return a new :func:`_expression.select` construct with
|
||
|
the given column expressions added to its columns clause.
|
||
|
|
||
|
E.g.::
|
||
|
|
||
|
my_select = my_select.add_columns(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.
|
||
|
|
||
|
"""
|
||
|
self._reset_memoizations()
|
||
|
|
||
|
self._raw_columns = self._raw_columns + [
|
||
|
coercions.expect(
|
||
|
roles.ColumnsClauseRole, column, apply_propagate_attrs=self
|
||
|
)
|
||
|
for column in columns
|
||
|
]
|
||
|
|
||
|
def _set_entities(self, entities):
|
||
|
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):
|
||
|
"""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=True):
|
||
|
"""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.
|
||
|
|
||
|
"""
|
||
|
return self.with_only_columns(
|
||
|
*util.preloaded.sql_util.reduce_columns(
|
||
|
self._all_selected_columns,
|
||
|
only_synonyms=only_synonyms,
|
||
|
*(self._where_criteria + self._from_obj)
|
||
|
)
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def with_only_columns(self, *columns, **kw):
|
||
|
r"""Return a new :func:`_expression.select` construct with its columns
|
||
|
clause replaced with the given columns.
|
||
|
|
||
|
By default, this method is exactly equivalent to as if the original
|
||
|
:func:`_expression.select` had been called with the given columns
|
||
|
clause. 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 \*columns: column expressions to be used.
|
||
|
|
||
|
.. versionchanged:: 1.4 the :meth:`_sql.Select.with_only_columns`
|
||
|
method accepts the list of column expressions positionally;
|
||
|
passing the expressions as a list is deprecated.
|
||
|
|
||
|
: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
|
||
|
|
||
|
# memoizations should be cleared here as of
|
||
|
# I95c560ffcbfa30b26644999412fb6a385125f663 , asserting this
|
||
|
# is the case for now.
|
||
|
self._assert_no_memoizations()
|
||
|
|
||
|
maintain_column_froms = kw.pop("maintain_column_froms", False)
|
||
|
if kw:
|
||
|
raise TypeError("unknown parameters: %s" % (", ".join(kw),))
|
||
|
|
||
|
if maintain_column_froms:
|
||
|
self.select_from.non_generative(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(
|
||
|
"columns", "Select.with_only_columns", columns
|
||
|
)
|
||
|
]
|
||
|
|
||
|
@property
|
||
|
def whereclause(self):
|
||
|
"""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):
|
||
|
"""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 = coercions.expect(roles.WhereHavingRole, criterion)
|
||
|
self._where_criteria += (where_criteria,)
|
||
|
|
||
|
@_generative
|
||
|
def having(self, having):
|
||
|
"""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.
|
||
|
|
||
|
"""
|
||
|
self._having_criteria += (
|
||
|
coercions.expect(roles.WhereHavingRole, having),
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def distinct(self, *expr):
|
||
|
r"""Return a new :func:`_expression.select` construct which
|
||
|
will apply DISTINCT to its columns clause.
|
||
|
|
||
|
:param \*expr: optional column expressions. When present,
|
||
|
the PostgreSQL dialect will render a ``DISTINCT ON (<expressions>>)``
|
||
|
construct.
|
||
|
|
||
|
.. 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) for e in expr
|
||
|
)
|
||
|
else:
|
||
|
self._distinct = True
|
||
|
|
||
|
@_generative
|
||
|
def select_from(self, *froms):
|
||
|
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
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def correlate(self, *fromclauses):
|
||
|
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: 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.Select.correlate_except`
|
||
|
|
||
|
:ref:`tutorial_scalar_subquery`
|
||
|
|
||
|
"""
|
||
|
|
||
|
self._auto_correlate = False
|
||
|
if fromclauses and fromclauses[0] in {None, False}:
|
||
|
self._correlate = ()
|
||
|
else:
|
||
|
self._correlate = self._correlate + tuple(
|
||
|
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
|
||
|
)
|
||
|
|
||
|
@_generative
|
||
|
def correlate_except(self, *fromclauses):
|
||
|
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, 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 fromclauses and fromclauses[0] in {None, False}:
|
||
|
self._correlate_except = ()
|
||
|
else:
|
||
|
self._correlate_except = (self._correlate_except or ()) + tuple(
|
||
|
coercions.expect(roles.FromClauseRole, f) for f in fromclauses
|
||
|
)
|
||
|
|
||
|
@HasMemoized.memoized_attribute
|
||
|
def selected_columns(self):
|
||
|
"""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.
|
||
|
|
||
|
.. 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 = SelectState._column_naming_convention(self._label_style)
|
||
|
|
||
|
return ColumnCollection(
|
||
|
[
|
||
|
(conv(c), c)
|
||
|
for c in self._all_selected_columns
|
||
|
if not c._is_text_clause
|
||
|
]
|
||
|
).as_immutable()
|
||
|
|
||
|
@HasMemoized.memoized_attribute
|
||
|
def _all_selected_columns(self):
|
||
|
meth = SelectState.get_plugin_class(self).all_selected_columns
|
||
|
return list(meth(self))
|
||
|
|
||
|
def _ensure_disambiguated_names(self):
|
||
|
if self._label_style is LABEL_STYLE_NONE:
|
||
|
self = self.set_label_style(LABEL_STYLE_DISAMBIGUATE_ONLY)
|
||
|
return self
|
||
|
|
||
|
def _generate_columns_plus_names(self, anon_for_dupe_key):
|
||
|
"""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.
|
||
|
|
||
|
"""
|
||
|
cols = self._all_selected_columns
|
||
|
|
||
|
key_naming_convention = SelectState._column_naming_convention(
|
||
|
self._label_style
|
||
|
)
|
||
|
|
||
|
names = {}
|
||
|
|
||
|
result = []
|
||
|
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:
|
||
|
effective_name = required_label_name = None
|
||
|
fallback_label_name = c._non_anon_label or c._anon_name_label
|
||
|
else:
|
||
|
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 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(
|
||
|
(
|
||
|
# string label name, if non-None, must be rendered as a
|
||
|
# label, i.e. "AS <name>"
|
||
|
required_label_name,
|
||
|
# 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
|
||
|
key_naming_convention(c),
|
||
|
# 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
|
||
|
fallback_label_name,
|
||
|
# the ColumnElement itself
|
||
|
c,
|
||
|
# True if this is a duplicate of a previous column
|
||
|
# in the list of columns
|
||
|
repeated,
|
||
|
)
|
||
|
)
|
||
|
|
||
|
return result
|
||
|
|
||
|
def _generate_fromclause_column_proxies(self, subquery):
|
||
|
"""Generate column proxies to place in the exported ``.c``
|
||
|
collection of a subquery."""
|
||
|
|
||
|
prox = [
|
||
|
c._make_proxy(
|
||
|
subquery,
|
||
|
key=proxy_key,
|
||
|
name=required_label_name,
|
||
|
name_is_truncatable=True,
|
||
|
)
|
||
|
for (
|
||
|
required_label_name,
|
||
|
proxy_key,
|
||
|
fallback_label_name,
|
||
|
c,
|
||
|
repeated,
|
||
|
) in (self._generate_columns_plus_names(False))
|
||
|
if not c._is_text_clause
|
||
|
]
|
||
|
|
||
|
subquery._columns._populate_separate_keys(prox)
|
||
|
|
||
|
def _needs_parens_for_grouping(self):
|
||
|
return self._has_row_limiting_clause or bool(
|
||
|
self._order_by_clause.clauses
|
||
|
)
|
||
|
|
||
|
def self_group(self, against=None):
|
||
|
"""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, **kwargs):
|
||
|
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, **kwargs)
|
||
|
|
||
|
def union_all(self, *other, **kwargs):
|
||
|
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, **kwargs)
|
||
|
|
||
|
def except_(self, *other, **kwargs):
|
||
|
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.
|
||
|
|
||
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
||
|
for the newly created :class:`_sql.CompoundSelect` object.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect._create_except(self, *other, **kwargs)
|
||
|
|
||
|
def except_all(self, *other, **kwargs):
|
||
|
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.
|
||
|
|
||
|
:param \**kwargs: keyword arguments are forwarded to the constructor
|
||
|
for the newly created :class:`_sql.CompoundSelect` object.
|
||
|
|
||
|
"""
|
||
|
return CompoundSelect._create_except_all(self, *other, **kwargs)
|
||
|
|
||
|
def intersect(self, *other, **kwargs):
|
||
|
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, **kwargs)
|
||
|
|
||
|
def intersect_all(self, *other, **kwargs):
|
||
|
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, **kwargs)
|
||
|
|
||
|
@property
|
||
|
@util.deprecated_20(
|
||
|
":attr:`.Executable.bind`",
|
||
|
alternative="Bound metadata is being removed as of SQLAlchemy 2.0.",
|
||
|
enable_warnings=False,
|
||
|
)
|
||
|
def bind(self):
|
||
|
"""Returns the :class:`_engine.Engine` or :class:`_engine.Connection`
|
||
|
to which this :class:`.Executable` is bound, or None if none found.
|
||
|
|
||
|
"""
|
||
|
if self._bind:
|
||
|
return self._bind
|
||
|
|
||
|
for item in self._iterate_from_elements():
|
||
|
if item._is_subquery and item.element is self:
|
||
|
raise exc.InvalidRequestError(
|
||
|
"select() construct refers to itself as a FROM"
|
||
|
)
|
||
|
|
||
|
e = item.bind
|
||
|
if e:
|
||
|
self._bind = e
|
||
|
return e
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
for c in self._raw_columns:
|
||
|
e = c.bind
|
||
|
if e:
|
||
|
self._bind = e
|
||
|
return e
|
||
|
|
||
|
@bind.setter
|
||
|
def bind(self, bind):
|
||
|
self._bind = bind
|
||
|
|
||
|
|
||
|
class ScalarSelect(roles.InElementRole, Generative, Grouping):
|
||
|
"""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
|
||
|
|
||
|
"""
|
||
|
|
||
|
_from_objects = []
|
||
|
_is_from_container = True
|
||
|
_is_implicitly_boolean = False
|
||
|
inherit_cache = True
|
||
|
|
||
|
def __init__(self, element):
|
||
|
self.element = element
|
||
|
self.type = element._scalar_type()
|
||
|
|
||
|
@property
|
||
|
def columns(self):
|
||
|
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):
|
||
|
"""Apply a WHERE clause to the SELECT statement referred to
|
||
|
by this :class:`_expression.ScalarSelect`.
|
||
|
|
||
|
"""
|
||
|
self.element = self.element.where(crit)
|
||
|
|
||
|
def self_group(self, **kwargs):
|
||
|
return self
|
||
|
|
||
|
@_generative
|
||
|
def correlate(self, *fromclauses):
|
||
|
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 = self.element.correlate(*fromclauses)
|
||
|
|
||
|
@_generative
|
||
|
def correlate_except(self, *fromclauses):
|
||
|
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 = self.element.correlate_except(*fromclauses)
|
||
|
|
||
|
|
||
|
class Exists(UnaryExpression):
|
||
|
"""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`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
_from_objects = []
|
||
|
inherit_cache = True
|
||
|
|
||
|
def __init__(self, *args, **kwargs):
|
||
|
"""Construct a new :class:`_expression.Exists` construct.
|
||
|
|
||
|
The :func:`_sql.exists` can be invoked by itself to produce an
|
||
|
:class:`_sql.Exists` construct, which will accept simple WHERE
|
||
|
criteria::
|
||
|
|
||
|
exists_criteria = exists().where(table1.c.col1 == table2.c.col2)
|
||
|
|
||
|
However, for greater flexibility in constructing the SELECT, an
|
||
|
existing :class:`_sql.Select` construct may be converted to an
|
||
|
:class:`_sql.Exists`, most conveniently by making use of the
|
||
|
:meth:`_sql.SelectBase.exists` method::
|
||
|
|
||
|
exists_criteria = (
|
||
|
select(table2.c.col2).
|
||
|
where(table1.c.col1 == table2.c.col2).
|
||
|
exists()
|
||
|
)
|
||
|
|
||
|
The EXISTS criteria is then used inside of an enclosing SELECT::
|
||
|
|
||
|
stmt = select(table1.c.col1).where(exists_criteria)
|
||
|
|
||
|
The above statement will then be of the form::
|
||
|
|
||
|
SELECT col1 FROM table1 WHERE EXISTS
|
||
|
(SELECT table2.col2 FROM table2 WHERE table2.col2 = table1.col1)
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`tutorial_exists` - in the :term:`2.0 style` tutorial.
|
||
|
|
||
|
:meth:`_sql.SelectBase.exists` - method to transform a ``SELECT`` to an
|
||
|
``EXISTS`` clause.
|
||
|
|
||
|
""" # noqa: E501
|
||
|
if args and isinstance(args[0], (SelectBase, ScalarSelect)):
|
||
|
s = args[0]
|
||
|
else:
|
||
|
if not args:
|
||
|
args = (literal_column("*"),)
|
||
|
s = Select._create(*args, **kwargs).scalar_subquery()
|
||
|
|
||
|
UnaryExpression.__init__(
|
||
|
self,
|
||
|
s,
|
||
|
operator=operators.exists,
|
||
|
type_=type_api.BOOLEANTYPE,
|
||
|
wraps_column_expression=True,
|
||
|
)
|
||
|
|
||
|
def _regroup(self, fn):
|
||
|
element = self.element._ungroup()
|
||
|
element = fn(element)
|
||
|
return element.self_group(against=operators.exists)
|
||
|
|
||
|
@util.deprecated_params(
|
||
|
whereclause=(
|
||
|
"2.0",
|
||
|
"The :paramref:`_sql.Exists.select().whereclause` parameter "
|
||
|
"is deprecated and will be removed in version 2.0. "
|
||
|
"Please make use "
|
||
|
"of the :meth:`.Select.where` "
|
||
|
"method to add WHERE criteria to the SELECT statement.",
|
||
|
),
|
||
|
kwargs=(
|
||
|
"2.0",
|
||
|
"The :meth:`_sql.Exists.select` method will no longer accept "
|
||
|
"keyword arguments in version 2.0. "
|
||
|
"Please use generative methods from the "
|
||
|
":class:`_sql.Select` construct in order to apply additional "
|
||
|
"modifications.",
|
||
|
),
|
||
|
)
|
||
|
def select(self, whereclause=None, **kwargs):
|
||
|
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::
|
||
|
|
||
|
SELECT EXISTS (SELECT id FROM some_table WHERE some_table = :param) AS anon_1
|
||
|
|
||
|
:param whereclause: a WHERE clause, equivalent to calling the
|
||
|
:meth:`_sql.Select.where` method.
|
||
|
|
||
|
:param **kwargs: additional keyword arguments are passed to the
|
||
|
legacy constructor for :class:`_sql.Select` described at
|
||
|
:meth:`_sql.Select.create_legacy_select`.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:func:`_expression.select` - general purpose
|
||
|
method which allows for arbitrary column lists.
|
||
|
|
||
|
""" # noqa
|
||
|
|
||
|
if whereclause is not None:
|
||
|
kwargs["whereclause"] = whereclause
|
||
|
return Select._create_select_from_fromclause(self, [self], **kwargs)
|
||
|
|
||
|
def correlate(self, *fromclause):
|
||
|
"""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(*fromclause)
|
||
|
)
|
||
|
return e
|
||
|
|
||
|
def correlate_except(self, *fromclause):
|
||
|
"""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(*fromclause)
|
||
|
)
|
||
|
return e
|
||
|
|
||
|
def select_from(self, *froms):
|
||
|
"""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):
|
||
|
"""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):
|
||
|
"""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 = [
|
||
|
("element", InternalTraversal.dp_clauseelement),
|
||
|
("column_args", InternalTraversal.dp_clauseelement_list),
|
||
|
] + SupportsCloneAnnotations._clone_annotations_traverse_internals
|
||
|
|
||
|
_is_textual = True
|
||
|
|
||
|
is_text = True
|
||
|
is_select = True
|
||
|
|
||
|
def __init__(self, text, columns, positional=False):
|
||
|
self.element = text
|
||
|
# convert for ORM attributes->columns, etc
|
||
|
self.column_args = [
|
||
|
coercions.expect(roles.ColumnsClauseRole, c) for c in columns
|
||
|
]
|
||
|
self.positional = positional
|
||
|
|
||
|
@HasMemoized.memoized_attribute
|
||
|
def selected_columns(self):
|
||
|
"""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_immutable()
|
||
|
|
||
|
@property
|
||
|
def _all_selected_columns(self):
|
||
|
return self.column_args
|
||
|
|
||
|
def _set_label_style(self, style):
|
||
|
return self
|
||
|
|
||
|
def _ensure_disambiguated_names(self):
|
||
|
return self
|
||
|
|
||
|
@property
|
||
|
def _bind(self):
|
||
|
return self.element._bind
|
||
|
|
||
|
@_generative
|
||
|
def bindparams(self, *binds, **bind_as_values):
|
||
|
self.element = self.element.bindparams(*binds, **bind_as_values)
|
||
|
|
||
|
def _generate_fromclause_column_proxies(self, fromclause):
|
||
|
fromclause._columns._populate_separate_keys(
|
||
|
c._make_proxy(fromclause) for c in self.column_args
|
||
|
)
|
||
|
|
||
|
def _scalar_type(self):
|
||
|
return self.column_args[0].type
|
||
|
|
||
|
|
||
|
TextAsFrom = TextualSelect
|
||
|
"""Backwards compatibility with the previous name"""
|
||
|
|
||
|
|
||
|
class AnnotatedFromClause(Annotated):
|
||
|
def __init__(self, element, values):
|
||
|
# force FromClause to generate their internal
|
||
|
# collections into __dict__
|
||
|
element.c
|
||
|
Annotated.__init__(self, element, values)
|