Bei den Kaskadenoptionen von SQLAlchemy muss mir etwas Triviales fehlen, da ich keine einfache Kaskadenlöschung erhalten kann, um ordnungsgemäß zu funktionieren. Wenn ein übergeordnetes Element gelöscht wird, bleiben die untergeordneten Elemente mit null
Fremdschlüsseln erhalten.
Ich habe hier einen kurzen Testfall gestellt:
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()
Ausgabe:
Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0
Es gibt eine einfache Eins-zu-Viele-Beziehung zwischen Eltern und Kind. Das Skript erstellt ein übergeordnetes Element, fügt 3 untergeordnete Elemente hinzu und schreibt dann fest. Als nächstes wird das übergeordnete Element gelöscht, die untergeordneten Elemente bleiben jedoch bestehen. Warum? Wie lösche ich die Kinderkaskade?
python
database
sqlalchemy
Carl
quelle
quelle
Antworten:
Das Problem ist, dass sqlalchemy
Child
als übergeordnetes Element betrachtet wird , da Sie dort Ihre Beziehung definiert haben (es ist natürlich egal, dass Sie sie "Kind" genannt haben).Wenn Sie
Parent
stattdessen die Beziehung für die Klasse definieren , funktioniert dies:(Hinweis
"Child"
als Zeichenfolge: Dies ist zulässig, wenn der deklarative Stil verwendet wird, damit Sie auf eine Klasse verweisen können, die noch nicht definiert ist.)Möglicherweise möchten Sie auch hinzufügen
delete-orphan
(delete
bewirkt, dass untergeordnete Elemente gelöscht werden, wenn das übergeordnete Element gelöscht wird,delete-orphan
und löscht auch alle untergeordneten Elemente, die vom übergeordneten Element "entfernt" wurden, auch wenn das übergeordnete Element nicht gelöscht wird).BEARBEITEN: gerade herausgefunden: Wenn Sie die Beziehung für die Klasse wirklich definieren möchten
Child
, können Sie dies tun, aber Sie müssen die Kaskade auf der Backref definieren (indem Sie die Backref explizit erstellen), wie folgt:(impliziert
from sqlalchemy.orm import backref
)quelle
Child
Objekt ausparent.children
, sollte das Objekt aus der Datenbank gelöscht werden, oder sollte es die nur als Referenz auf den übergeordneten entfernt werden (dh Satz.parentid
Spalte null, stattdessen die Zeile zu löschen)relationship
diktiert nicht das Eltern-Kind-Setup. Die VerwendungForeignKey
auf einem Tisch ist das, was ihn als Kind einrichtet. Es spielt keine Rolle, ob sich dasrelationship
auf dem Elternteil oder dem Kind befindet.@ Steven's Antwort ist gut, wenn Sie löschen
session.delete()
, was in meinem Fall nie passiert. Mir ist aufgefallen, dass ich die meiste Zeit durch löschesession.query().filter().delete()
(wodurch keine Elemente in den Speicher gestellt und direkt aus der Datenbank gelöscht werden ). Mit dieser Methode funktioniert sqlalchemy'scascade='all, delete'
nicht. Es gibt jedoch eine Lösung:ON DELETE CASCADE
über db (Hinweis: Nicht alle Datenbanken unterstützen dies).quelle
session.query().filter().delete()
das Problem zu nutzen und zu findenpassive_deletes='all'
, dass die untergeordneten Elemente von der Datenbankkaskade gelöscht werden, wenn das übergeordnete Element gelöscht wird. Mitpassive_deletes=True
wurden untergeordnete Objekte getrennt (übergeordnetes Element auf NULL gesetzt), bevor das übergeordnete Objekt gelöscht wurde, sodass die Datenbankkaskade nichts unternahm.passive_deletes=True
dies in diesem Szenario ordnungsgemäß funktioniert.Ziemlich alter Beitrag, aber ich habe nur ein oder zwei Stunden damit verbracht, deshalb wollte ich meine Ergebnisse teilen, zumal einige der anderen aufgeführten Kommentare nicht ganz richtig sind.
TL; DR
Geben Sie der untergeordneten Tabelle eine Fremdtabelle oder ändern Sie die vorhandene und fügen Sie Folgendes hinzu
ondelete='CASCADE'
:Und eine der folgenden Beziehungen:
a) Dies in der übergeordneten Tabelle:
b) Oder dies auf dem Kindertisch:
Einzelheiten
Zunächst einmal wird die Eltern-Kind-Beziehung trotz der akzeptierten Antwort nicht durch Verwendung hergestellt
relationship
, sondern durch VerwendungForeignKey
. Sie können dierelationship
entweder auf die übergeordnete oder die untergeordnete Tabelle setzen und es wird gut funktionieren. Obwohl Sie anscheinend in den untergeordneten Tabellen diebackref
Funktion zusätzlich zum Schlüsselwortargument verwenden müssen.Option 1 (bevorzugt)
Zweitens unterstützt SqlAlchemy zwei verschiedene Arten der Kaskadierung. Die erste und die von mir empfohlene ist in Ihre Datenbank integriert und hat normalerweise die Form einer Einschränkung für die Fremdschlüsseldeklaration. In PostgreSQL sieht es so aus:
Dies bedeutet, dass beim Löschen eines Datensatzes
parent_table
alle entsprechenden Zeilen inchild_table
von der Datenbank für Sie gelöscht werden. Es ist schnell und zuverlässig und wahrscheinlich die beste Wahl. Sie richten dies in SqlAlchemy folgendermaßen einForeignKey
(Teil der Definition der untergeordneten Tabelle):Das
ondelete='CASCADE'
ist der Teil, der dasON DELETE CASCADE
auf dem Tisch erstellt.Erwischt!
Hier gibt es eine wichtige Einschränkung. Beachten Sie, wie ich eine mit
relationship
angegeben habepassive_deletes=True
? Wenn Sie das nicht haben, wird das Ganze nicht funktionieren. Dies liegt daran, dass SqlAlchemy beim Löschen eines übergeordneten Datensatzes standardmäßig etwas wirklich Seltsames tut. Es setzt die Fremdschlüssel aller untergeordneten Zeilen aufNULL
. Wenn Sie also eine Zeile ausparent_table
whereid
= 5 löschen , wird sie grundsätzlich ausgeführtWarum du das willst, weiß ich nicht. Es würde mich wundern, wenn Sie in vielen Datenbankmodulen sogar einen gültigen Fremdschlüssel festlegen könnten
NULL
, um eine Waise zu erstellen. Scheint eine schlechte Idee zu sein, aber vielleicht gibt es einen Anwendungsfall. Wenn Sie dies von SqlAlchemy ausführen lassen, verhindern Sie auf jeden Fall, dass die Datenbank die untergeordneten Elemente mit dem vonON DELETE CASCADE
Ihnen eingerichteten bereinigen kann. Dies liegt daran, dass diese Fremdschlüssel erforderlich sind, um zu wissen, welche untergeordneten Zeilen gelöscht werden sollen. Sobald SqlAlchemy sie alle festgelegt hat,NULL
kann die Datenbank sie nicht mehr löschen. Durch Einstellen von wirdpassive_deletes=True
verhindert, dass SqlAlchemyNULL
die Fremdschlüssel herausgibt .Weitere Informationen zu passiven Löschvorgängen finden Sie in den SqlAlchemy-Dokumenten .
Option 2
Die andere Möglichkeit besteht darin, SqlAlchemy dies für Sie tun zu lassen. Dies wird mit dem
cascade
Argument von eingerichtetrelationship
. Wenn Sie die Beziehung in der übergeordneten Tabelle definiert haben, sieht sie folgendermaßen aus:Wenn die Beziehung auf dem Kind liegt, machen Sie es so:
Auch dies ist das untergeordnete Element. Sie müssen also eine aufgerufene Methode aufrufen
backref
und die Kaskadendaten dort ablegen.Wenn Sie eine übergeordnete Zeile löschen, führt SqlAlchemy tatsächlich Löschanweisungen aus, damit Sie die untergeordneten Zeilen bereinigen können. Dies ist wahrscheinlich nicht so effizient wie das Handhaben dieser Datenbank, wenn ich es für Sie nicht empfehle.
Hier sind die SqlAlchemy-Dokumente zu den unterstützten Kaskadenfunktionen .
quelle
Column
in der untergeordneten Tabelle alsForeignKey('parent.id', ondelete='cascade', onupdate='cascade')
nicht deklariert zu deklarieren ? Ich habe erwartet, dass die Kinder gelöscht werden, wenn auch die übergeordnete Tabellenzeile gelöscht wird. Stattdessen setzt SQLA die untergeordneten Elemente entweder auf aparent.id=NULL
oder belässt sie "wie sie sind", löscht sie jedoch nicht. Das ist nach der ursprünglichen Definition derrelationship
im Elternteil alschildren = relationship('Parent', backref='parent')
oderrelationship('Parent', backref=backref('parent', passive_deletes=True))
; DB zeigtcascade
Regeln in der DDL (SQLite3-basierter Proof-of-Concept). Gedanken?backref=backref('parent', passive_deletes=True)
die folgende Warnung erhalte: DiesSAWarning: On Parent.children, 'passive_deletes' is normally configured on one-to-many, one-to-one, many-to-many relationships only. "relationships only." % self
deutet darauf hin, dass die Verwendungpassive_deletes=True
in dieser (offensichtlichen) Eins-zu-Viele-Eltern-Kind-Beziehung aus irgendeinem Grund nicht gefällt .delete
überflüssig incascade='all,delete'
?delete
ist redundant incascade='all,delete'
, da nach der SQLAlchemy der docs ,all
ist ein Synonym für:save-update, merge, refresh-expire, expunge, delete
Steven hat insofern Recht, als Sie die Backref explizit erstellen müssen. Dies führt dazu, dass die Kaskade auf die Eltern angewendet wird (im Gegensatz zu der, die wie im Testszenario auf das Kind angewendet wird).
Das Definieren der Beziehung für das Kind führt jedoch NICHT dazu, dass sqlalchemy Child als Elternteil betrachtet. Es spielt keine Rolle, wo die Beziehung definiert ist (Kind oder Elternteil), es ist der Fremdschlüssel, der die beiden Tabellen verbindet, die bestimmen, welches das Elternteil und welches das Kind ist.
Es ist jedoch sinnvoll, sich an eine Konvention zu halten, und basierend auf Stevens Antwort definiere ich alle meine Kinderbeziehungen für die Eltern.
quelle
Ich hatte auch Probleme mit der Dokumentation, stellte jedoch fest, dass die Dokumentzeichenfolgen selbst in der Regel einfacher sind als das Handbuch. Wenn Sie beispielsweise eine Beziehung aus sqlalchemy.orm importieren und Hilfe (Beziehung) ausführen, erhalten Sie alle Optionen, die Sie für die Kaskade angeben können. Die Kugel für
delete-orphan
sagt:Mir ist klar, dass Ihr Problem eher in der Dokumentation der Definition von Eltern-Kind-Beziehungen lag. Aber es schien, dass Sie auch ein Problem mit den Kaskadenoptionen haben könnten, weil
"all"
enthält"delete"
."delete-orphan"
ist die einzige Option, die nicht in enthalten ist"all"
.quelle
help(..)
auf densqlalchemy
Objekten hilft sehr! Vielen Dank :-))) ! PyCharm zeigt im Kontext nichts an und hat offenbar vergessen, das zu überprüfenhelp
. Vielen Dank!Stevens Antwort ist solide. Ich möchte auf eine zusätzliche Implikation hinweisen.
Durch die Nutzung
relationship
Sie die App-Ebene (Flask) für die referenzielle Integrität verantwortlich. Das bedeutet, dass andere Prozesse, die nicht über Flask auf die Datenbank zugreifen, wie ein Datenbankdienstprogramm oder eine Person, die eine direkte Verbindung zur Datenbank herstellt, diese Einschränkungen nicht erfahren und Ihre Daten so ändern können, dass das logische Datenmodell, an dem Sie so hart gearbeitet haben, beschädigt wird .Verwenden Sie nach Möglichkeit den
ForeignKey
von d512 und Alex beschriebenen Ansatz. Die DB-Engine ist sehr gut darin, Einschränkungen wirklich (auf unvermeidbare Weise) durchzusetzen. Dies ist bei weitem die beste Strategie zur Aufrechterhaltung der Datenintegrität. Sie müssen sich nur dann auf eine App verlassen, um die Datenintegrität zu gewährleisten, wenn die Datenbank diese nicht verarbeiten kann, z. B. Versionen von SQLite, die keine Fremdschlüssel unterstützen.Wenn Sie eine weitere Verknüpfung zwischen Entitäten erstellen müssen, um App-Verhalten wie das Navigieren in Eltern-Kind-Objektbeziehungen zu ermöglichen, verwenden Sie
backref
in Verbindung mitForeignKey
.quelle
Die Antwort von Stevan ist perfekt. Aber wenn Sie immer noch den Fehler bekommen. Ein anderer möglicher Versuch wäre -
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
Vom Link kopiert
Schneller Tipp, wenn Sie Probleme mit einer Fremdschlüsselabhängigkeit haben, auch wenn Sie in Ihren Modellen eine Kaskadenlöschung angegeben haben.
Verwenden Sie SQLAlchemy, um eine Kaskadenlöschung anzugeben, die Sie
cascade='all, delete'
in Ihrer übergeordneten Tabelle haben sollten. Ok, aber dann, wenn Sie etwas ausführen wie:Es löst tatsächlich einen Fehler über einen Fremdschlüssel aus, der in Ihren untergeordneten Tabellen verwendet wird.
Die Lösung, mit der ich das Objekt abgefragt und dann gelöscht habe:
Dies sollte Ihren übergeordneten Datensatz UND alle damit verbundenen untergeordneten Datensätze löschen.
quelle
.first()
erforderlich? Welche Filterbedingungen geben eine Liste von Objekten zurück und alles muss gelöscht werden? Erhält der Aufruf nicht.first()
nur das erste Objekt? @ PrashantDie Antwort von Alex Okrushko hat für mich fast am besten funktioniert. Verwendet ondelete = 'CASCADE' und passive_deletes = True kombiniert. Aber ich musste etwas extra tun, damit es für SQLite funktioniert.
Stellen Sie sicher, dass Sie diesen Code hinzufügen, um sicherzustellen, dass er für SQLite funktioniert.
Von hier gestohlen: SQLAlchemy-Ausdruckssprache und SQLites bei Löschkaskade
quelle
TLDR: Wenn die oben genannten Lösungen nicht funktionieren, fügen Sie Ihrer Spalte nullable = False hinzu.
Ich möchte hier einen kleinen Punkt für einige Leute hinzufügen, die möglicherweise nicht die Kaskadenfunktion erhalten, um mit den vorhandenen Lösungen zu arbeiten (die großartig sind). Der Hauptunterschied zwischen meiner Arbeit und dem Beispiel war, dass ich automap verwendet habe. Ich weiß nicht genau, wie dies die Einrichtung von Kaskaden beeinträchtigen könnte, aber ich möchte darauf hinweisen, dass ich es verwendet habe. Ich arbeite auch mit einer SQLite-Datenbank.
Ich habe jede hier beschriebene Lösung ausprobiert, aber für Zeilen in meiner untergeordneten Tabelle wurde der Fremdschlüssel beim Löschen der übergeordneten Zeile weiterhin auf null gesetzt. Ich hatte alle Lösungen hier ohne Erfolg ausprobiert. Die Kaskade funktionierte jedoch, nachdem ich die untergeordnete Spalte mit dem Fremdschlüssel auf nullable = False gesetzt hatte.
Auf dem Kindertisch fügte ich hinzu:
Bei diesem Setup funktionierte die Kaskade wie erwartet.
quelle