Source: Sqlalchemy документация bd
← Renderedmarkdown source:
---
description: Документация sqlalchemy
tags: sqlalchemy python bd
title: Sqlalchemy документация bd
---
Схема работы

[туторилалы](https://docs.sqlalchemy.org/en/14/)
## [Запуск приложения](https://docs.sqlalchemy.org/en/14/tutorial/engine.html)
Используется объект [engine](https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Engine). Engine реализует [connection pool](https://docs.sqlalchemy.org/en/14/core/pooling.html). Engine - это глобальный объект, который формируется и конфигурируется только один раз для части сервера, общающегося с ДБ.
```python
>>> from sqlalchemy import create_engine
>>> engine = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
```
[ДБ url](https://docs.sqlalchemy.org/en/14/core/engines.html#database-urls) задает три вещи: диалект, параметры доступа и имя конкретной БД
`dialect+driver://username:password@host:port/database`
[Пример для](https://docs.sqlalchemy.org/en/14/core/engines.html#sqlite) [[sqlite]]
## [Работа с транзакциями и БД](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html)
`connect()`
```python
>>> from sqlalchemy import text
>>> with engine.connect() as conn:
... result = conn.execute(text("select 'hello world'"))
... print(result.all())
```
```sql
BEGIN (implicit)
select 'hello world'
[...] ()
[('hello world',)]
ROLLBACK
```
`commit()`
```python
# "commit as you go"
>>> with engine.connect() as conn:
... conn.execute(text("CREATE TABLE some_table (x int, y int)"))
... conn.execute(
... text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
... [{"x": 1, "y": 1}, {"x": 2, "y": 4}]
... )
... conn.commit()
```
```sql
BEGIN (implicit)
CREATE TABLE some_table (x int, y int)
[...] ()
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
INSERT INTO some_table (x, y) VALUES (?, ?)
[...] ((1, 1), (2, 4))
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
COMMIT
```
Другой метод `begin()` возвращает коннект [с уже открытым комитом](https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Engine.begin)
```python
# "begin once"
>>> with engine.begin() as conn:
... conn.execute(
... text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
... [{"x": 6, "y": 8}, {"x": 9, "y": 10}]
... )
```
```sql
BEGIN (implicit)
INSERT INTO some_table (x, y) VALUES (?, ?)
[...] ((6, 8), (9, 10))
<sqlalchemy.engine.cursor.CursorResult object at 0x...>
COMMIT
```
## [Извлечение состояний БД напрямую](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#basics-of-statement-execution)
[Основной метод](https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.execute) `execute()` В коньюнкции с [методом](https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text) `text()` он возвращает [объект](https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Result) `result`
### [Объединение строк](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#fetching-rows)
```python
>>> with engine.connect() as conn:
... result = conn.execute(text("SELECT x, y FROM some_table"))
... for row in result:
... print(f"x: {row.x} y: {row.y}")
```
```sql
BEGIN (implicit)
SELECT x, y FROM some_table
[...] ()
x: 1 y: 1
x: 2 y: 4
x: 6 y: 8
x: 9 y: 10
ROLLBACK
```
### [Отправка параметров](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#sending-parameters)
### [Построение параметров из полученного ранее представления бд](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#bundling-parameters-with-a-statement)
## [Извлечение представления при помощи ОРМ-сессии](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#executing-with-an-orm-session)
Используется [объект](https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session) `Session`. Сессию есть смысл использовать только с ОРМ, т.к. в других случаях она не дает особых преимуществ.
```python
>>> from sqlalchemy.orm import Session
>>> stmt = text("SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y").bindparams(y=6)
>>> with Session(engine) as session:
... result = session.execute(stmt)
... for row in result:
... print(f"x: {row.x} y: {row.y}")
```
```sql
BEGIN (implicit)
SELECT x, y FROM some_table WHERE y > ? ORDER BY x, y
[...] (6,)
x: 6 y: 8
x: 9 y: 10
x: 11 y: 12
x: 13 y: 14
ROLLBACK
```
## [Работа с метаданными БД](https://docs.sqlalchemy.org/en/14/tutorial/metadata.html)
Используется [объект](https://docs.sqlalchemy.org/en/14/glossary.html#term-database-metadata) `database metadata`, который в пиотоне реализует представление БД строки-колонки. Базовым элементом метаданных БД является `table`. Метвдвнные можно инициализировать так:
```python
>>> from sqlalchemy import MetaData
>>> metadata = MetaData()
```
Типичное решение для большинства приложений - один инстантс метаданных. SGLAlchemy позволяет комбинировать множетво для одного приложения. Следующий шан - создание таблиц:
```python
>>> from sqlalchemy import Table, Column, Integer, String
>>> user_table = Table(
... "user_account",
... metadata,
... Column('id', Integer, primary_key=True),
... Column('name', String(30)),
... Column('fullname', String)
... )
```
Здесь инстанс `table` представляет отдельную таблицу в БД. Колонка представлена инстансом `column`. Типы данных, такие как `Integer` могут добавляться в колонку как с помощью, так и без помощи инствнсов.
```python
>>> user_table.c.name
Column('name', String(length=30), table=<user_account>)
>>> user_table.c.keys()
['id', 'name', 'fullname']
```
Тут-же задаются ограничение, к примеру `primary key` или [foregn key](https://docs.sqlalchemy.org/en/14/glossary.html#term-foreign-key-constraint)
```python
>>> from sqlalchemy import ForeignKey
>>> address_table = Table(
... "address",
... metadata,
... Column('id', Integer, primary_key=True),
... Column('user_id', ForeignKey('user_account.id'), nullable=False),
... Column('email_address', String, nullable=False)
... )
```
## [Определение метаданных с помощью ОРМ](https://docs.sqlalchemy.org/en/14/tutorial/metadata.html#defining-table-metadata-with-the-orm)
Когда мыф используем ОРМ, процесс декларирования метаданных совмещен с процессом декларирования [маппированных классов](https://docs.sqlalchemy.org/en/14/glossary.html#term-mapped). Маппированный класс - это любой #python класс, который имеет аттрибуты слинкованные с колонками БД. Это делается с помощью [объекта](https://docs.sqlalchemy.org/en/14/orm/mapping_api.html#sqlalchemy.orm.registry) `registry`
```python
>>> from sqlalchemy.orm import registry
>>> mapper_registry = registry()
>>> mapper_registry.metadata
MetaData()
```
Теперь, вместо декларпирования таблиц и колонок напрямую, мы можем это сделать в маппируемом классе.
```python
>>> Base = mapper_registry.generate_base()
```
Есть и комбинированный метод, объединяющий `registry` и декларацию БД
```python
from sqlalchemy.orm import declarative_base
Base = declarative_base()
```
Теперь осталось задекларировать маппированный классы.
```python
>>> from sqlalchemy.orm import relationship
>>> class User(Base):
... __tablename__ = 'user_account'
...
... id = Column(Integer, primary_key=True)
... name = Column(String(30))
... fullname = Column(String)
...
... addresses = relationship("Address", back_populates="user")
...
... def __repr__(self):
... return f"User(id={self.id!r}, name={self.name!r}, fullname={self.fullname!r})"
>>> class Address(Base):
... __tablename__ = 'address'
...
... id = Column(Integer, primary_key=True)
... email_address = Column(String, nullable=False)
... user_id = Column(Integer, ForeignKey('user_account.id'))
...
... user = relationship("User", back_populates="addresses")
...
... def __repr__(self):
... return f"Address(id={self.id!r}, email_address={self.email_address!r})"
>>> User.__table__
Table('user_account', MetaData(),
Column('id', Integer(), table=<user_account>, primary_key=True, nullable=False),
Column('name', String(length=30), table=<user_account>),
Column('fullname', String(), table=<user_account>), schema=None)
```
Несколько нюансов:
- класс автоматически генерирует \__init__()
- в данном случае реализован \__repr__(). но это не обязательно
- связи между таблицами [реализованы двусторонее](https://docs.sqlalchemy.org/en/14/orm/relationship_api.html#sqlalchemy.orm.relationship) через `relationship()`
Такой метод создания метаданных [сочетаем и с прямым](https://docs.sqlalchemy.org/en/14/tutorial/metadata.html#id1) (без ОРм)
## [Работа с данными в core режиме](https://docs.sqlalchemy.org/en/14/tutorial/data.html)
Core режим сфокусирован на извлечении и отправке данных при помощи #sql эзыка
### [Core insert](https://docs.sqlalchemy.org/en/14/tutorial/data.html#core-insert)
`insert()`
```python
>>> from sqlalchemy import insert
>>> stmt = insert(user_table).values(name='spongebob', fullname="Spongebob Squarepants")
>>> print(stmt)
INSERT INTO user_account (name, fullname) VALUES (:name, :fullname)
```
Можно [скомпилировать](https://docs.sqlalchemy.org/en/14/core/internals.html#sqlalchemy.engine.Compiled)
```python
>>> compiled = stmt.compile()
>>> compiled.params
{'name': 'spongebob', 'fullname': 'Spongebob Squarepants'}
```
`execute()`
```python
>>> with engine.connect() as conn:
... result = conn.execute(stmt)
... conn.commit()
```
```sql
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('spongebob', 'Spongebob Squarepants')
COMMIT
```
Инсерт генерирует значения автоматически из параметров, переданных в `execute()`
```python
>>> with engine.connect() as conn:
... result = conn.execute(
... insert(user_table),
... [
... {"name": "sandy", "fullname": "Sandy Cheeks"},
... {"name": "patrick", "fullname": "Patrick Star"}
... ]
... )
... conn.commit()
```
```sql
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] (('sandy', 'Sandy Cheeks'), ('patrick', 'Patrick Star'))
COMMIT
```
Более реалистичный Пример
```python
>>> from sqlalchemy import select, bindparam
>>> scalar_subquery = (
... select(user_table.c.id).
... where(user_table.c.name==bindparam('username')).
... scalar_subquery()
... )
>>> with engine.connect() as conn:
... result = conn.execute(
... insert(address_table).values(user_id=scalar_subquery),
... [
... {"username": 'spongebob', "email_address": "spongebob@sqlalchemy.org"},
... {"username": 'sandy', "email_address": "sandy@sqlalchemy.org"},
... {"username": 'sandy', "email_address": "sandy@squirrelpower.org"},
... ]
... )
... conn.commit()
```
```sql
BEGIN (implicit)
INSERT INTO address (user_id, email_address) VALUES ((SELECT user_account.id
FROM user_account
WHERE user_account.name = ?), ?)
[...] (('spongebob', 'spongebob@sqlalchemy.org'), ('sandy', 'sandy@sqlalchemy.org'),
('sandy', 'sandy@squirrelpower.org'))
COMMIT
```
`Insert.from_select()` [описание метода](https://docs.sqlalchemy.org/en/14/core/dml.html#sqlalchemy.sql.expression.Insert.from_select)
```python
>>> select_stmt = select(user_table.c.id, user_table.c.name + "@aol.com")
>>> insert_stmt = insert(address_table).from_select(
... ["user_id", "email_address"], select_stmt
... )
>>> print(insert_stmt)
```
```sql
INSERT INTO address (user_id, email_address)
SELECT user_account.id, user_account.name || :name_1 AS anon_1
FROM user_account
```
`Insert.returning()` [Описание метода](https://docs.sqlalchemy.org/en/14/core/dml.html#sqlalchemy.sql.expression.Insert.returning)
```python
>>> insert_stmt = insert(address_table).returning(address_table.c.id, address_table.c.email_address)
>>> print(insert_stmt)
```
```sql
INSERT INTO address (id, user_id, email_address)
VALUES (:id, :user_id, :email_address)
RETURNING address.id, address.email_address
```
### [Selecting Data](https://docs.sqlalchemy.org/en/14/tutorial/data.html#selecting-data)
`select()`
```python
>>> from sqlalchemy import select
>>> stmt = select(user_table).where(user_table.c.name == 'spongebob')
>>> print(stmt)
```
```sql
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = :name_1
```
Тоже самое через `row`
```python
>>> with engine.connect() as conn:
... for row in conn.execute(stmt):
... print(row)
```
```sql
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
```
Или через ОРМ
```python
>>> stmt = select(User).where(User.name == 'spongebob')
>>> with Session(engine) as session:
... for row in session.execute(stmt):
... print(row)
```
```sql
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
```
Примеры работы с колонками
```python
>>> print(select(user_table))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
>>> print(select(user_table.c.name, user_table.c.fullname))
SELECT user_account.name, user_account.fullname
FROM user_account
```
Ну и так далее - всевозможные типы запросов, делиты и т.д.: [core вариант работы с данными](https://docs.sqlalchemy.org/en/14/tutorial/data.html#selecting-data)
## [Работа с данными в ОРМ](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html)
ОРМ режим сфокусирован на ОРМ модели и оперирует объектом `Session()`. Чтобы работать в ОРМ режиме предварительно нужно знать:
- [Executing with an ORM Session](https://docs.sqlalchemy.org/en/14/tutorial/dbapi_transactions.html#tutorial-executing-orm-session) (см. пример выше)
- [Defining Table Metadata with the ORM](https://docs.sqlalchemy.org/en/14/tutorial/metadata.html#tutorial-orm-table-metadata) (см.пример выше)
- [Selecting ORM Entities and Columns](https://docs.sqlalchemy.org/en/14/tutorial/data.html#tutorial-selecting-orm-entities) (см. пример выше)
### [Inserting Rows with the ORM](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#inserting-rows-with-the-orm)
Вначале мы добавляем данные к инстансу объекта `Session`, затем используя `flush()` мы отправляем весь пул в БД.
Инстанс класса представляет строки в БД.
```python
>>> squidward = User(name="squidward", fullname="Squidward Tentacles")
>>> krabs = User(name="ehkrabs", fullname="Eugene H. Krabs")
>>> squidward
User(id=None, name='squidward', fullname='Squidward Tentacles')
```
Значение `None` означает, что атрибут не был определен к текущему моменту. Маппер sqlalchemy не поднимает ошибку при неверном определении атрибута или неопределении - вместо этого он всегда подставляет представленное значение, либо `None`, либо пропускает несуществующий аттрибут.
В настоящий момент в моедли данные замаплены согласно модели `User`, но еще не ассоциированиы ни с какой базой данных. Их необходимо добавить к сессии.
```python
>>> session = Session(engine)
>>> session.add(squidward)
>>> session.add(krabs)
>>> session.new
IdentitySet([User(id=None, name='squidward', fullname='Squidward Tentacles'), User(id=None, name='ehkrabs', fullname='Eugene H. Krabs')])
```
Объект `Session` аккумулирует изменеения во времени до тех пор, пока не будет [вызван](https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.flush) `Session.flush`.
```python
>>> session.flush()
```
```sql
BEGIN (implicit)
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('squidward', 'Squidward Tentacles')
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('ehkrabs', 'Eugene H. Krabs')
```
Пока `Session.flush` мы можем выполнить `Session.commit()`, `Session.rollback()`, или `Session.close()`
ОРМ создаст автоматически `primary key`для строк, добавленных во `flush`
```python
>>> squidward.id
4
>>> krabs.id
5
```
Все объекты, добавленные в память, мапятся по `identity map` и доступны через метод `get()`
```python
>>> some_squidward = session.get(User, 4)
>>> some_squidward
User(id=4, name='squidward', fullname='Squidward Tentacles')
>>> some_squidward is squidward
True
```
В завершении изменеения необходимо закомиитить, например так
```python
>>> session.commit()
COMMIT
```
### [Updating ORM Objects](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#updating-orm-objects)
В core реализации используется UPDATE/DELETE #sql представления. При использовании ОРМ есть два пути реализовать UPDATE:
- праймари - апдейт делается автоматически в процессе `Session`
- второй вариант - использование `Update` оператора с поддержкой ОРМ
#### Primary
```python
>>> sandy = session.execute(select(User).filter_by(name="sandy")).scalar_one()
```
```sql
BEGIN (implicit)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('sandy',)
```
```python
>>> sandy
User(id=2, name='sandy', fullname='Sandy Cheeks')
# Вносим изменеения
>>> sandy.fullname = "Sandy Squirrel"
>>> sandy in session.dirty
True
```
`session.dirty` - [сет всех](https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.dirty) "грязных" объектов, готовых к отправке изменеений.
Перед каждым следующим селектом происходит `autoflush` и изменеения отправляются в БД
```python
>>> sandy_fullname = session.execute(
... select(User.fullname).where(User.id == 2)
... ).scalar_one()
```
```sql
UPDATE user_account SET fullname=? WHERE user_account.id = ?
[...] ('Sandy Squirrel', 2)
SELECT user_account.fullname
FROM user_account
WHERE user_account.id = ?
[...] (2,)
```
```python
>>> print(sandy_fullname)
Sandy Squirrel
>>> sandy in session.dirty
False
```
#### [ORM-enabled UPDATE statements](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#updating-orm-objects)
Второй вариант - это вызвать вручную update в ОРМ модели
```python
>>> session.execute(
... update(User).
... where(User.name == "sandy").
... values(fullname="Sandy Squirrel Extraodinaire")
... )
```
```sql
UPDATE user_account SET fullname=? WHERE user_account.name = ?
[...] ('Sandy Squirrel Extraodinaire', 'sandy')
```
В этом случае используется специальная логика для добавления объекта в сессию.
```python
>>> sandy.fullname
'Sandy Squirrel Extraodinaire'
```
### [Deleting ORM Objects](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#deleting-orm-objects)
```python
>>> patrick = session.get(User, 3)
>>> session.delete(patrick)
```
До следующего селекта объект будет доступен в сессии. При селекте будет выполнен автофлаш и объект удалится
```python
>>> session.execute(select(User).where(User.name == "patrick")).first()
```
```sql
SELECT address.id AS address_id, address.email_address AS address_email_address,
address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (3,)
DELETE FROM user_account WHERE user_account.id = ?
[...] (3,)
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.name = ?
[...] ('patrick',)
```
```python
>>> patrick in session
False
```
Так-же как и с UPDATE мы можем это сделать вручную
```python
>>> # refresh the target object for demonstration purposes
>>> # only, not needed for the DELETE
SQL>>> squidward = session.get(User, 4)
>>> session.execute(delete(User).where(User.name == "squidward"))
```
```sql
DELETE FROM user_account WHERE user_account.name = ?
[...] ('squidward',)
```
### [Rolling Back](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#rolling-back)
```python
>>> session.rollback()
ROLLBACK
>>> sandy.fullname
'Sandy Cheeks'
>>> sandy.__dict__
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x...>,
'id': 2, 'name': 'sandy', 'fullname': 'Sandy Cheeks'}
>>> patrick in session
True
>>> session.execute(select(User).where(User.name == 'patrick')).scalar_one() is patrick
True
```
Роллбек отмотал все изменения в сессии, в т.ч. и в терминах БД.
### [Closing session](https://docs.sqlalchemy.org/en/14/tutorial/orm_data_manipulation.html#closing-a-session)
```python
>>> session.close()
ROLLBACK
```
Происходит закрытие всех коннекшенов и транзакций. Это означает, что если делалось только извлечение данных, совсем не обязательно вызывать `rollback()` - роллбек будет вызван автоматически. Все объекты извлекаются из сессии - это означает, что у них нет функционального состояния, связанного с БД, их, к примеру, нельзя закомитить или получить какие-то даныне из БД
```python
>>> squidward.name
Traceback (most recent call last):
...
sqlalchemy.orm.exc.DetachedInstanceError: Instance <User at 0x...> is not bound to a Session; attribute refresh operation cannot proceed
```
Чтобы прикрепить объекты к сессии снова, необходимо снова заюзать `add()`
```python
>>> session.add(squidward)
>>> squidward.name
```
```sql
BEGIN (implicit)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name, user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (4,)
'squidward'
```
## [Working with Related Objects](https://docs.sqlalchemy.org/en/14/tutorial/orm_related_objects.html)
Для установки связей между объектами [используется](https://docs.sqlalchemy.org/en/14/orm/relationship_api.html#sqlalchemy.orm.relationship) `relationship()`
```python
from sqlalchemy.orm import relationship
class User(Base):
__tablename__ = 'user_account'
# ... Column mappings
addresses = relationship("Address", back_populates="user")
class Address(Base):
__tablename__ = 'address'
# ... Column mappings
user = relationship("User", back_populates="addresses")
```
### Парсинг и загрузка связей
```python
>>> u1 = User(name='pkrabs', fullname='Pearl Krabs')
>>> u1.addresses
[]
>>> a1 = Address(email_address="pearl.krabs@gmail.com")
>>> u1.addresses.append(a1)
# Появились связанные данные
>>> u1.addresses
[Address(id=None, email_address='pearl.krabs@gmail.com')]
>>> a1.user
User(id=None, name='pkrabs', fullname='Pearl Krabs')
```
Синхронизацию данных обеспечивает `relationship.back_populates` параметр. Мы можем связать в отношении один ко многим и многие к одному.
```python
>>> a2 = Address(email_address="pearl@aol.com", user=u1)
>>> u1.addresses
[Address(id=None, email_address='pearl.krabs@gmail.com'), Address(id=None, email_address='pearl@aol.com')]
```
### Добавление в сессию
Каскадно добавляются все связанные объекты.
```python
>>> session.add(u1)
>>> u1 in session
True
>>> a1 in session
True
>>> a2 in session
True
>>> print(u1.id)
None
>>> print(a1.user_id)
None
```
Так как мы не делали коммит, у нас все еще не присвоены айдишники.
```python
>>> session.commit()
```
```sql
INSERT INTO user_account (name, fullname) VALUES (?, ?)
[...] ('pkrabs', 'Pearl Krabs')
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('pearl.krabs@gmail.com', 6)
INSERT INTO address (email_address, user_id) VALUES (?, ?)
[...] ('pearl@aol.com', 6)
COMMIT
```
### Загрузка связей
```sql
>>> u1.id
BEGIN (implicit)
SELECT user_account.id AS user_account_id, user_account.name AS user_account_name,
user_account.fullname AS user_account_fullname
FROM user_account
WHERE user_account.id = ?
[...] (6,)
6
```
```sql
>>> u1.addresses
SELECT address.id AS address_id, address.email_address AS address_email_address,
address.user_id AS address_user_id
FROM address
WHERE ? = address.user_id
[...] (6,)
[Address(id=4, email_address='pearl.krabs@gmail.com'), Address(id=5, email_address='pearl@aol.com')]
```
Запросы со связями к БД в ОРМ высокоптимизированы, чтобы не быть затратными. Больше о стратегиях загрузки связанных данных можно [узнать тут](https://docs.sqlalchemy.org/en/14/tutorial/orm_related_objects.html#tutorial-orm-loader-strategies). [[sqlalchemy-loader-strategy]]
## [Использование связанных данных в queries](https://docs.sqlalchemy.org/en/14/tutorial/orm_related_objects.html#using-relationships-in-queries)
[[sqlalchemy-querying]]
- `Select.join()`
- `aliased()`
- `where()`
- `all()`
- `any()`
[Простейшие лоады](https://docs.sqlalchemy.org/en/14/tutorial/orm_related_objects.html#common-relationship-operators) со связанными данными:
```python
# many to one equals comparison
>>> print(select(Address).where(Address.user == u1))
SELECT address.id, address.email_address, address.user_id
FROM address
WHERE :param_1 = address.user_id
# many to one not equals comparison
>>> print(select(Address).where(Address.user != u1))
SELECT address.id, address.email_address, address.user_id
FROM address
WHERE address.user_id != :user_id_1 OR address.user_id IS NULL
# object is contained in a one-to-many collection
>>> print(select(User).where(User.addresses.contains(a1)))
SELECT user_account.id, user_account.name, user_account.fullname
FROM user_account
WHERE user_account.id = :param_1
# An object has a particular parent from a one-to-many perspective
>>> from sqlalchemy.orm import with_parent
>>> print(select(Address).where(with_parent(u1, User.addresses)))
SELECT address.id, address.email_address, address.user_id
FROM address
WHERE :param_1 = address.user_id
```
[100500 дополнительных материалов по ОРМ](https://docs.sqlalchemy.org/en/14/tutorial/further_reading.html)
[колонки и типы данных](https://docs.sqlalchemy.org/en/14/core/types.html)
[коннекшен и работа с in-memory](https://docs.sqlalchemy.org/en/14/dialects/sqlite.html#connect-strings)
Утилиты для sqlalchemy: [sqlalchemy_utils](https://sqlalchemy-utils.readthedocs.io/en/latest/index.html). Не пашет с sqlalchemy-1.4. [Сурс](https://stackoverflow.com/a/66651808)
> You have installed SQLAlchemy 1.4.0. SQLAlchemy-utils is currently not compatible with SQLAlchemy > 1.4.0. The solution is to downgrade SQLAlchemy to 1.3.23.
- [[sqlalchemy-querying]]
- [[fatsapi-sql-orm-example]]
- [[sqlite]]
- [[alembic]] - тулза для миграций для sqlalchemy
- [[sqlalchemy]]
[sqlite]: sqlite "Sqlite bd"
[sqlalchemy-loader-strategy]: sqlalchemy-loader-strategy "Sqlalchemy loader strategy bd"
[sqlalchemy-querying]: sqlalchemy-querying "Sqlalchgemy querying bd"
[fatsapi-sql-orm-example]: fatsapi-sql-orm-example "Fatsapi sql orm example"
[alembic]: alembic "Alembic"
[sqlalchemy]: ../lists/sqlalchemy "Sqlalchemy"