Je dois manquer quelque chose de trivial avec les options en cascade de SQLAlchemy car je ne peux pas obtenir une simple suppression en cascade pour fonctionner correctement - si un élément parent est supprimé, les enfants persistent, avec null
des clés étrangères.
J'ai mis un cas de test concis 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()
Production:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Il existe une relation simple, un-à-plusieurs entre le parent et l'enfant. Le script crée un parent, ajoute 3 enfants, puis valide. Ensuite, il supprime le parent, mais les enfants persistent. Pourquoi? Comment faire supprimer les enfants en cascade?
Réponses:
Le problème est que sqlalchemy considère
Child
comme le parent, car c'est là que vous avez défini votre relation (il ne se soucie pas que vous l' appeliez "Enfant" bien sûr).Si vous définissez
Parent
plutôt la relation sur la classe, cela fonctionnera:(note
"Child"
sous forme de chaîne: ceci est autorisé lors de l'utilisation du style déclaratif, afin que vous puissiez faire référence à une classe qui n'est pas encore définie)Vous voudrez peut-être également ajouter
delete-orphan
(delete
entraîne ladelete-orphan
suppression des enfants lorsque le parent est supprimé, supprime également tous les enfants qui ont été "supprimés" du parent, même si le parent n'est pas supprimé)EDIT: vient de découvrir: si vous voulez vraiment définir la relation sur la
Child
classe, vous pouvez le faire, mais vous devrez définir la cascade sur la backref (en créant explicitement la backref), comme ceci:(sous-entendu
from sqlalchemy.orm import backref
)la source
Child
objet deparent.children
, cet objet doit-il être supprimé de la base de données, ou doit-il seulement supprimer sa référence au parent (c'est-à-dire définir laparentid
colonne sur null, au lieu de supprimer la ligne)relationship
ne dicte pas la configuration parent-enfant. UtiliserForeignKey
sur une table est ce qui le définit comme l'enfant. Peu importe sirelationship
c'est le parent ou l'enfant.La réponse de @ Steven est bonne lorsque vous supprimez
session.delete()
ce qui n'arrive jamais dans mon cas. J'ai remarqué que la plupart du temps, je supprime viasession.query().filter().delete()
(qui ne met pas d'éléments dans la mémoire et supprime directement de db). En utilisant cette méthode, sqlalchemycascade='all, delete'
ne fonctionne pas. Il existe cependant une solution:ON DELETE CASCADE
via db (note: toutes les bases de données ne le prennent pas en charge).la source
session.query().filter().delete()
et j'ai du mal à trouver le problèmepassive_deletes='all'
pour que les enfants soient supprimés par la cascade de bases de données lorsque le parent est supprimé. Avecpassive_deletes=True
, les objets enfants étaient dissociés (le parent défini sur NULL) avant la suppression du parent, de sorte que la cascade de bases de données ne faisait rien.passive_deletes=True
cela fonctionne correctement dans ce scénario.Un article assez ancien, mais je viens de passer une heure ou deux à ce sujet, alors je voulais partager ma découverte, d'autant plus que certains des autres commentaires énumérés ne sont pas tout à fait corrects.
TL; DR
Donnez à la table enfant un étranger ou modifiez l'existant, en ajoutant
ondelete='CASCADE'
:Et l' une des relations suivantes:
a) Ceci sur la table parent:
b) Ou ceci sur la table enfant:
Détails
Tout d'abord, malgré ce que dit la réponse acceptée, la relation parent / enfant n'est pas établie en utilisant
relationship
, elle est établie en utilisantForeignKey
. Vous pouvez mettre lerelationship
sur les tables parent ou enfant et cela fonctionnera très bien. Bien que, apparemment sur les tables enfants, vous devez utiliser labackref
fonction en plus de l'argument mot-clé.Option 1 (préférée)
Deuxièmement, SqlAlchemy prend en charge deux types différents de cascade. Le premier, et celui que je recommande, est intégré à votre base de données et prend généralement la forme d'une contrainte sur la déclaration de clé étrangère. Dans PostgreSQL, cela ressemble à ceci:
Cela signifie que lorsque vous supprimez un enregistrement de
parent_table
, toutes les lignes correspondantes danschild_table
seront supprimées pour vous par la base de données. C'est rapide et fiable et probablement votre meilleur pari. Vous configurez cela dans SqlAlchemyForeignKey
comme ceci (une partie de la définition de la table enfant):Le
ondelete='CASCADE'
est la partie qui crée leON DELETE CASCADE
sur la table.Je t'ai eu!
Il y a une mise en garde importante ici. Remarquez comment j'ai
relationship
spécifié avecpassive_deletes=True
? Si vous ne l'avez pas, tout ne fonctionnera pas. En effet, 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 surNULL
. Donc, si vous supprimez une ligne d'parent_table
oùid
= 5, alors il exécutera essentiellementPourquoi tu voudrais ça, je n'en ai aucune idée. Je serais surpris si de nombreux moteurs de base de données vous permettaient même de définir une clé étrangère valide
NULL
, créant ainsi un orphelin. Cela semble être une mauvaise idée, mais il y a peut-être un cas d'utilisation. Quoi qu'il en soit, si vous laissez SqlAlchemy faire cela, vous empêcherez la base de données de pouvoir nettoyer les enfants à l'aide du fichierON DELETE CASCADE
que vous avez configuré. En effet, il s'appuie sur ces clés étrangères pour savoir quelles lignes enfants supprimer. Une fois que SqlAlchemy les a tous définis surNULL
, la base de données ne peut pas les supprimer. La définition depassive_deletes=True
empêche SqlAlchemy d'entrerNULL
les clés étrangères.Vous pouvez en savoir plus sur les suppressions passives dans la documentation SqlAlchemy .
Option 2
L'autre façon dont vous pouvez le faire est de laisser SqlAlchemy le faire pour vous. Ceci est configuré à l'aide de l'
cascade
argument durelationship
. Si vous avez la relation définie sur la table parent, cela ressemble à ceci:Si la relation concerne l'enfant, procédez comme suit:
Encore une fois, c'est l'enfant, vous devez donc appeler une méthode appelée
backref
et y placer les données en cascade.Avec cela en place, lorsque vous supprimez une ligne parent, SqlAlchemy exécutera en fait des instructions de suppression pour vous permettre de nettoyer les lignes enfants. Ce ne sera probablement pas aussi efficace que de laisser cette base de données gérer si c'est pour vous, donc je ne le recommande pas.
Voici la documentation SqlAlchemy sur les fonctionnalités en cascade qu'il prend en charge.
la source
Column
dans la table enfant commeForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
ne fonctionne pas non plus? Je m'attendais à ce que les enfants soient supprimés lorsque leur ligne de table parent a également été supprimée. Au lieu de cela, SQLA définit les enfants sur aparent.id=NULL
ou les laisse "tels quels ", mais aucune suppression. C'est après avoir défini à l'origine lerelationship
dans le parent commechildren = relationship('Parent', backref='parent')
ourelationship('Parent', backref=backref('parent', passive_deletes=True))
; DB affiche lescascade
règles dans le DDL (preuve de concept basée sur SQLite3). Pensées?backref=backref('parent', passive_deletes=True)
je reçois l'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
suggérant qu'il n'aime pas l'utilisation depassive_deletes=True
dans cette relation parent-enfant (évidente) un-à-plusieurs pour une raison quelconque.delete
redondantcascade='all,delete'
?delete
EST redondant danscascade='all,delete'
, puisque selon la documentation de SQLAlchemy ,all
est synonyme de:save-update, merge, refresh-expire, expunge, delete
Steven a raison en ce sens que vous devez créer explicitement la référence arrière, ce qui entraîne l'application de la cascade sur le parent (par opposition à son application à l'enfant comme dans le scénario de test).
Cependant, la définition de la relation sur Child ne fait PAS que sqlalchemy considère Child comme le parent. Peu importe où la relation est définie (enfant ou parent), c'est la clé étrangère qui relie les deux tables qui détermine lequel est le parent et lequel est l'enfant.
Cependant, il est logique de s'en tenir à une convention et, sur la base de la réponse de Steven, je définis toutes les relations de mon enfant sur le parent.
la source
J'ai également eu du mal avec la documentation, mais j'ai trouvé que les docstrings eux-mêmes ont tendance à être plus faciles que le manuel. Par exemple, si vous importez une relation depuis sqlalchemy.orm et que vous faites de l'aide (relation), cela vous donnera toutes les options que vous pouvez spécifier pour cascade. La balle pour
delete-orphan
dit:Je sais que votre problème concernait davantage la manière dont la documentation pour définir les relations parents-enfants. Mais il semble que vous ayez peut-être également un problème avec les options en cascade, car
"all"
comprend"delete"
."delete-orphan"
est la seule option non incluse dans"all"
.la source
help(..)
lessqlalchemy
objets aide beaucoup! Merci :-))) ! PyCharm n'affiche rien dans les docks contextuels et a clairement oublié de vérifier le fichierhelp
. Merci beaucoup!La réponse de Steven est solide. Je voudrais souligner une implication supplémentaire.
En utilisant
relationship
, vous rendez la couche d'application (Flask) responsable de l'intégrité référentielle. Cela signifie que d'autres processus qui accèdent à la base de données sans passer par Flask, comme un utilitaire de base de données ou une personne se connectant directement à la base de données, ne subiront pas ces contraintes et pourraient modifier vos données d'une manière qui rompt le modèle de données logique que vous avez travaillé si dur à concevoir. .Dans la mesure du possible, utilisez l'
ForeignKey
approche décrite par d512 et Alex. Le moteur de base de données est très efficace pour vraiment appliquer les contraintes (de manière inévitable), c'est donc de loin la meilleure stratégie pour maintenir l'intégrité des données. Le seul moment où vous devez vous fier à 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 prennent pas en charge les clés étrangères.Si vous avez besoin de créer davantage de liens entre les entités pour activer des comportements d'application tels que la navigation dans les relations d'objet parent-enfant, utilisez-le
backref
conjointement avecForeignKey
.la source
La réponse de Stevan est parfaite. Mais si vous obtenez toujours l'erreur. Un autre essai possible en plus de cela serait -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Copié à partir du lien-
Astuce rapide si vous rencontrez des problèmes avec une dépendance de clé étrangère même si vous avez spécifié une suppression en cascade dans vos modèles.
En utilisant SQLAlchemy, pour spécifier une suppression en cascade que vous devez avoir
cascade='all, delete'
sur votre table parent. Ok mais alors quand vous exécutez quelque chose comme:Il déclenche en fait une erreur sur une clé étrangère utilisée dans vos tables enfants.
La solution que je l'ai utilisée pour interroger l'objet puis le supprimer:
Cela devrait supprimer votre enregistrement parent ET tous les enfants qui lui sont associés.
la source
.first()
nécessaire? Quelles conditions de filtrage renvoient une liste d'objets et tout doit être supprimé? L'appel.first()
n'obtient -il pas seulement le premier objet? @PrashantLa réponse d'Alex Okrushko a presque fonctionné le mieux pour moi. Utilisé ondelete = 'CASCADE' et passive_deletes = True combinés. Mais j'ai dû faire quelque chose de plus pour que cela fonctionne pour sqlite.
Assurez-vous d'ajouter ce code pour vous assurer qu'il fonctionne pour sqlite.
Volé d'ici: langage d'expression SQLAlchemy et SQLite sur la cascade de suppression
la source
TLDR: Si les solutions ci-dessus ne fonctionnent pas, essayez d'ajouter nullable = False à votre colonne.
Je voudrais ajouter un petit point ici pour certaines personnes qui ne peuvent pas faire fonctionner la fonction de cascade avec les solutions existantes (qui sont excellentes). La principale différence entre mon travail et l'exemple était que j'utilisais automap. Je ne sais pas exactement comment cela pourrait interférer avec la configuration des cascades, mais je tiens à noter que je l'ai utilisé. Je travaille également avec une base de données SQLite.
J'ai essayé toutes les solutions décrites ici, mais les lignes de ma table enfant ont continué à avoir leur clé étrangère définie sur null lorsque la ligne parent a été supprimée. J'avais essayé toutes les solutions ici en vain. Cependant, la cascade a fonctionné une fois que j'ai défini la colonne enfant avec la clé étrangère sur nullable = False.
Sur la table enfant, j'ai ajouté:
Avec cette configuration, la cascade a fonctionné comme prévu.
la source