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):
__table= "parent"
id = Column(Integer, primary_key = True)
class Child(Base):
__table= "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
親と子の間には、単純な1対多の関係があります。スクリプトは親を作成し、3つの子を追加してからコミットします。次に、親は削除されますが、子は保持されます。どうして?子をカスケード削除するにはどうすればよいですか?
問題は、sqlalchemyがChild
を親と見なすことです。なぜなら、それはあなたが関係を定義した場所だからです(もちろん、それを「子」と呼んでもかまいません)。
代わりにParent
クラスでリレーションシップを定義すると、動作します:
children = relationship("Child", cascade="all,delete", backref="parent")
(注:文字列として"Child"
:これは宣言スタイルを使用する場合に許可されるため、まだ定義されていないクラスを参照できます)
delete-Orphan
を追加することもできます(delete
は、親が削除されたときに子を削除します。delete-Orphan
は、親から「削除」された子も削除します。親は削除されません)
編集:ちょうど見つけた:あなたが本当にChild
クラスの関係を定義したいなら、あなたはそうすることができますが、あなたはそうします次のように、カスケードを定義する必要がありますbackrefで(明示的にbackrefを作成することにより):
parent = relationship(Parent, backref=backref("children", cascade="all,delete"))
(from sqlalchemy.orm import backref
を意味する)
@Stevenのasnwerは、session.delete()
を介して削除する場合に適しています。ほとんどの場合、session.query().filter().delete()
(メモリに要素を入れず、dbから直接削除する)を使用して削除することに気付きました。このメソッドを使用して、sqlalchemyのcascade='all, delete'
は機能しません。ただし、解決策があります:ON DELETE CASCADE
からdb(注:すべてのデータベースでサポートされているわけではありません)。
class Child(Base):
__table= "children"
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))
class Parent(Base):
__table= "parents"
id = Column(Integer, primary_key=True)
child = relationship(Child, backref="parent", passive_deletes=True)
かなり古い投稿ですが、これに1〜2時間を費やしたので、特にリストされている他のコメントのいくつかが正しくないので、発見を共有したいと思いました。
子テーブルに外部を与えるか、既存のテーブルを変更して、ondelete='CASCADE'
を追加します。
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
そして、次の関係のいずれか:
a)親テーブルでこれ:
children = db.relationship('Child', backref='parent', passive_deletes=True)
b)Orこれは子テーブルで:
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))
最初に、受け入れられた答えが言っているにもかかわらず、親/子関係はrelationship
を使用して確立されず、ForeignKey
を使用して確立されます。親テーブルまたは子テーブルのいずれかにrelationship
を配置でき、正常に機能します。明らかに子テーブルでは、キーワード引数に加えてbackref
関数を使用する必要があります。
第二に、SqlAlchemyは2種類のカスケードをサポートしています。最初の、そして私がお勧めするものは、データベースに組み込まれ、通常、外部キー宣言に対する制約の形式をとります。 PostgreSQLでは、次のようになります。
CONSTRAINT child_parent_id_fkey FOREIGN KEY (parent_id)
REFERENCES parent_table(id) MATCH SIMPLE
ON DELETE CASCADE
つまり、parent_table
からレコードを削除すると、child_table
内の対応するすべての行がデータベースによって削除されます。高速で信頼性が高く、おそらく最善の方法です。次のようにForeignKey
を介してSqlAlchemyでこれを設定します(子テーブル定義の一部):
parent_id = db.Column(db.Integer, db.ForeignKey('parent.id', ondelete='CASCADE'))
parent = db.relationship('Parent', backref=backref('children', passive_deletes=True))
ondelete='CASCADE'
は、テーブルにON DELETE CASCADE
を作成する部分です。
ここには重要な注意事項があります。 passive_deletes=True
でrelationship
が指定されていることに注目してください。あなたがそれを持っていない場合、全体が動作しません。これは、デフォルトで親レコードを削除すると、SqlAlchemyが本当に奇妙なことをするためです。すべての子行の外部キーをNULL
に設定します。したがって、parent_table
から行を削除すると(id
= 5)、基本的に実行されます
UPDATE child_table SET parent_id = NULL WHERE parent_id = 5
なぜこれが必要なのか、私にはわかりません。多くのデータベースエンジンで、有効な外部キーをNULL
に設定して孤児を作成することさえできたら、私は驚くでしょう。悪い考えのように思えますが、多分ユースケースがあります。とにかく、SqlAlchemyにこれを行わせた場合、セットアップしたON DELETE CASCADE
を使用してデータベースが子をクリーンアップできないようにします。これは、削除する子行を知るために、それらの外部キーに依存しているためです。 SqlAlchemyがそれらをすべてNULL
に設定すると、データベースはそれらを削除できません。 passive_deletes=True
を設定すると、SqlAlchemyが外部キーをNULL
アウトできなくなります。
パッシブ削除の詳細については、 SqlAlchemy docs をご覧ください。
もう1つの方法は、SqlAlchemyに実行させることです。これは、cascade
のrelationship
引数を使用して設定されます。親テーブルでリレーションシップが定義されている場合、次のようになります。
children = relationship('Child', cascade='all,delete', backref='parent')
関係が子にある場合、次のようにします。
parent = relationship('Parent', backref=backref('children', cascade='all,delete'))
繰り返しますが、これは子であるため、backref
というメソッドを呼び出し、そこにカスケードデータを配置する必要があります。
これにより、親行を削除すると、SqlAlchemyは実際にdeleteステートメントを実行して、子行をクリーンアップします。これはおそらく、このデータベースで処理できるほど効率的ではないので、お勧めしません。
以下に、サポートするカスケード機能に関する SqlAlchemy docs を示します。
スティーブンは、明示的にbackrefを作成する必要があるという点で正しいです。これにより、カスケードが(テストシナリオのように子に適用されるのではなく)親に適用されます。
ただし、子でリレーションシップを定義しても、sqlalchemyは子を親と見なしません。リレーションシップが定義されている場所(子または親)、どちらが親でどちらが子であるかを決定する2つのテーブルをリンクする外部キーは関係ありません。
ただし、1つの規則に固執することは理にかなっており、スティーブンの応答に基づいて、親に対するすべての子関係を定義しています。
ドキュメントにも苦労しましたが、ドキュメント文字列自体はマニュアルよりも簡単な傾向があることがわかりました。たとえば、sqlalchemy.ormからリレーションシップをインポートしてhelp(relationship)を実行すると、カスケードに指定できるすべてのオプションが提供されます。 「delete-Orphan」の箇条書きには、「親のない子のタイプのアイテムが検出された場合、削除のマークを付けます。このオプションは、子のクラスの保留中のアイテムが親の存在なしで永続化されるのを防ぐことに注意してください」
あなたの問題は、親子関係を定義するためのドキュメントの方法に関するものであったことを理解しています。しかし、「すべて」には「削除」が含まれるため、カスケードオプションにも問題があるように思われます。 「孤児の削除」は、「すべて」に含まれていない唯一のオプションです。
スティーブンの答えは確かです。追加の意味を指摘したいと思います。
relationship
を使用することで、参照整合性を担当するアプリ層(Flask)を作成しています。つまり、データベースユーティリティやデータベースに直接接続している人など、Flaskを介さずにデータベースにアクセスする他のプロセスでは、これらの制約が発生せず、設計に苦労した論理データモデルを壊すような方法でデータが変更される可能性があります。
可能な限り、d512とAlexが説明しているForeignKey
アプローチを使用してください。 DBエンジンは、(やむを得ない方法で)制約を真に強制するのに非常に優れているため、これはデータの整合性を維持するための圧倒的な最良の戦略です。データの整合性を処理するためにアプリに依存する必要があるのは、データベースがそれらを処理できないときだけです。外部キーをサポートしないSQLiteのバージョン。
親子オブジェクトの関係をナビゲートするなどのアプリの動作を有効にするためにエンティティ間のリンクをさらに作成する必要がある場合は、backref
をForeignKey
と組み合わせて使用します。
ステバンによる回答は完璧です。しかし、まだエラーが発生する場合。その上で可能な他の試みは-
http://vincentaudebert.github.io/python/sql/2015/10/09/cascade-delete-sqlalchemy/
リンクからコピー
モデルでカスケード削除を指定した場合でも、外部キーの依存関係で問題が発生した場合のクイックヒント。
SQLAlchemyを使用して、カスケード削除を指定するには、親テーブルでcascade = 'all、delete'が必要です。 Okただし、次のように実行すると:
session.query(models.yourmodule.YourParentTable).filter(conditions).delete()
実際に、子テーブルで使用される外部キーに関するエラーをトリガーします。
私はそれを使用してオブジェクトを照会してから削除するソリューション:
session = models.DBSession()
your_db_object = session.query(models.yourmodule.YourParentTable).filter(conditions).first()
if your_db_object is not None:
session.delete(your_db_object)
これにより、親レコードとそれに関連付けられているすべての子が削除されます。