Должно быть, мне не хватает чего-то тривиального с каскадными параметрами SQLAlchemy, потому что я не могу заставить простое каскадное удаление работать правильно - если родительский элемент удален, дочерние элементы сохраняются с null
внешними ключами.
Я поместил здесь краткий тестовый пример:
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key = True)
parentid = Column(Integer, ForeignKey(Parent.id))
parent = relationship(Parent, cascade = "all,delete", backref = "children")
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
parent = Parent()
parent.children.append(Child())
parent.children.append(Child())
parent.children.append(Child())
session.add(parent)
session.commit()
print "Before delete, children = {0}".format(session.query(Child).count())
print "Before delete, parent = {0}".format(session.query(Parent).count())
session.delete(parent)
session.commit()
print "After delete, children = {0}".format(session.query(Child).count())
print "After delete parent = {0}".format(session.query(Parent).count())
session.close()
Вывод:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Между родительским и дочерним объектами существует простая связь «один ко многим». Скрипт создает родителя, добавляет 3 потомка, а затем фиксирует. Затем он удаляет родителя, но потомки остаются. Зачем? Как заставить детей каскадно удалять?
python
database
sqlalchemy
деревенщина
источник
источник
Ответы:
Проблема в том, что sqlalchemy считается
Child
родительским, потому что именно там вы определили свои отношения (конечно, неважно, что вы назвали его «Дочерним»).Если
Parent
вместо этого вы определите отношения в классе, это будет работать:(обратите внимание
"Child"
на строку: это разрешено при использовании декларативного стиля, чтобы вы могли ссылаться на класс, который еще не определен)Вы также можете добавить
delete-orphan
(delete
приводит к удалению дочерних элементов при удалении родителя,delete-orphan
также удаляет всех дочерних элементов, которые были «удалены» от родителя, даже если родитель не удален)РЕДАКТИРОВАТЬ: только что выяснилось: если вы действительно хотите определить отношения в
Child
классе, вы можете это сделать, но вам нужно будет определить каскад на обратной ссылке (путем создания обратной ссылки явно), например:(подразумевая
from sqlalchemy.orm import backref
)источник
Child
объект изparent.children
, должен ли этот объект быть удален из базы данных или должна быть удалена только его ссылка на родительский элемент (т.е. установить дляparentid
столбца значение null вместо удаления строки)relationship
это не диктует настройку родитель-потомок. ИспользованиеForeignKey
на столе - вот что настраивает его как ребенка. Не имеет значения, принадлежит лиrelationship
он родителю или ребенку.@ Ответ Стивена хорош, когда вы удаляете через,
session.delete()
что никогда не происходит в моем случае. Я заметил, что большую часть времени я удаляю черезsession.query().filter().delete()
(что не помещает элементы в память и удаляет непосредственно из db). Использование этого метода sqlalchemycascade='all, delete'
не работает. Однако есть решение:ON DELETE CASCADE
через db (примечание: не все базы данных поддерживают его).источник
session.query().filter().delete()
и изо всех сил пытался найти проблемуpassive_deletes='all'
, чтобы дочерние элементы были удалены каскадом базы данных при удалении родителя. При этомpassive_deletes=True
дочерние объекты отделялись (родительский объект был установлен на NULL) до того, как родительский объект был удален, поэтому каскад базы данных ничего не делал.passive_deletes=True
в этом сценарии все работает правильно.Довольно старый пост, но я потратил на это час или два, поэтому я хотел поделиться своим выводом, тем более что некоторые из других перечисленных комментариев не совсем правильные.
TL; DR
Дайте дочерней таблице чужую или измените существующую, добавив
ondelete='CASCADE'
:И одно из следующих отношений:
а) Это в родительской таблице:
б) Или это в дочерней таблице:
подробности
Во-первых, несмотря на то, что говорится в принятом ответе, отношения родитель / потомок не устанавливаются с помощью
relationship
, они устанавливаются с помощьюForeignKey
. Вы можете поместить егоrelationship
в родительскую или дочернюю таблицы, и он будет работать нормально. Хотя, очевидно, в дочерних таблицах вы должны использоватьbackref
функцию в дополнение к аргументу ключевого слова.Вариант 1 (желательно)
Во-вторых, SqlAlchemy поддерживает два разных типа каскадирования. Первый и тот, который я рекомендую, встроен в вашу базу данных и обычно принимает форму ограничения на объявление внешнего ключа. В PostgreSQL это выглядит так:
Это означает, что при удалении записи из базы данных
parent_table
все соответствующие строкиchild_table
будут удалены за вас. Это быстро и надежно и, вероятно, ваш лучший выбор. Вы устанавливаете это в SqlAlchemyForeignKey
следующим образом (часть определения дочерней таблицы):Это
ondelete='CASCADE'
та часть, которая создаетON DELETE CASCADE
на столе.Попался!
Здесь есть важная оговорка. Обратите внимание, как я
relationship
указалpassive_deletes=True
? Если у вас этого нет, все не будет работать. Это связано с тем, что по умолчанию при удалении родительской записи SqlAlchemy делает что-то действительно странное. Он устанавливает для внешних ключей всех дочерних строк значениеNULL
. Итак, если вы удалите строку изparent_table
whereid
= 5, она в основном выполнитЯ понятия не имею, зачем тебе это нужно. Я был бы удивлен, если бы многие движки баз данных даже позволили вам установить действительный внешний ключ
NULL
, создавая сироту. Вроде плохая идея, но, возможно, есть вариант использования. В любом случае, если вы позволите SqlAlchemy сделать это, вы не позволите базе данных очищать дочерние элементы, используяON DELETE CASCADE
настроенный вами. Это потому, что он полагается на эти внешние ключи, чтобы знать, какие дочерние строки следует удалить. Как только SqlAlchemy установит их всеNULL
, база данных не сможет их удалить. Установкаpassive_deletes=True
предотвращает выдачуNULL
внешних ключей SqlAlchemy .Вы можете узнать больше о пассивных удалениях в документации SqlAlchemy .
Вариант 2
Другой способ сделать это - позволить SqlAlchemy сделать это за вас. Это настраивается с использованием
cascade
аргументаrelationship
. Если у вас есть связь, определенная в родительской таблице, она выглядит так:Если отношения на ребенке, вы делаете это так:
Опять же, это дочерний элемент, поэтому вам нужно вызвать вызываемый метод
backref
и поместить туда каскадные данные.При этом, когда вы удаляете родительскую строку, SqlAlchemy фактически запускает операторы удаления, чтобы вы могли очистить дочерние строки. Скорее всего, это будет не так эффективно, как позволить этой базе данных обрабатывать, если для вас, поэтому я не рекомендую это делать.
Вот документы SqlAlchemy о поддерживаемых каскадных функциях.
источник
Column
в дочерней таблице какForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
не работает? Я ожидал, что потомки будут удалены, когда их строка родительской таблицы также будет удалена. Вместо этого SQLA либо устанавливает дочерние элементы в a,parent.id=NULL
либо оставляет их «как есть», но не удаляет. Это после первоначального определенияrelationship
в родительском элементе какchildren = relationship('Parent', backref='parent')
orrelationship('Parent', backref=backref('parent', passive_deletes=True))
; DB показываетcascade
правила в DDL (доказательство концепции на основе SQLite3). Мысли?backref=backref('parent', passive_deletes=True)
я получаю следующее предупреждение:SAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % self
предполагаю, чтоpassive_deletes=True
по какой-то причине ему не нравится использование в этих (очевидных) отношениях родитель-потомок один-ко-многим.delete
лишнееcascade='all,delete'
?delete
избыточнымcascade='all,delete'
, поскольку, согласно документации SQLAlchemy ,all
является синонимом для:save-update, merge, refresh-expire, expunge, delete
Стивен прав в том, что вам нужно явно создать обратную ссылку, это приводит к тому, что каскад применяется к родительскому элементу (в отличие от его применения к дочернему, как в тестовом сценарии).
Однако определение отношения для Child НЕ заставляет sqlalchemy рассматривать Child как родителя. Не имеет значения, где определена связь (дочерняя или родительская), это внешний ключ, связывающий две таблицы, который определяет, какая из них является родительской, а какая дочерней.
Однако имеет смысл придерживаться одного соглашения, и на основе ответа Стивена я определяю все свои дочерние отношения с родителем.
источник
Я тоже боролся с документацией, но обнаружил, что сами строки документации, как правило, проще, чем руководство. Например, если вы импортируете отношение из sqlalchemy.orm и выполняете help (Relationship), он предоставит вам все параметры, которые вы можете указать для каскада. Пуля для
delete-orphan
говорит:Я понимаю, что ваша проблема была больше в том, как документация по определению родительско-дочерних отношений. Но похоже, что у вас также может быть проблема с параметрами каскада, потому что
"all"
include"delete"
."delete-orphan"
- единственный вариант, который не включен в"all"
.источник
help(..)
наsqlalchemy
объектах очень помогает! Спасибо :-))) ! PyCharm ничего не показывает в контекстных доках и просто забыл проверить файлhelp
. Большое тебе спасибо!Ответ Стивена твердый. Я хотел бы указать на дополнительный смысл.
Используя
relationship
, вы делаете уровень приложения (Flask) ответственным за ссылочную целостность. Это означает, что другие процессы, которые обращаются к базе данных не через Flask, такие как утилита базы данных или человек, напрямую подключающийся к базе данных, не будут испытывать этих ограничений и могут изменить ваши данные таким образом, чтобы нарушить логическую модель данных, над разработкой которой вы так усердно работали. .По возможности используйте
ForeignKey
подход, описанный d512 и Alex. Механизм БД очень хорош в том, чтобы действительно обеспечивать соблюдение ограничений (неизбежным образом), так что это, безусловно, лучшая стратегия для поддержания целостности данных. Единственный раз, когда вам нужно полагаться на приложение для обработки целостности данных, - это когда база данных не может их обработать, например, версии SQLite, которые не поддерживают внешние ключи.Если вам нужно создать дополнительную связь между сущностями, чтобы включить поведение приложения, такое как навигация по отношениям родительско-дочерних объектов, используйте
backref
вместе сForeignKey
.источник
Ответ Стевана идеален. Но если вы все еще получаете ошибку. Другая возможная попытка на вершине этого была бы -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Скопировано по ссылке -
Подсказка, если у вас возникнут проблемы с зависимостью внешнего ключа, даже если вы указали каскадное удаление в своих моделях.
Используя SQLAlchemy, чтобы указать каскадное удаление, которое вы должны иметь
cascade='all, delete'
в родительской таблице. Хорошо, но тогда, когда вы выполните что-то вроде:Фактически это вызывает ошибку о внешнем ключе, используемом в ваших дочерних таблицах.
Решение, которое я использовал для запроса объекта, а затем его удаления:
Это должно удалить вашу родительскую запись И все связанные с ней дочерние элементы.
источник
.first()
Требуется звонок ? Какие условия фильтрации возвращают список объектов, и все нужно удалить? Разве вызов не.first()
получает только первый объект? @PrashantОтвет Алекса Окрушко почти сработал для меня. Используется ondelete = 'CASCADE' и passive_deletes = True вместе. Но мне пришлось сделать что-то еще, чтобы он работал для sqlite.
Не забудьте добавить этот код, чтобы убедиться, что он работает для sqlite.
Украдено отсюда: язык выражений SQLAlchemy и SQLite при каскаде удаления
источник
TL; DR: если вышеуказанные решения не работают, попробуйте добавить в столбец nullable = False.
Я хотел бы добавить здесь небольшой момент для некоторых людей, у которых может не получиться заставить каскадную функцию работать с существующими решениями (которые великолепны). Основное различие между моей работой и примером в том, что я использовал автокарту. Я не знаю точно, как это может помешать настройке каскадов, но хочу отметить, что использовал его. Я также работаю с базой данных SQLite.
Я пробовал каждое решение, описанное здесь, но для строк в моей дочерней таблице по-прежнему был установлен нулевой внешний ключ, когда родительская строка была удалена. Я пробовал все решения здесь безрезультатно. Однако каскад сработал, как только я установил для дочернего столбца с внешним ключом значение nullable = False.
В дочернюю таблицу я добавил:
При такой настройке каскад работал так, как ожидалось.
источник