SQLAlchemy: supprimer en cascade
Je doit manquer quelque chose de trivial avec SQLAlchemy de la cascade d'options que je ne peux pas obtenir un simple supprimer en cascade pour fonctionner correctement-si un parent de l'élément est supprimé, les enfants persistent, avec null
clés étrangères.
J'ai mis un concis de cas de test ici:
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()
De sortie:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Il est un simple, un-à-plusieurs relation entre le Parent et l'Enfant. Le script crée un parent, ajoute 3 enfants, puis s'engage. Ensuite, il supprime les parents, mais les enfants persistent. Pourquoi? Comment puis-je rendre les enfants effacer en cascade?
- Cette section de la documentation (au moins aujourd'hui, 3 ans plus tard, après le post original) semble tout à fait utile à ce sujet: docs.sqlalchemy.org/en/rel_0_9/orm/session.html#cascades
Vous devez vous connecter pour publier un commentaire.
Le problème est que sqlalchemy considère
Child
en tant que parent, parce que c'est là que vous avez défini votre relation (il ne se soucie pas que vous l'avez appelé "Enfant", bien entendu).Si vous définissez la relation sur le
Parent
classe au lieu de cela, il va travailler:(note
"Child"
comme une chaîne de caractères: ce qui est permis lors de l'utilisation de l'ensemble des déclarations de style, de sorte que vous êtes en mesure de se référer à une classe qui n'est pas encore défini)Vous voudrez peut-être ajouter
delete-orphan
ainsi (delete
incite les enfants à être supprimée lorsque le parent est supprimé,delete-orphan
supprime également tous les enfants qui ont été "enlevés" par les parents, même si le parent n'est pas supprimé)EDIT: viens de découvrir: si vous vraiment souhaitez définir la relation sur le
Child
de classe, vous pouvez le faire, mais vous aurez à définir la cascade sur le backref (par la création de la backref explicitement), comme ceci:(ce qui implique
from sqlalchemy.orm import backref
)Child
objet deparent.children
si l'objet sera supprimé de la base de données, ou ne devrait-il référence à la mère d'être supprimé (ie. ensembleparentid
colonne null, plutôt que de supprimer la ligne)relationship
n'est pas dicter la relation parent-enfant de l'installation. À l'aide deForeignKey
sur une table est ce qui le définit comme l'enfant. Il n'a pas d'importance si lerelationship
est sur le parent ou l'enfant.@Steven est asnwer est bon quand vous supprimez par
session.delete()
qui n'arrive jamais, dans mon cas. J'ai remarqué que la plupart du temps je supprime parsession.query().filter().delete()
(ce qui ne veut pas mettre d'éléments dans la mémoire et supprime directement à partir de db).L'utilisation de cette méthode de sqlalchemy de
cascade='all, delete'
ne fonctionne pas. Il y a une solution si:ON DELETE CASCADE
par db (remarque: pas toutes les bases de données de la soutenir).session.query().filter().delete()
et de la difficulté à trouver le problèmepassive_deletes='all'
dans le but d'amener les enfants à être supprimé par la base de données de la cascade lorsque le parent est supprimé. Avecpassive_deletes=True
, les enfants, les objets étaient arriver dissocié (parent NULL) avant que le parent est supprimé, de sorte que la base de données cascade de ne pas faire n'importe quoi.passive_deletes=True
fonctionne correctement dans ce scénario.Assez vieux post, mais je viens de passer une heure ou deux sur ce, j'ai donc voulu partager ma conclusion, d'autant plus que certains des autres commentaires indiquées ne sont pas tout à fait droit.
TL;DR
Donner à l'enfant le tableau étrangère ou modifier l'existant, l'ajout de
ondelete='CASCADE'
:Et un des relations suivantes:
a) le Présent sur la table parent:
b) Ou cela sur la table d'enfant:
Détails
Tout d'abord, en dépit de ce que l'on a accepté la réponse dit, la relation parent/enfant n'est pas établi à l'aide de
relationship
, il est établi à l'aide deForeignKey
. Vous pouvez mettre lerelationship
sur le parent ou l'enfant, de tables et il fonctionne parfaitement. Bien que, apparemment sur les tables enfants, vous devez utiliser lebackref
la fonction en plus de l'argument mot-clé.Option 1 (par défaut)
Deuxième, SqlAlchemy prend en charge deux types différents d'une cascade. Le premier, et le seul que je recommande, est intégré dans votre base de données et prend généralement la forme d'une contrainte de clé étrangère de la déclaration. Dans PostgreSQL, il ressemble à ceci:
Cela signifie que lorsque vous supprimez un enregistrement de
parent_table
, puis toutes les lignes correspondantes danschild_table
sera supprimé pour vous dans la base de données. Il est rapide et fiable et probablement votre meilleur pari. Vous mis cela dans SqlAlchemy parForeignKey
comme ceci (la partie de l'enfant définition de la table):La
ondelete='CASCADE'
est la partie qui crée laON DELETE CASCADE
sur la table.Gotcha!
Il y a une mise en garde importante ici. Remarquez comment j'ai un
relationship
spécifié avecpassive_deletes=True
? Si vous n'avez pas que, toute chose ne fonctionne pas. C'est parce que par défaut, lorsque vous supprimez un enregistrement parent SqlAlchemy fait quelque chose de vraiment bizarre. Il définit les clés étrangères de toutes les lignes enfants àNULL
. Donc, si vous supprimez une ligne deparent_table
oùid
= 5, alors il sera fondamentalement exécuterPourquoi vous voulez ce que je n'ai aucune idée. Je serais surpris si un grand nombre de moteurs de base de données, même vous a permis de définir un étranger valide la clé de
NULL
, la création d'un orphelin. Semble être une mauvaise idée, mais peut-être qu'il y a un cas d'utilisation. De toute façon, si vous laissez SqlAlchemy ce faire, vous devrez empêcher la base de données d'être en mesure de nettoyer les enfants à l'aide de laON DELETE CASCADE
que vous avez défini. C'est parce qu'il s'appuie sur ces touches pour savoir quel enfant de lignes à supprimer. Une fois SqlAlchemy a les mettre tous àNULL
, la base de données ne peut pas les supprimer. Réglage de lapassive_deletes=True
empêche de SqlAlchemy deNULL
ing les clés étrangères.Vous pouvez en lire plus à propos de passif supprime en SqlAlchemy docs.
Option 2
L'autre façon que vous pouvez faire c'est laisser la SqlAlchemy le faire pour vous. Il est configuré à l'aide de la
cascade
argument de larelationship
. Si vous avez de la relation définie sur la table parent, il ressemble à ceci:Si la relation est sur l'enfant, à vous de faire comme ceci:
Encore une fois, c'est l'enfant de sorte que vous devez appeler une méthode appelée
backref
et de mettre la cascade de données là-bas.Dans ce lieu, lorsque vous supprimez une ligne parent, SqlAlchemy fonctionneront delete pour vous de nettoyer les lignes enfants. Ce ne sera probablement pas aussi efficace que la location de cette base de données à manipuler si pour vous, donc je ne le recommande pas.
Voici les SqlAlchemy docs sur la cascade de fonctionnalités, il prend en charge.
Column
dans la table enfant commeForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
pas de travail, que ce soit? J'ai attendu que les enfants soient supprimés lorsque leur parent ligne de la table a été supprimé aussi. Au lieu de cela, SQLA soit sets de enfants à uneparent.id=NULL
ou les laisse, "comme", mais ne supprime. C'est après l'origine, la définition de larelationship
dans le parent commechildren = relationship('Parent', backref='parent')
ourelationship('Parent', backref=backref('parent', passive_deletes=True))
; DB montrecascade
règles dans le DDL (SQLite3 base de la preuve de concept). Pensées?backref=backref('parent', passive_deletes=True)
je reçois le message d'avertissement suivant:SAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % self
, ce qui suggère qu'il n'aime pas l'utilisation depassive_deletes=True
dans ce (évident) un-à-plusieurs relation parent-enfant pour une raison quelconque.Steven est correct en ce que vous avez besoin de créer explicitement la backref, cette résultats dans la cascade d'être appliqué sur la mère (contrairement à ce qu'elle soit appliquée à l'enfant, comme dans le scénario de test).
Toutefois, la définition de la relation de l'Enfant ne fait PAS de sqlalchemy considérer l'Enfant de la mère. Il n'a pas d'importance où la relation est définie (enfant ou parent), c'est la clé étrangère qui relie les deux tableaux qui détermine qui est le parent et qui est l'enfant.
Il est logique de s'en tenir à une convention, bien, et sur la base de Steven réponse, je suis à la définition de l'ensemble de mon enfant relations sur le parent.
J'ai du mal avec la documentation ainsi, mais a constaté que les docstrings eux-mêmes ont tendance à être plus facile que le manuel. Par exemple, si vous importez une relation à partir de sqlalchemy.l'orm et le faire aider(relation), il vous donnera toutes les options que vous pouvez spécifier pour cascade. La balle pour "delete-orphan" dit, "si un élément de l'enfant, du type avec aucun parent n'est détecté, le marquer pour suppression. Notez que cette option empêche un élément en attente de la classe de leur enfant d'être persisté sans leurs parents."
Je me rends compte votre est plus un problème avec la façon dont la documentation pour la définition des relations parent-enfant. Mais il semble que vous pourriez aussi avoir un problème avec la cascade d'options, parce que "tout le" comprend "supprimer". "supprimer orphelin" est la seule option qui n'est pas inclus dans "tous".
Steven réponse est solide. Je voudrais souligner un autre implication.
En utilisant
relationship
, vous vous faites de l'application de la couche (Flacon), responsable de l'intégrité référentielle. Cela signifie que d'autres processus d'accès à la base de données, non par Flacon, comme un utilitaire de base de données ou une personne qui se connecte à la base de données directement, ne sera pas l'expérience de ces contraintes et pourrait modifier vos données d'une manière qui rompt le modèle logique de données que vous avez travaillé si dur à concevoir.Chaque fois que possible, utiliser les
ForeignKey
approche décrite par d512 et Alex. Le moteur de base est très bonne à l'application de contraintes (un incontournable façon), donc c'est de loin la meilleure stratégie pour le maintien de l'intégrité des données. Le seul moment où vous devez vous appuyer sur une application pour gérer l'intégrité des données est lorsque la base de données ne peut pas les gérer, par exemple, les versions de SQLite qui ne supportent pas les clés étrangères.Si vous avez besoin de créer davantage de liens entre les entités pour activer l'application de comportements tels que la navigation parent-enfant, les relations d'objet, l'utilisation
backref
en conjonction avecForeignKey
.Réponse par Stevan est parfait. Mais si vous obtenez toujours l'erreur. Autre essai possible sur le dessus de qui serait -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Copié à partir du lien-
Petit conseil si vous avez des ennuis avec une clé étrangère de la dépendance, même si vous avez spécifié une cascade de supprimer dans vos modèles.
À l'aide de SQLAlchemy, pour spécifier une cascade de supprimer, vous devriez avoir cascade= "tout supprimer" sur votre table parent. Ok mais alors lorsque vous effectuez quelque chose comme:
Il déclenche réellement une erreur sur une clé étrangère utilisée dans vos enfants tables.
La solution que j'ai utilisé à s'interroger sur l'objet, puis de le supprimer:
Cela devrait supprimer votre enregistrement parent ET tous les enfants y sont associés.