Рубрики
Без рубрики

SQLALCHEMY CASCADING Удаляет

Я думал, что я быстро напишу о чем -то, что я нашел уже некоторое время запутанным, но никогда не … с меткой SQL, база данных, Python.

Я подумал, что я быстро напишу о том, что я нашел уже некоторое время запутанным, но никогда не тратил время на то, чтобы посмотреть. Любой, кто работает с базами данных, вероятно, слышал о ограничениях иностранных ключей – используется мудро, они мешают нам создавать полную хэш нашей базы данных. Используя классический пример артистов и песен, иностранные ключевые ограничения могут помочь нам убедиться, что наша таблица песен не усеяна таинственными сиротами, у которых нет ассоциированного исполнителя.

При удалении исполнителя мы обычно хотели бы, чтобы все песни этого исполнителя были удалены из таблицы песен одновременно. Вот где На удалении каскада Вариант ограничений иностранного ключа вступает в игру. Учитывая этот вариант, большинство баз данных позаботятся о «каскаде» по удалению родителей до детей. Вот пример от PostgreSQL.

CREATE TABLE artist (
        id SERIAL NOT NULL, 
        PRIMARY KEY (id)
)

CREATE TABLE song (
        id SERIAL NOT NULL, 
        artist_id INTEGER, 
        PRIMARY KEY (id), 
        FOREIGN KEY(artist_id) REFERENCES artist (id) ON DELETE cascade
)

Как это переводится на землю sqlalchemy? Здесь у меня были некоторые неисправные предположения.

from sqlalchemy import create_engine, Column, Integer, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship


Base = declarative_base()


class Artist(Base):
    __tablename__ = "artist"

    id = Column(Integer, primary_key=True)
    songs = relationship("Song", cascade="all, delete")


class Song(Base):
    __tablename__ = "song"

    id = Column(Integer, primary_key=True)
    artist_id = Column(Integer, ForeignKey("artist.id"))


if __name__ == "__main__":
    uri = "postgresql://pguser:pguser@localhost/test"
    engine = create_engine(uri, echo=True)

    Base.metadata.create_all(engine)

    Session = sessionmaker(bind=engine)
    session = Session()

    artist = Artist()
    artist.songs.append(Song())

    session.add(artist)
    session.commit()

    session.delete(artist)
    session.commit()

Я предполагал, что добавление каскадного аргумента к отношениям привело к той же схеме, что и выше. К сожалению, это не … правда. Чтобы быть справедливым, документы SQLalchemy довольно ясны по этому вопросу. По сути, происходит то, что ORM управляет удалениями от вашего имени, если вариант Delete указан в аргументе каскада отношений.

Если мы посмотрим на SQL, который испускается во время вышеуказанной сессии, это станет более ясным.

2018-06-23 15:16:17,868 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-06-23 15:16:17,870 INFO sqlalchemy.engine.base.Engine INSERT INTO artist DEFAULT VALUES RETURNING artist.id
2018-06-23 15:16:17,870 INFO sqlalchemy.engine.base.Engine {}
2018-06-23 15:16:17,873 INFO sqlalchemy.engine.base.Engine INSERT INTO song (artist_id) VALUES (%(artist_id)s) RETURNING song.id
2018-06-23 15:16:17,874 INFO sqlalchemy.engine.base.Engine {'artist_id': 3}
2018-06-23 15:16:17,880 INFO sqlalchemy.engine.base.Engine COMMIT
2018-06-23 15:16:17,887 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2018-06-23 15:16:17,888 INFO sqlalchemy.engine.base.Engine SELECT artist.id AS artist_id 
FROM artist 
WHERE artist.id = %(param_1)s
2018-06-23 15:16:17,888 INFO sqlalchemy.engine.base.Engine {'param_1': 3}
2018-06-23 15:16:17,893 INFO sqlalchemy.engine.base.Engine SELECT song.id AS song_id, song.artist_id AS song_artist_id 
FROM song 
WHERE %(param_1)s = song.artist_id
2018-06-23 15:16:17,893 INFO sqlalchemy.engine.base.Engine {'param_1': 3}
2018-06-23 15:16:17,898 INFO sqlalchemy.engine.base.Engine DELETE FROM song WHERE song.id = %(id)s
2018-06-23 15:16:17,898 INFO sqlalchemy.engine.base.Engine {'id': 3}
2018-06-23 15:16:17,901 INFO sqlalchemy.engine.base.Engine DELETE FROM artist WHERE artist.id = %(id)s
2018-06-23 15:16:17,901 INFO sqlalchemy.engine.base.Engine {'id': 3}
2018-06-23 15:16:17,903 INFO sqlalchemy.engine.base.Engine COMMIT

SQLalchemy использует свои знания о отношениях песни и артиста, чтобы сначала удалить все песни, связанные с исполнителем, прежде чем пытаться удалить из таблицы исполнителей.

Это работает нормально, но дает нам несколько проблем.

Во -первых, это будет работать только в контексте SQLALCHEMY ORM. База данных по -прежнему имеет ограничение иностранного ключа, поэтому удаление с помощью RAW SQL из таблицы артистов потерпит неудачу, если будут связанные строки песни. Во -вторых, это простой пример, но реальные отношения могут иметь для них несколько уровней, и в некоторых случаях Sqlalchemy в конечном итоге выпустит метель запросов. Это намного медленнее, чем позволить базе данных обеспечить соблюдение отношений внутри.

Чтобы SQLalchemy создал ту же схему, которую мы первоначально видели, мы можем определить внешний ключ следующим образом:

artist_id = Column(Integer, ForeignKey("artist.id", ondelete="cascade"))

Интересная вещь здесь – то, что произойдет, если мы определим «каскад» на внешнем ключе, но не на родительских отношениях. При удалении художников через ORM, каскад удаления внутри базы данных игнорируется, и ORM продолжает обрабатывать свои удаления. Поведение по умолчанию в этом случае состоит в том, чтобы установить Artist_ID NULL на любые записи, родители которых были удалены.

Определение «каскада» в обоих местах приводит к тому, что ORM выполняет отдельные удаления на таблицах песни и исполнителей.

Если мы хотим полагаться на базу данных для выполнения этих каскадных удалений, нам нужно использовать аргумент passive_deletes следующим образом:

songs = relationship("Song", cascade="all, delete", passive_deletes=True)

Я считаю, что причиной этого поведения является то, что в некоторых случаях полагаться на базу данных в каскадных изменениях может не работать, если, например, с использованием SQLite, где ограничения внешнего ключа отключены по умолчанию.

Оригинал: “https://dev.to/zchtodd/sqlalchemy-cascading-deletes-8hk”