Выбор строк с помощью ядра или ORM¶
Для Core и ORM функция select()
генерирует конструкцию Select
, которая используется для всех запросов SELECT. Переданный методам типа Connection.execute()
в Core и Session.execute()
в ORM, оператор SELECT выдается в текущей транзакции, а строки результата доступны через возвращаемый объект Result
.
ORM Readers - содержание этой статьи одинаково хорошо применимо как к Core, так и к ORM, и здесь упоминаются основные варианты использования ORM. Однако существует также много других возможностей, специфичных для ORM; они документированы в Руководство по составлению запросов ORM.
Конструкция SQL-выражения select()¶
Конструкция select()
строит высказывание так же, как и insert()
, используя подход generative, при котором каждый метод наращивает состояние объекта. Как и другие конструкции SQL, она может быть построена на месте:
>>> from sqlalchemy import select
>>> stmt = select(user_table).where(user_table.c.name == "spongebob")
>>> print(stmt)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1
Также, как и все остальные конструкции SQL на уровне оператора, для фактического выполнения оператора мы передаем его методу выполнения. Поскольку оператор SELECT возвращает строки, мы всегда можем итерировать объект результата, чтобы получить Row
объектов обратно:
>>> with engine.connect() as conn:
... for row in conn.execute(stmt):
... print(row)
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('spongebob',)
(1, 'spongebob', 'Spongebob Squarepants')
ROLLBACK
При использовании ORM, особенно с конструкцией select()
, составленной из сущностей ORM, мы захотим выполнить ее, используя метод Session.execute()
на Session
; используя этот подход, мы продолжаем получать объекты Row
из результата, однако эти строки теперь способны включать полные сущности, такие как экземпляры класса User
, как отдельные элементы в каждой строке:
>>> stmt = select(User).where(User.name == "spongebob")
>>> with Session(engine) as session:
... for row in session.execute(stmt):
... print(row)
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('spongebob',)
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
ROLLBACK
В следующих разделах конструкция SELECT будет рассмотрена более подробно.
Настройка COLUMNS и предложения FROM¶
Функция select()
принимает позиционные элементы, представляющие любое количество выражений Column
и/или Table
, а также широкий спектр совместимых объектов, которые преобразуются в список SQL-выражений, подлежащих ВЫБОРУ, которые будут возвращены в качестве столбцов в наборе результатов. Эти элементы также служат в более простых случаях для создания предложения FROM, которое выводится из переданных столбцов и табличных выражений:
>>> print(select(user_table))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
Для SELECT из отдельных столбцов, используя подход Core, объекты Column
доступны из аксессора Table.c
и могут быть отправлены напрямую; предложение FROM будет выведено как набор всех Table
и других FromClause
объектов, которые представлены этими столбцами:
>>> print(select(user_table.c.name, user_table.c.fullname))
SELECT user_account.name, user_account.fullname
FROM user_account
Выбор сущностей и столбцов ORM¶
ORM-сущности, такие как наш класс User
, а также атрибуты, отображаемые на столбцы, такие как User.name
, также участвуют в системе SQL Expression Language, представляющей таблицы и столбцы. Ниже показан пример выбора из сущности User
, который в конечном итоге отображается так же, как если бы мы использовали непосредственно user_table
:
>>> print(select(User))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
При выполнении оператора, подобного приведенному выше, с использованием метода ORM Session.execute()
, существует важное различие, когда мы выбираем из полной сущности, такой как User
, в отличие от user_table
, которое заключается в том, что сама сущность возвращается как один элемент в каждой строке. То есть, когда мы получаем строки из приведенного выше оператора, поскольку в списке объектов для выборки есть только сущность User
, мы получаем обратно объекты Row
, имеющие только один элемент, которые содержат экземпляры класса User
:
>>> row = session.execute(select(User)).first()
BEGIN...
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
[...] ()
>>> row
(User(id=1, name='spongebob', fullname='Spongebob Squarepants'),)
Приведенный выше Row
имеет только один элемент, представляющий сущность User
:
>>> row[0]
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
Настоятельно рекомендуемый удобный метод достижения того же результата, что и выше, заключается в использовании метода Session.scalars()
для непосредственного выполнения оператора; этот метод вернет объект ScalarResult
, который доставит первый «столбец» каждой строки сразу, в данном случае экземпляры класса User
:
>>> user = session.scalars(select(User)).first()
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
[...] ()
>>> user
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
В качестве альтернативы, мы можем выбрать отдельные столбцы сущности ORM как отдельные элементы в строках результатов, используя атрибуты, связанные с классом; когда они передаются в такую конструкцию, как select()
, они преобразуются в Column
или другое выражение SQL, представленное каждым атрибутом:
>>> print(select(User.name, User.fullname))
SELECT user_account.name, user_account.fullname
FROM user_account
Когда мы вызываем этот оператор с помощью Session.execute()
, мы теперь получаем строки, которые имеют отдельные элементы на каждое значение, каждый из которых соответствует отдельному столбцу или другому выражению SQL:
>>> row = session.execute(select(User.name, User.fullname)).first()
SELECT user_account.name, user_account.fullname
FROM user_account
[...] ()
>>> row
('spongebob', 'Spongebob Squarepants')
Подходы также можно смешивать, как показано ниже, где мы ВЫБИРАЕМ атрибут name
сущности User
в качестве первого элемента строки, и объединяем его с полными сущностями Address
во втором элементе:
>>> session.execute(
... select(User.name, Address).where(User.id == Address.user_id).order_by(Address.id)
... ).all()
SELECT user_account.name, address.id, address.email_address, address.user_id
FROM user_account, address
WHERE user_account.id = address.user_id ORDER BY address.id
[...] ()
[('spongebob', Address(id=1, email_address='spongebob@sqlalchemy.org')),
('sandy', Address(id=2, email_address='sandy@sqlalchemy.org')),
('sandy', Address(id=3, email_address='sandy@squirrelpower.org'))]
Подходы к выбору сущностей и столбцов ORM, а также общие методы преобразования строк обсуждаются далее в Выбор сущностей и атрибутов ORM.
Выборка из меченых выражений SQL¶
Метод ColumnElement.label()
, а также одноименный метод, доступный в атрибутах ORM, предоставляет SQL-метку столбца или выражения, позволяя ему иметь определенное имя в наборе результатов. Это может быть полезно при обращении к произвольным SQL-выражениям в строке результата по имени:
>>> from sqlalchemy import func, cast
>>> stmt = select(
... ("Username: " + user_table.c.name).label("username"),
... ).order_by(user_table.c.name)
>>> with engine.connect() as conn:
... for row in conn.execute(stmt):
... print(f"{row.username}")
BEGIN (implicit)
SELECT ? || user_account.name AS username
FROM user_account ORDER BY user_account.name
[...] ('Username: ',)
Username: patrick
Username: sandy
Username: spongebob
ROLLBACK
См.также
Упорядочивание или группировка по метке - имена меток, которые мы создаем, могут также упоминаться в пункте ORDER BY или GROUP BY в Select
.
Выбор с помощью текстовых выражений столбцов¶
Когда мы создаем объект Select
с помощью функции select()
, мы обычно передаем ему серию объектов Table
и Column
, которые были определены с помощью table metadata, или при использовании ORM мы можем передавать ORM-mapped атрибуты, представляющие столбцы таблицы. Однако иногда возникает необходимость в производстве произвольных блоков SQL внутри операторов, таких как константные строковые выражения или просто произвольный SQL, который быстрее написать буквально.
Конструкция text()
, введенная в Работа с транзакциями и DBAPI, может быть встроена в конструкцию Select
непосредственно, как показано ниже, где мы создаем жестко закодированный строковый литерал 'some label'
и встраиваем его в оператор SELECT:
>>> from sqlalchemy import text
>>> stmt = select(text("'some phrase'"), user_table.c.name).order_by(user_table.c.name)
>>> with engine.connect() as conn:
... print(conn.execute(stmt).all())
BEGIN (implicit)
SELECT 'some phrase', user_account.name
FROM user_account ORDER BY user_account.name
[generated in ...] ()
[('some phrase', 'patrick'), ('some phrase', 'sandy'), ('some phrase', 'spongebob')]
ROLLBACK
Хотя конструкцию text()
можно использовать в большинстве мест для введения буквальных фраз SQL, чаще всего мы имеем дело с текстовыми единицами, каждая из которых представляет собой отдельное выражение столбца. В этом случае мы можем получить больше функциональности от нашего текстового фрагмента, используя вместо него конструкцию literal_column()
. Этот объект похож на text()
за исключением того, что вместо того, чтобы представлять произвольный SQL в любой форме, он явно представляет один «столбец» и может быть затем помечен и упомянут в подзапросах и других выражениях:
>>> from sqlalchemy import literal_column
>>> stmt = select(literal_column("'some phrase'").label("p"), user_table.c.name).order_by(
... user_table.c.name
... )
>>> with engine.connect() as conn:
... for row in conn.execute(stmt):
... print(f"{row.p}, {row.name}")
BEGIN (implicit)
SELECT 'some phrase' AS p, user_account.name
FROM user_account ORDER BY user_account.name
[generated in ...] ()
some phrase, patrick
some phrase, sandy
some phrase, spongebob
ROLLBACK
Обратите внимание, что в обоих случаях при использовании text()
или literal_column()
мы пишем синтаксическое выражение SQL, а не буквальное значение. Поэтому мы должны включить любые кавычки или синтаксис, необходимые для того SQL, который мы хотим видеть в визуализации.
Предложение ГДЕ¶
SQLAlchemy позволяет нам составлять SQL-выражения, такие как name = 'squidward'
или user_id > 10
, используя стандартные операторы Python в сочетании с Column
и подобными объектами. Для булевых выражений большинство операторов Python, таких как ==
, !=
, <
, >=
и т.д. генерируют новые объекты SQL Expression, а не простые булевые значения True
/False
:
>>> print(user_table.c.name == "squidward")
user_account.name = :name_1
>>> print(address_table.c.user_id > 10)
address.user_id > :user_id_1
Мы можем использовать выражения, подобные этим, для создания предложения WHERE, передавая полученные объекты в метод Select.where()
:
>>> print(select(user_table).where(user_table.c.name == "squidward"))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1
Для получения нескольких выражений, объединенных методом AND, метод Select.where()
может быть вызван любое количество раз:
>>> print(
... select(address_table.c.email_address)
... .where(user_table.c.name == "squidward")
... .where(address_table.c.user_id == user_table.c.id)
... )
SELECT address.email_address
FROM address, user_account
WHERE user_account.name = :name_1 AND address.user_id = user_account.id
Один вызов Select.where()
также принимает несколько выражений с тем же эффектом:
>>> print(
... select(address_table.c.email_address).where(
... user_table.c.name == "squidward", address_table.c.user_id == user_table.c.id
... )
... )
SELECT address.email_address
FROM address, user_account
WHERE user_account.name = :name_1 AND address.user_id = user_account.id
Конъюнкции «И» и «ИЛИ» доступны непосредственно с помощью функций and_()
и or_()
, проиллюстрированных ниже в терминах сущностей ORM:
>>> from sqlalchemy import and_, or_
>>> print(
... select(Address.email_address).where(
... and_(
... or_(User.name == "squidward", User.name == "sandy"),
... Address.user_id == User.id,
... )
... )
... )
SELECT address.email_address
FROM address, user_account
WHERE (user_account.name = :name_1 OR user_account.name = :name_2)
AND address.user_id = user_account.id
Для простого сравнения «равенства» с одной сущностью существует также популярный метод Select.filter_by()
, который принимает аргументы в виде ключевых слов, соответствующих ключам столбцов или именам атрибутов ORM. Он будет фильтровать по крайнему левому предложению FROM или последней сущности, к которой присоединяется:
>>> print(select(User).filter_by(name="spongebob", fullname="Spongebob Squarepants"))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1 AND user_account.fullname = :fullname_1
См.также
Справочник оператора - описание большинства функций операторов SQL в SQLAlchemy
Явные предложения FROM и JOIN¶
Как упоминалось ранее, предложение FROM обычно зависит от выражений, которые мы задаем в предложении columns, а также от других элементов Select
.
Если мы задаем один столбец из определенного Table
в предложении COLUMNS, он помещает этот Table
также в предложении FROM:
>>> print(select(user_table.c.name))
SELECT user_account.name
FROM user_account
Если мы поместим столбцы из двух таблиц, то получим предложение FROM, разделенное запятыми:
>>> print(select(user_table.c.name, address_table.c.email_address))
SELECT user_account.name, address.email_address
FROM user_account, address
Для того чтобы соединить эти две таблицы вместе, мы обычно используем один из двух методов Select
. Первый - это метод Select.join_from()
, который позволяет нам явно указать левую и правую сторону JOIN:
>>> print(
... select(user_table.c.name, address_table.c.email_address).join_from(
... user_table, address_table
... )
... )
SELECT user_account.name, address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
Другим методом является метод Select.join()
, который указывает только правую сторону JOIN, левая сторона выводится:
>>> print(select(user_table.c.name, address_table.c.email_address).join(address_table))
SELECT user_account.name, address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
У нас также есть возможность добавлять элементы в предложение FROM явно, если они не выводятся нужным образом из предложения columns. Для этого мы используем метод Select.select_from()
, как показано ниже, где мы устанавливаем user_table
как первый элемент в предложении FROM, а Select.join()
устанавливаем address_table
как второй:
>>> print(select(address_table.c.email_address).select_from(user_table).join(address_table))
SELECT address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
Другой пример, когда мы можем захотеть использовать Select.select_from()
, это если в нашем предложении columns недостаточно информации для предложения FROM. Например, для SELECT из общего выражения SQL count(*)
мы используем элемент SQLAlchemy, известный как sqlalchemy.sql.expression.func
, чтобы создать функцию SQL count()
:
>>> from sqlalchemy import func
>>> print(select(func.count("*")).select_from(user_table))
SELECT count(:count_2) AS count_1
FROM user_account
См.также
Управление тем, к чему присоединяться - в Руководство по составлению запросов ORM - содержит дополнительные примеры и примечания относительно взаимодействия Select.select_from()
и Select.join()
.
Установка положения о включении¶
Предыдущие примеры JOIN иллюстрировали, что конструкция Select
может соединять две таблицы и автоматически создавать предложение ON. Это происходит в тех примерах потому, что объекты user_table
и address_table
Table
включают одно определение ForeignKeyConstraint
, которое используется для формирования предложения ON.
Если левая и правая цели объединения не имеют такого ограничения, или существует несколько ограничений, нам необходимо указать предложение ON напрямую. И Select.join()
, и Select.join_from()
принимают дополнительный аргумент для предложения ON, который указывается с помощью той же механики SQL Expression, о которой мы говорили в Предложение ГДЕ:
>>> print(
... select(address_table.c.email_address)
... .select_from(user_table)
... .join(address_table, user_table.c.id == address_table.c.user_id)
... )
SELECT address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
Совет ORM - существует еще один способ создания предложения ON при использовании сущностей ORM, использующих конструкцию relationship()
, например, отображение, установленное в предыдущем разделе Объявление сопоставленных классов. Это целая тема, которая подробно рассматривается в Использование отношений для присоединения.
ВНЕШНЕЕ и ПОЛНОЕ соединение¶
Оба метода Select.join()
и Select.join_from()
принимают ключевые аргументы Select.join.isouter
и Select.join.full
, которые будут отображать LEFT OUTER JOIN и FULL OUTER JOIN, соответственно:
>>> print(select(user_table).join(address_table, isouter=True))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id
>>> print(select(user_table).join(address_table, full=True))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id
Существует также метод Select.outerjoin()
, который эквивалентен использованию .join(..., isouter=True)
.
Совет
В SQL также есть «RIGHT OUTER JOIN». SQLAlchemy не отображает это напрямую; вместо этого следует изменить порядок таблиц и использовать «LEFT OUTER JOIN».
ПО ПОРЯДКУ, ПО ГРУППАМ, ПО НАЛИЧИЮ¶
SQL-оператор SELECT включает предложение ORDER BY, которое используется для возврата выбранных строк в заданном порядке.
Предложение GROUP BY строится аналогично предложению ORDER BY и имеет целью разделить выбранные строки на определенные группы, для которых могут быть вызваны агрегатные функции. Предложение HAVING обычно используется вместе с GROUP BY и по форме аналогично предложению WHERE, за исключением того, что оно применяется к агрегированным функциям, используемым в группах.
ПОРЯДКОВЫЙ НОМЕР¶
Предложение ORDER BY строится в терминах конструкций SQL Expression, обычно основанных на Column
или аналогичных объектах. Метод Select.order_by()
принимает одно или несколько таких выражений позиционно:
>>> print(select(user_table).order_by(user_table.c.name))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account ORDER BY user_account.name
Возрастание / убывание доступно из модификаторов ColumnElement.asc()
и ColumnElement.desc()
, которые также присутствуют из ORM-связанных атрибутов:
>>> print(select(User).order_by(User.fullname.desc()))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account ORDER BY user_account.fullname DESC
Приведенное выше утверждение даст строки, отсортированные по столбцу user_account.fullname
в порядке убывания.
Агрегатные функции с GROUP BY / HAVING¶
В SQL агрегатные функции позволяют объединять выражения столбцов в нескольких строках для получения единого результата. Примеры включают подсчет, вычисление средних значений, а также нахождение максимального или минимального значения в наборе значений.
SQLAlchemy предоставляет функции SQL в открытом виде, используя пространство имен, известное как func
. Это специальный объект-конструктор, который будет создавать новые экземпляры Function
, когда ему дается имя конкретной SQL-функции, которая может иметь любое имя, а также ноль или более аргументов для передачи функции, которые, как и во всех других случаях, являются конструкциями SQL-выражений. Например, чтобы вывести функцию SQL COUNT() для столбца user_account.id
, мы обращаемся к имени count()
:
>>> from sqlalchemy import func
>>> count_fn = func.count(user_table.c.id)
>>> print(count_fn)
count(user_account.id)
Функции SQL более подробно описаны далее в этом учебнике в Работа с функциями SQL.
При использовании агрегатных функций в SQL предложение GROUP BY является важным, поскольку оно позволяет разделить строки на группы, где агрегатные функции будут применяться к каждой группе отдельно. При запросе неагрегированных столбцов в предложении COLUMNS оператора SELECT, SQL требует, чтобы все эти столбцы были подчинены предложению GROUP BY, прямо или косвенно на основе ассоциации с первичным ключом. Предложение HAVING затем используется аналогично предложению WHERE, за исключением того, что оно отфильтровывает строки на основе агрегированных значений, а не прямого содержимого строки.
SQLAlchemy предусматривает эти два условия с помощью методов Select.group_by()
и Select.having()
. Ниже мы проиллюстрируем выбор полей имени пользователя, а также подсчет адресов для тех пользователей, которые имеют более одного адреса:
>>> with engine.connect() as conn:
... result = conn.execute(
... select(User.name, func.count(Address.id).label("count"))
... .join(Address)
... .group_by(User.name)
... .having(func.count(Address.id) > 1)
... )
... print(result.all())
BEGIN (implicit)
SELECT user_account.name, count(address.id) AS count
FROM user_account JOIN address ON user_account.id = address.user_id GROUP BY user_account.name
HAVING count(address.id) > ?
[...] (1,)
[('sandy', 2)]
ROLLBACK
Упорядочивание или группировка по метке¶
Важным приемом, особенно в некоторых бэкендах баз данных, является возможность ORDER BY или GROUP BY выражения, которое уже указано в предложении columns, без повторного указания выражения в предложении ORDER BY или GROUP BY, а вместо этого используя имя столбца или имя метки из предложения COLUMNS. Эта форма доступна, если передать строковый текст имени в метод Select.order_by()
или Select.group_by()
. Переданный текст не отображается напрямую; вместо него отображается имя, заданное выражению в клаузе columns и отображаемое как имя этого выражения в контексте, выдавая ошибку, если совпадение не найдено. Унарные модификаторы asc()
и desc()
также могут быть использованы в этой форме:
>>> from sqlalchemy import func, desc
>>> stmt = (
... select(Address.user_id, func.count(Address.id).label("num_addresses"))
... .group_by("user_id")
... .order_by("user_id", desc("num_addresses"))
... )
>>> print(stmt)
SELECT address.user_id, count(address.id) AS num_addresses
FROM address GROUP BY address.user_id ORDER BY address.user_id, num_addresses DESC
Использование псевдонимов¶
Теперь, когда мы выбираем из нескольких таблиц и используем объединения, мы быстро сталкиваемся со случаем, когда нам нужно несколько раз сослаться на одну и ту же таблицу в предложении FROM оператора. Для этого мы используем SQL aliases, которые представляют собой синтаксис, дающий альтернативное имя таблице или подзапросу, с которого на нее можно ссылаться в операторе.
В языке выражений SQLAlchemy эти «имена» вместо этого представлены объектами FromClause
, известными как конструкция Alias
, которая создается в Core с помощью метода FromClause.alias()
. Конструкция Alias
подобна конструкции Table
в том смысле, что она также имеет пространство имен объектов Column
внутри коллекции Alias.c
. Приведенный ниже оператор SELECT, например, возвращает все уникальные пары имен пользователей:
>>> user_alias_1 = user_table.alias()
>>> user_alias_2 = user_table.alias()
>>> print(
... select(user_alias_1.c.name, user_alias_2.c.name).join_from(
... user_alias_1, user_alias_2, user_alias_1.c.id > user_alias_2.c.id
... )
... )
SELECT user_account_1.name, user_account_2.name AS name_1
FROM user_account AS user_account_1
JOIN user_account AS user_account_2 ON user_account_1.id > user_account_2.id
Псевдонимы сущностей ORM¶
ORM-эквивалентом метода FromClause.alias()
является функция ORM aliased()
, которая может быть применена к объекту, такому как User
и Address
. В результате получается внутренний объект Alias
, который противопоставляется оригинальному сопоставленному объекту Table
, сохраняя при этом функциональность ORM. Приведенный ниже SELECT выбирает из объекта User
все объекты, включающие два определенных адреса электронной почты:
>>> from sqlalchemy.orm import aliased
>>> address_alias_1 = aliased(Address)
>>> address_alias_2 = aliased(Address)
>>> print(
... select(User)
... .join_from(User, address_alias_1)
... .where(address_alias_1.email_address == "patrick@aol.com")
... .join_from(User, address_alias_2)
... .where(address_alias_2.email_address == "patrick@gmail.com")
... )
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
JOIN address AS address_1 ON user_account.id = address_1.user_id
JOIN address AS address_2 ON user_account.id = address_2.user_id
WHERE address_1.email_address = :email_address_1
AND address_2.email_address = :email_address_2
Совет
Как упоминалось в Установка положения о включении, ORM предусматривает другой способ объединения с помощью конструкции relationship()
. Приведенный выше пример с использованием псевдонимов демонстрируется с помощью relationship()
в Объединение между смежными целями.
Подзапросы и CTE¶
Подзапрос в SQL - это оператор SELECT, заключенный в круглые скобки и помещенный в контекст вложенного оператора, обычно оператора SELECT, но не обязательно.
В этом разделе мы рассмотрим так называемый «нескалярный» подзапрос, который обычно помещается в предложение FROM вложенного SELECT. Мы также рассмотрим общее табличное выражение или CTE, которое используется так же, как и подзапрос, но включает дополнительные возможности.
SQLAlchemy использует объект Subquery
для представления подзапроса и CTE
для представления CTE, обычно получаемого из методов Select.subquery()
и Select.cte()
соответственно. Любой из объектов может быть использован как элемент FROM внутри более крупной конструкции select()
.
Мы можем построить Subquery
, который будет выбирать агрегированный подсчет строк из таблицы address
(агрегированные функции и GROUP BY были представлены ранее в Агрегатные функции с GROUP BY / HAVING):
>>> subq = (
... select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
... .group_by(address_table.c.user_id)
... .subquery()
... )
Стрингизация подзапроса сама по себе, без встраивания его внутрь другого Select
или другого оператора, дает обычный оператор SELECT без каких-либо заключающих скобок:
>>> print(subq)
SELECT count(address.id) AS count, address.user_id
FROM address GROUP BY address.user_id
Объект Subquery
ведет себя как любой другой объект FROM, такой как Table
, в частности, он включает пространство имен Subquery.c
столбцов, которые он выбирает. Мы можем использовать это пространство имен для ссылки как на столбец user_id
, так и на наше пользовательское помеченное выражение count
:
>>> print(select(subq.c.user_id, subq.c.count))
SELECT anon_1.user_id, anon_1.count
FROM (SELECT count(address.id) AS count, address.user_id AS user_id
FROM address GROUP BY address.user_id) AS anon_1
С выбором строк, содержащихся в объекте subq
, мы можем применить объект к более крупному Select
, который присоединит данные к таблице user_account
:
>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
... user_table, subq
... )
>>> print(stmt)
SELECT user_account.name, user_account.fullname, anon_1.count
FROM user_account JOIN (SELECT count(address.id) AS count, address.user_id AS user_id
FROM address GROUP BY address.user_id) AS anon_1 ON user_account.id = anon_1.user_id
Для того чтобы присоединиться от user_account
к address
, мы использовали метод Select.join_from()
. Как было показано ранее, условие ON этого соединения было снова задано на основе ограничений внешнего ключа. Даже если подзапрос SQL сам по себе не имеет никаких ограничений, SQLAlchemy может действовать на ограничения, представленные в столбцах, определяя, что столбец subq.c.user_id
является производным от столбца address_table.c.user_id
, который выражает отношение внешнего ключа к столбцу user_table.c.id
, который затем используется для создания предложения ON.
Общие табличные выражения (CTE)¶
Использование конструкции CTE
в SQLAlchemy практически не отличается от использования конструкции Subquery
. Изменив вызов метода Select.subquery()
на использование Select.cte()
вместо него, мы можем использовать полученный объект в качестве элемента FROM таким же образом, но в качестве SQL используется совсем другой синтаксис обычного табличного выражения:
>>> subq = (
... select(func.count(address_table.c.id).label("count"), address_table.c.user_id)
... .group_by(address_table.c.user_id)
... .cte()
... )
>>> stmt = select(user_table.c.name, user_table.c.fullname, subq.c.count).join_from(
... user_table, subq
... )
>>> print(stmt)
WITH anon_1 AS
(SELECT count(address.id) AS count, address.user_id AS user_id
FROM address GROUP BY address.user_id)
SELECT user_account.name, user_account.fullname, anon_1.count
FROM user_account JOIN anon_1 ON user_account.id = anon_1.user_id
Конструкция CTE
также имеет возможность использования в «рекурсивном» стиле, и в более сложных случаях может быть составлена из пункта RETURNING оператора INSERT, UPDATE или DELETE. Подробные сведения об этих дополнительных шаблонах содержатся в документальной строке для CTE
.
В обоих случаях подзапрос и CTE были названы на уровне SQL с помощью «анонимного» имени. В коде Python нам совсем не нужно предоставлять эти имена. Идентификатор объекта Subquery
или CTE
служит синтаксическим идентификатором объекта при отображении. Имя, которое будет отображаться в SQL, можно указать, передав его в качестве первого аргумента методов Select.subquery()
или Select.cte()
.
См.также
Select.subquery()
- более подробная информация о подзапросах
Select.cte()
- примеры для CTE, включая использование RECURSIVE, а также DML-ориентированных CTE
ORM Entity Subqueries/CTEs¶
В ORM конструкция aliased()
может быть использована для ассоциации сущности ORM, такой как наш класс User
или Address
, с любой концепцией FromClause
, которая представляет собой источник строк. В предыдущем разделе Псевдонимы сущностей ORM показано использование aliased()
для ассоциации сопоставленного класса с Alias
его сопоставленного Table
. Здесь мы иллюстрируем, как aliased()
делает то же самое в отношении как Subquery
, так и CTE
, созданного в отношении конструкции Select
, которая в конечном счете является производной от того же сопоставленного Table
.
Ниже приведен пример применения aliased()
к конструкции Subquery
, чтобы из ее строк можно было извлекать сущности ORM. Результат показывает серию объектов User
и Address
, где данные для каждого объекта Address
в конечном итоге получены из подзапроса к таблице address
, а не непосредственно из этой таблицы:
>>> subq = select(Address).where(~Address.email_address.like("%@aol.com")).subquery()
>>> address_subq = aliased(Address, subq)
>>> stmt = (
... select(User, address_subq)
... .join_from(User, address_subq)
... .order_by(User.id, address_subq.id)
... )
>>> with Session(engine) as session:
... for user, address in session.execute(stmt):
... print(f"{user} {address}")
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname,
anon_1.id AS id_1, anon_1.email_address, anon_1.user_id
FROM user_account JOIN
(SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id
FROM address
WHERE address.email_address NOT LIKE ?) AS anon_1 ON user_account.id = anon_1.user_id
ORDER BY user_account.id, anon_1.id
[...] ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK
Далее следует другой пример, который точно такой же, только вместо него используется конструкция CTE
:
>>> cte_obj = select(Address).where(~Address.email_address.like("%@aol.com")).cte()
>>> address_cte = aliased(Address, cte_obj)
>>> stmt = (
... select(User, address_cte)
... .join_from(User, address_cte)
... .order_by(User.id, address_cte.id)
... )
>>> with Session(engine) as session:
... for user, address in session.execute(stmt):
... print(f"{user} {address}")
BEGIN (implicit)
WITH anon_1 AS
(SELECT address.id AS id, address.email_address AS email_address, address.user_id AS user_id
FROM address
WHERE address.email_address NOT LIKE ?)
SELECT user_account.id, user_account.name, user_account.fullname,
anon_1.id AS id_1, anon_1.email_address, anon_1.user_id
FROM user_account
JOIN anon_1 ON user_account.id = anon_1.user_id
ORDER BY user_account.id, anon_1.id
[...] ('%@aol.com',)
User(id=1, name='spongebob', fullname='Spongebob Squarepants') Address(id=1, email_address='spongebob@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=2, email_address='sandy@sqlalchemy.org')
User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org')
ROLLBACK
UNION, UNION ALL и другие операции с наборами¶
В SQL операторы SELECT могут быть объединены вместе с помощью операции UNION или UNION ALL SQL, которая создает набор всех строк, созданных одним или несколькими операторами вместе. Возможны и другие операции с наборами, такие как INTERSECT [ALL] и EXCEPT [ALL].
Конструкция SQLAlchemy Select
поддерживает композиции такого рода с помощью таких функций, как union()
, intersect()
и except_()
, а также аналогов «все» union_all()
, intersect_all()
и except_all()
. Все эти функции принимают произвольное количество подселектов, которые обычно являются конструкциями Select
, но могут быть и существующей композицией.
Конструкция, создаваемая этими функциями, - это CompoundSelect
, которая используется так же, как и конструкция Select
, за исключением того, что у нее меньше методов. Например, конструкция CompoundSelect
, создаваемая функцией union_all()
, может быть вызвана непосредственно с помощью Connection.execute()
:
>>> from sqlalchemy import union_all
>>> stmt1 = select(user_table).where(user_table.c.name == "sandy")
>>> stmt2 = select(user_table).where(user_table.c.name == "spongebob")
>>> u = union_all(stmt1, stmt2)
>>> with engine.connect() as conn:
... result = conn.execute(u)
... print(result.all())
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
UNION ALL SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[generated in ...] ('sandy', 'spongebob')
[(2, 'sandy', 'Sandy Cheeks'), (1, 'spongebob', 'Spongebob Squarepants')]
ROLLBACK
Для использования CompoundSelect
в качестве подзапроса, как и Select
, он предоставляет метод SelectBase.subquery()
, который создает объект Subquery
с коллекцией FromClause.c
, на которую можно ссылаться в объемлющем select()
:
>>> u_subq = u.subquery()
>>> stmt = (
... select(u_subq.c.name, address_table.c.email_address)
... .join_from(address_table, u_subq)
... .order_by(u_subq.c.name, address_table.c.email_address)
... )
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... print(result.all())
BEGIN (implicit)
SELECT anon_1.name, address.email_address
FROM address JOIN
(SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname
FROM user_account
WHERE user_account.name = ?
UNION ALL
SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname
FROM user_account
WHERE user_account.name = ?)
AS anon_1 ON anon_1.id = address.user_id
ORDER BY anon_1.name, address.email_address
[generated in ...] ('sandy', 'spongebob')
[('sandy', 'sandy@sqlalchemy.org'), ('sandy', 'sandy@squirrelpower.org'), ('spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK
Выбор объектов ORM из объединений¶
В предыдущих примерах было показано, как построить UNION на основе двух объектов Table
, чтобы затем вернуть строки базы данных. Если мы хотим использовать UNION или другую операцию набора для выбора строк, которые мы затем получаем как объекты ORM, можно использовать два подхода. В обоих случаях мы сначала создаем объект select()
или CompoundSelect
, который представляет оператор SELECT / UNION / etc, который мы хотим выполнить; этот оператор должен быть составлен против целевых объектов ORM или их базовых сопоставленных объектов Table
:
>>> stmt1 = select(User).where(User.name == "sandy")
>>> stmt2 = select(User).where(User.name == "spongebob")
>>> u = union_all(stmt1, stmt2)
Для простого SELECT с UNION, который еще не вложен в подзапрос, их часто можно использовать в контексте выборки объектов ORM с помощью метода Select.from_statement()
. При таком подходе оператор UNION представляет весь запрос; никакие дополнительные критерии не могут быть добавлены после использования Select.from_statement()
:
>>> orm_stmt = select(User).from_statement(u)
>>> with Session(engine) as session:
... for obj in session.execute(orm_stmt).scalars():
... print(obj)
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ? UNION ALL SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[generated in ...] ('sandy', 'spongebob')
User(id=2, name='sandy', fullname='Sandy Cheeks')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
ROLLBACK
Для более гибкого использования UNION или другой конструкции, связанной с множеством, в качестве компонента, связанного с сущностью, конструкция CompoundSelect
может быть организована в подзапрос с помощью CompoundSelect.subquery()
, который затем связывается с объектами ORM с помощью функции aliased()
. Это работает так же, как и в ORM Entity Subqueries/CTEs, сначала создается специальное «отображение» нужной нам сущности на подзапрос, а затем из него выбирается новая сущность, как если бы это был любой другой отображенный класс. В примере ниже мы можем добавить дополнительные критерии, такие как ORDER BY, вне самого UNION, так как мы можем фильтровать или упорядочивать по столбцам, экспортированным подзапросом:
>>> user_alias = aliased(User, u.subquery())
>>> orm_stmt = select(user_alias).order_by(user_alias.id)
>>> with Session(engine) as session:
... for obj in session.execute(orm_stmt).scalars():
... print(obj)
BEGIN (implicit)
SELECT anon_1.id, anon_1.name, anon_1.fullname
FROM (SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname
FROM user_account
WHERE user_account.name = ? UNION ALL SELECT user_account.id AS id, user_account.name AS name, user_account.fullname AS fullname
FROM user_account
WHERE user_account.name = ?) AS anon_1 ORDER BY anon_1.id
[generated in ...] ('sandy', 'spongebob')
User(id=1, name='spongebob', fullname='Spongebob Squarepants')
User(id=2, name='sandy', fullname='Sandy Cheeks')
ROLLBACK
подзапросы EXISTS¶
Ключевое слово SQL EXISTS - это оператор, который используется вместе с scalar subqueries для возврата булевой истины или лжи в зависимости от того, вернет ли оператор SELECT строку. SQLAlchemy включает вариант объекта ScalarSelect
под названием Exists
, который будет генерировать подзапрос EXISTS и наиболее удобно генерируется с помощью метода SelectBase.exists()
. Ниже мы создадим EXISTS, чтобы вернуть строки user_account
, которые имеют более одной связанной строки в address
:
>>> subq = (
... select(func.count(address_table.c.id))
... .where(user_table.c.id == address_table.c.user_id)
... .group_by(address_table.c.user_id)
... .having(func.count(address_table.c.id) > 1)
... ).exists()
>>> with engine.connect() as conn:
... result = conn.execute(select(user_table.c.name).where(subq))
... print(result.all())
BEGIN (implicit)
SELECT user_account.name
FROM user_account
WHERE EXISTS (SELECT count(address.id) AS count_1
FROM address
WHERE user_account.id = address.user_id GROUP BY address.user_id
HAVING count(address.id) > ?)
[...] (1,)
[('sandy',)]
ROLLBACK
Конструкция EXISTS чаще всего используется в качестве отрицания, например, NOT EXISTS, поскольку она обеспечивает эффективную с точки зрения SQL форму поиска строк, для которых в связанной таблице нет строк. Ниже мы выбираем имена пользователей, у которых нет адресов электронной почты; обратите внимание на двоичный оператор отрицания (~
), используемый во втором предложении WHERE:
>>> subq = (
... select(address_table.c.id).where(user_table.c.id == address_table.c.user_id)
... ).exists()
>>> with engine.connect() as conn:
... result = conn.execute(select(user_table.c.name).where(~subq))
... print(result.all())
BEGIN (implicit)
SELECT user_account.name
FROM user_account
WHERE NOT (EXISTS (SELECT address.id
FROM address
WHERE user_account.id = address.user_id))
[...] ()
[('patrick',)]
ROLLBACK
Работа с функциями SQL¶
Впервые представленный ранее в этом разделе в Агрегатные функции с GROUP BY / HAVING, объект func
служит фабрикой для создания новых объектов Function
, которые при использовании в конструкции типа select()
создают отображение SQL-функции, обычно состоящее из имени, некоторых скобок (хотя и не всегда) и, возможно, некоторых аргументов. Примеры типичных SQL-функций включают:
функция
count()
, агрегатная функция, которая подсчитывает, сколько строк возвращается:>>> print(select(func.count()).select_from(user_table)) SELECT count(*) AS count_1 FROM user_account
функция
lower()
, строковая функция, которая преобразует строку в нижний регистр:>>> print(select(func.lower("A String With Much UPPERCASE"))) SELECT lower(:lower_2) AS lower_1
функция
now()
, которая предоставляет текущую дату и время; поскольку это общая функция, SQLAlchemy знает, как отобразить ее по-разному для каждого бэкенда, в случае SQLite используя функцию CURRENT_TIMESTAMP:>>> stmt = select(func.now()) >>> with engine.connect() as conn: ... result = conn.execute(stmt) ... print(result.all())
BEGIN (implicit) SELECT CURRENT_TIMESTAMP AS now_1 [...] () [(datetime.datetime(...),)] ROLLBACK
Поскольку большинство бэкендов баз данных содержат десятки, а то и сотни различных SQL-функций, func
старается быть максимально либеральным в том, что он принимает. Любое имя, к которому обращаются из этого пространства имен, автоматически считается SQL-функцией, которая будет отображаться общим образом:
>>> print(select(func.some_crazy_function(user_table.c.name, 17)))
SELECT some_crazy_function(user_account.name, :some_crazy_function_2) AS some_crazy_function_1
FROM user_account
В то же время, относительно небольшой набор чрезвычайно распространенных функций SQL, таких как count
, now
, max
, concat
, включает в себя предварительно упакованные версии, которые обеспечивают надлежащую информацию о типизации, а также в некоторых случаях генерацию SQL, специфичную для бэкенда. В приведенном ниже примере показано, как происходит генерация SQL для диалекта PostgreSQL по сравнению с диалектом Oracle для функции now
:
>>> from sqlalchemy.dialects import postgresql
>>> print(select(func.now()).compile(dialect=postgresql.dialect()))
SELECT now() AS now_1
>>> from sqlalchemy.dialects import oracle
>>> print(select(func.now()).compile(dialect=oracle.dialect()))
SELECT CURRENT_TIMESTAMP AS now_1 FROM DUAL
Функции имеют возвратные типы¶
Поскольку функции являются выражениями столбцов, они также имеют SQL datatypes, которые описывают тип данных сгенерированного SQL-выражения. Здесь мы называем эти типы «возвращаемыми типами SQL», имея в виду тип значения SQL, возвращаемого функцией в контексте SQL-выражения на стороне базы данных, в отличие от «возвращаемого типа» функции Python.
Тип возврата любой функции SQL может быть доступен, обычно в целях отладки, путем обращения к атрибуту Function.type
:
>>> func.now().type
DateTime()
Эти типы возврата SQL имеют значение при использовании выражения функции в контексте более крупного выражения; то есть, математические операторы будут работать лучше, если тип данных выражения будет таким, как Integer
или Numeric
, а аксессоры JSON для работы должны использовать такой тип, как JSON
. Некоторые классы функций возвращают целые строки вместо значений столбцов, когда необходимо обратиться к конкретным столбцам; такие функции относятся к типу table valued functions.
Тип SQL-возврата функции также может иметь значение при выполнении оператора и получении строк обратно, в тех случаях, когда SQLAlchemy приходится применять обработку набора результатов. Ярким примером этого являются функции, связанные с датой на SQLite, где SQLAlchemy DateTime
и связанные с ним типы данных берут на себя роль преобразования строковых значений в объекты Python datetime()
по мере получения строк результата.
Чтобы применить определенный тип к создаваемой функции, мы передаем его с помощью параметра Function.type_
; аргументом типа может быть либо класс TypeEngine
, либо экземпляр. В примере ниже мы передаем класс JSON
для создания функции PostgreSQL json_object()
, отмечая, что возвращаемый тип SQL будет иметь тип JSON:
>>> from sqlalchemy import JSON
>>> function_expr = func.json_object('{a, 1, b, "def", c, 3.5}', type_=JSON)
Создавая нашу функцию JSON с типом данных JSON
, объект выражения SQL приобретает возможности, связанные с JSON, например, доступ к элементам:
>>> stmt = select(function_expr["def"])
>>> print(stmt)
SELECT json_object(:json_object_1)[:json_object_2] AS anon_1
Встроенные функции имеют заранее настроенные типы возврата¶
Для распространенных агрегатных функций, таких как count
, max
, min
, а также очень небольшого числа функций даты, таких как now
, и строковых функций, таких как concat
, тип возврата SQL устанавливается соответствующим образом, иногда на основе использования. Функция max
и аналогичные функции агрегатной фильтрации устанавливают тип возврата SQL на основе заданного аргумента:
>>> m1 = func.max(Column("some_int", Integer))
>>> m1.type
Integer()
>>> m2 = func.max(Column("some_str", String))
>>> m2.type
String()
Функции даты и времени обычно соответствуют выражениям SQL, описываемым символами DateTime
, Date
или Time
:
>>> func.now().type
DateTime()
>>> func.current_date().type
Date()
Известная строковая функция, такая как concat
, будет знать, что выражение SQL будет иметь тип String
:
>>> func.concat("x", "y").type
String()
Однако для подавляющего большинства функций SQL SQLAlchemy не имеет их явного присутствия в своем очень небольшом списке известных функций. Например, хотя обычно нет проблем с использованием SQL-функций func.lower()
и func.upper()
для преобразования регистра строк, SQLAlchemy на самом деле не знает об этих функциях, поэтому они имеют «нулевой» тип возврата SQL:
>>> func.upper("lowercase").type
NullType()
Для простых функций, таких как upper
и lower
, проблема обычно не существенна, поскольку строковые значения могут быть получены из базы данных без какой-либо специальной обработки типов на стороне SQLAlchemy, а правила когеренции типов SQLAlchemy часто могут правильно определить намерение; например, оператор Python +
будет правильно интерпретирован как оператор конкатенации строк на основе рассмотрения обеих сторон выражения:
>>> print(select(func.upper("lowercase") + " suffix"))
SELECT upper(:upper_1) || :upper_2 AS anon_1
В целом, сценарий, в котором параметр Function.type_
, скорее всего, необходим, таков:
функция еще не является встроенной функцией SQLAlchemy; об этом можно судить, создав функцию и заметив атрибут
Function.type
, то есть:>>> func.count().type Integer()
против:
>>> func.json_object('{"a", "b"}').type NullType()
Необходима поддержка выражений с поддержкой функций; чаще всего это относится к специальным операторам, связанным с типами данных, таким как
JSON
илиARRAY
.Необходима обработка значения результата, который может включать такие типы, как
DateTime
,Boolean
,Enum
, или снова специальные типы данных, такие какJSON
,ARRAY
.
Расширенные методы работы с функциями SQL¶
Следующие подразделы иллюстрируют больше вещей, которые можно сделать с помощью функций SQL. Хотя эти методы менее распространены и более сложны, чем использование базовых функций SQL, они, тем не менее, чрезвычайно популярны, во многом благодаря тому, что PostgreSQL уделяет особое внимание более сложным формам функций, включая формы с табличными и столбцовыми значениями, которые популярны при работе с данными JSON.
Использование функций окна¶
Оконная функция - это специальное использование агрегатной функции SQL, которая вычисляет суммарное значение по строкам, возвращаемым в группе, по мере обработки отдельных строк результата. В то время как функция типа MAX()
даст вам наибольшее значение столбца в наборе строк, использование той же функции в качестве «оконной функции» даст вам наибольшее значение для каждой строки, начиная с этой строки.
В SQL оконные функции позволяют указать строки, над которыми должна быть применена функция, значение «partition», которое рассматривает окно над различными подмножествами строк, и выражение «order by», которое указывает порядок, в котором строки должны быть применены к агрегатной функции.
В SQLAlchemy все SQL-функции, генерируемые пространством имен func
, включают метод FunctionElement.over()
, который предоставляет синтаксис оконной функции, или «OVER»; создаваемая конструкция - это конструкция Over
.
Обычной функцией, используемой с оконными функциями, является функция row_number()
, которая просто подсчитывает строки. Мы можем разделить это количество строк по имени пользователя, чтобы пронумеровать адреса электронной почты отдельных пользователей:
>>> stmt = (
... select(
... func.row_number().over(partition_by=user_table.c.name),
... user_table.c.name,
... address_table.c.email_address,
... )
... .select_from(user_table)
... .join(address_table)
... )
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... print(result.all())
BEGIN (implicit)
SELECT row_number() OVER (PARTITION BY user_account.name) AS anon_1,
user_account.name, address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
[...] ()
[(1, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (1, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK
Выше использован параметр FunctionElement.over.partition_by
, чтобы предложение PARTITION BY
было выведено внутри предложения OVER. Мы также можем использовать предложение ORDER BY
с помощью параметра FunctionElement.over.order_by
:
>>> stmt = (
... select(
... func.count().over(order_by=user_table.c.name),
... user_table.c.name,
... address_table.c.email_address,
... )
... .select_from(user_table)
... .join(address_table)
... )
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... print(result.all())
BEGIN (implicit)
SELECT count(*) OVER (ORDER BY user_account.name) AS anon_1,
user_account.name, address.email_address
FROM user_account JOIN address ON user_account.id = address.user_id
[...] ()
[(2, 'sandy', 'sandy@sqlalchemy.org'), (2, 'sandy', 'sandy@squirrelpower.org'), (3, 'spongebob', 'spongebob@sqlalchemy.org')]
ROLLBACK
Дополнительные опции для оконных функций включают использование диапазонов; примеры см. в разделе over()
.
Совет
Важно отметить, что метод FunctionElement.over()
применяется только к тем SQL-функциям, которые на самом деле являются агрегатными функциями; в то время как конструкция Over
с радостью выполнится для любой заданной SQL-функции, база данных отвергнет выражение, если сама функция не является агрегатной функцией SQL.
Специальные модификаторы WITHIN GROUP, FILTER¶
Синтаксис SQL «WITHIN GROUP» используется в сочетании с агрегатной функцией «упорядоченный набор» или «гипотетический набор». Обычные функции «упорядоченного множества» включают percentile_cont()
и rank()
. SQLAlchemy включает встроенные реализации rank
, dense_rank
, mode
, percentile_cont
и percentile_disc
, которые включают метод FunctionElement.within_group()
:
>>> print(
... func.unnest(
... func.percentile_disc([0.25, 0.5, 0.75, 1]).within_group(user_table.c.name)
... )
... )
unnest(percentile_disc(:percentile_disc_1) WITHIN GROUP (ORDER BY user_account.name))
«FILTER» поддерживается некоторыми бэкендами для ограничения диапазона агрегатной функции определенным подмножеством строк по сравнению с общим диапазоном возвращаемых строк, доступно с помощью метода FunctionElement.filter()
:
>>> stmt = (
... select(
... func.count(address_table.c.email_address).filter(user_table.c.name == "sandy"),
... func.count(address_table.c.email_address).filter(
... user_table.c.name == "spongebob"
... ),
... )
... .select_from(user_table)
... .join(address_table)
... )
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... print(result.all())
BEGIN (implicit)
SELECT count(address.email_address) FILTER (WHERE user_account.name = ?) AS anon_1,
count(address.email_address) FILTER (WHERE user_account.name = ?) AS anon_2
FROM user_account JOIN address ON user_account.id = address.user_id
[...] ('sandy', 'spongebob')
[(2, 1)]
ROLLBACK
Функции с табличными значениями¶
Таблично-значные функции SQL поддерживают скалярное представление, содержащее именованные подэлементы. Часто используется для JSON и ARRAY-ориентированных функций, а также функций типа generate_series()
, таблично-оцениваемая функция указывается в предложении FROM, а затем обозначается как таблица или иногда даже как столбец. Функции такой формы широко распространены в базе данных PostgreSQL, однако некоторые формы таблично-оцениваемых функций также поддерживаются SQLite, Oracle и SQL Server.
См.также
postgresql_table_valued_overview - в документации PostgreSQL.
Хотя многие базы данных поддерживают табличные значения и другие специальные формы, PostgreSQL, как правило, пользуется наибольшим спросом. Дополнительные примеры синтаксиса PostgreSQL, а также дополнительные возможности смотрите в этом разделе.
SQLAlchemy предоставляет метод FunctionElement.table_valued()
как базовую конструкцию «функция, оцениваемая таблицей», которая преобразует объект func
в предложение FROM, содержащее ряд именованных столбцов, основанных на строковых именах, переданных позиционно. В результате возвращается объект TableValuedAlias
, который представляет собой конструкцию Alias
с функцией, которая может быть использована как любое другое предложение FROM, представленное в Использование псевдонимов. Ниже мы проиллюстрируем функцию json_each()
, которая, будучи распространенной в PostgreSQL, также поддерживается современными версиями SQLite:
>>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value")
>>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"]))
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... result.all()
BEGIN (implicit)
SELECT anon_1.value
FROM json_each(?) AS anon_1
WHERE anon_1.value IN (?, ?)
[...] ('["one", "two", "three"]', 'two', 'three')
[('two',), ('three',)]
ROLLBACK
Выше мы использовали функцию json_each()
JSON, поддерживаемую SQLite и PostgreSQL, для создания табличного ценностного выражения с одним столбцом, обозначаемым как value
, а затем выбрали две из трех его строк.
См.также
postgresql_table_valued - в документации PostgreSQL - в этом разделе будут подробно описаны дополнительные синтаксисы, такие как специальные производные столбцов и «WITH ORDINALITY», которые, как известно, работают с PostgreSQL.
Функции, оцениваемые по столбцу - Функция, оцениваемая по таблице, как скалярный столбец¶
Специальный синтаксис, поддерживаемый PostgreSQL и Oracle, заключается в ссылке на функцию в предложении FROM, которая затем передается как один столбец в предложении columns оператора SELECT или другом контексте выражения столбца. PostgreSQL широко использует этот синтаксис для таких функций, как json_array_elements()
, json_object_keys()
, json_each_text()
, json_each()
и т.д.
SQLAlchemy называет это «функцией, оцениваемой по столбцам», и она доступна путем применения модификатора FunctionElement.column_valued()
к конструкции Function
:
>>> from sqlalchemy import select, func
>>> stmt = select(func.json_array_elements('["one", "two"]').column_valued("x"))
>>> print(stmt)
SELECT x
FROM json_array_elements(:json_array_elements_1) AS x
Форма «column valued» также поддерживается диалектом Oracle, где она используется для пользовательских функций SQL:
>>> from sqlalchemy.dialects import oracle
>>> stmt = select(func.scalar_strings(5).column_valued("s"))
>>> print(stmt.compile(dialect=oracle.dialect()))
SELECT COLUMN_VALUE s
FROM TABLE (scalar_strings(:scalar_strings_1)) s
См.также
postgresql_column_valued - в документации PostgreSQL.
Приведение данных и принуждение типов¶
В SQL нам часто требуется явно указать тип данных выражения, либо чтобы сообщить базе данных, какой тип ожидается в неоднозначном выражении, либо в некоторых случаях, когда мы хотим преобразовать подразумеваемый тип данных выражения SQL во что-то другое. Для этой задачи используется ключевое слово SQL CAST, которое в SQLAlchemy обеспечивается функцией cast()
. Эта функция принимает в качестве аргументов выражение столбца и объект типа данных, как показано ниже, где мы получаем SQL-выражение CAST(user_account.id AS VARCHAR)
из объекта столбца user_table.c.id
:
>>> from sqlalchemy import cast
>>> stmt = select(cast(user_table.c.id, String))
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... result.all()
BEGIN (implicit)
SELECT CAST(user_account.id AS VARCHAR) AS id
FROM user_account
[...] ()
[('1',), ('2',), ('3',)]
ROLLBACK
Функция cast()
не только отображает синтаксис SQL CAST, но и создает выражение столбца SQLAlchemy, которое будет действовать как данный тип данных на стороне Python. Строковое выражение от cast()
до JSON
получит JSON subscript и операторы сравнения, например:
>>> from sqlalchemy import JSON
>>> print(cast("{'a': 'b'}", JSON)["a"])
CAST(:param_1 AS JSON)[:param_2]
type_coerce() - «приведение» только в Python¶
Иногда возникает необходимость, чтобы SQLAlchemy знал тип данных выражения по всем вышеупомянутым причинам, но не отображал само выражение CAST на стороне SQL, где оно может помешать SQL-операции, которая уже работает без него. Для этого довольно распространенного случая использования существует еще одна функция type_coerce()
, которая тесно связана с cast()
, в том смысле, что она устанавливает выражение Python как имеющее определенный тип базы данных SQL, но не отображает ключевое слово CAST
или тип данных на стороне базы данных. type_coerce()
особенно важно при работе с типом данных JSON
, который обычно имеет сложную связь со строково-ориентированными типами данных на разных платформах и может даже не быть явным типом данных, как, например, в SQLite и MariaDB. Ниже мы используем type_coerce()
для передачи структуры Python в виде строки JSON в одну из функций MySQL JSON:
>>> import json
>>> from sqlalchemy import JSON
>>> from sqlalchemy import type_coerce
>>> from sqlalchemy.dialects import mysql
>>> s = select(type_coerce({"some_key": {"foo": "bar"}}, JSON)["some_key"])
>>> print(s.compile(dialect=mysql.dialect()))
SELECT JSON_EXTRACT(%s, %s) AS anon_1
Выше SQL-функция MySQL JSON_EXTRACT
была вызвана, поскольку мы использовали type_coerce()
, чтобы указать, что наш словарь Python должен рассматриваться как JSON
. В результате оператор Python __getitem__
, ['some_key']
в данном случае, стал доступен и позволил отобразить выражение пути JSON_EXTRACT
(не показано, однако в данном случае оно в конечном итоге будет '$."some_key"'
).