如何从数据库中删除 SQLAlchemy 多对多孤儿?
How to remove SQLAlchemy Many-To-Many Orphans from database?
上下文
我有一个用 SQLAlchemy 编写的简单 MySQL 数据库。以下是我的两个模型,Subreddit 和 Keyword,它们具有多对多关系,以及它们的关联 table:
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
作为测试数据,我创建了以下数据集:
Subreddit:
test_subreddit
Keywords:
test_keyword1
test_keyword2
test_keyword3
也就是说test_subreddit.keywords应该return[test_keyword1,test_keyword2,test_keyword3].
问题
当我删除 test_subreddit、test_keyword1、test_keyword2 时,test_keyword3 仍然保留在数据库中。
我知道对于多对多关系,从技术上讲没有父级关系,因此从技术上讲级联不会根据此 post 工作:
.
我试过的
我关注了这个link:https://github.com/sqlalchemy/sqlalchemy/wiki/ManyToManyOrphan。
这个 link 提供了一个库函数,应该可以解决我的确切问题。
但是,当通过以下方式集成到我的模型文件中时,该功能不起作用:
方法一:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans <------ # library
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
auto_delete_orphans(Subreddit.keywords) <------ # Library function
不过,这个函数好像没什么作用。没有错误输出来帮助指导我朝着正确的方向前进。当我在 MySQL workbench 中检查我的数据库时,Subreddit test_subreddit 被删除,但是关键字 [test_keyword1、test_keyword2、test_keyword3 ] 仍在关键字 table.
下的数据库中
方法二:
我尝试将库函数所基于的实际函数也集成到我的代码中:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans
# for deleting many-to-many "orphans".
from sqlalchemy import event, create_engine
from sqlalchemy.orm import attributes, sessionmaker
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
engine = create_engine("mysql://", echo=True)
Session = sessionmaker(bind=engine)
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
# optional: look through Session state to see if we want
# to emit a DELETE for orphan Tags
flag = False
for instance in session.dirty:
if isinstance(instance, Subreddit) and \
attributes.get_history(instance, 'keywords').deleted:
flag = True
break
for instance in session.deleted:
if isinstance(instance, Subreddit):
flag = True
break
# emit a DELETE for all orphan Tags. This is safe to emit
# regardless of "flag", if a less verbose approach is
# desired.
if flag:
session.query(Keyword).\
filter(~Keyword.subreddits.any()).\
delete(synchronize_session=False)
同样,尽管关键字没有附加到父级,但仍然存在。
我想要完成的事情
当数据库中的子项不再有父项时,我希望将它们从数据库中删除。我做错了什么?
我没有使用 auto_delete_orphans
,而是创建了一个可以在我想删除 children 时调用的方法。此方法检查有问题的 child,并查看它是否有任何 parent。如果它确实有 parent,我们保留它,但如果它没有 parent,我们然后删除 children.
下面是我实现这个方法的方法,假设 Subreddit
是 parent 而 Keyword
是 Subreddit 的 child。
def check_for_keyword_orphans(keyword):
# check if each keyword has an associated subreddit
if len(keyword.subreddits) == 0:
db.session.delete(keyword)
return True # keyword deleted
else:
return False # keyword has an associated subreddit
下面是我在 API 路线中使用该方法的方式:
keywords = subreddit.keywords
for keyword in keywords:
check_for_keyword_orphans(keyword)
db.session.commit()
上下文
我有一个用 SQLAlchemy 编写的简单 MySQL 数据库。以下是我的两个模型,Subreddit 和 Keyword,它们具有多对多关系,以及它们的关联 table:
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
作为测试数据,我创建了以下数据集:
Subreddit:
test_subreddit
Keywords:
test_keyword1
test_keyword2
test_keyword3
也就是说test_subreddit.keywords应该return[test_keyword1,test_keyword2,test_keyword3].
问题
当我删除 test_subreddit、test_keyword1、test_keyword2 时,test_keyword3 仍然保留在数据库中。
我知道对于多对多关系,从技术上讲没有父级关系,因此从技术上讲级联不会根据此 post 工作: .
我试过的
我关注了这个link:https://github.com/sqlalchemy/sqlalchemy/wiki/ManyToManyOrphan。
这个 link 提供了一个库函数,应该可以解决我的确切问题。
但是,当通过以下方式集成到我的模型文件中时,该功能不起作用:
方法一:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans <------ # library
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
auto_delete_orphans(Subreddit.keywords) <------ # Library function
不过,这个函数好像没什么作用。没有错误输出来帮助指导我朝着正确的方向前进。当我在 MySQL workbench 中检查我的数据库时,Subreddit test_subreddit 被删除,但是关键字 [test_keyword1、test_keyword2、test_keyword3 ] 仍在关键字 table.
下的数据库中方法二:
我尝试将库函数所基于的实际函数也集成到我的代码中:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans
# for deleting many-to-many "orphans".
from sqlalchemy import event, create_engine
from sqlalchemy.orm import attributes, sessionmaker
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
engine = create_engine("mysql://", echo=True)
Session = sessionmaker(bind=engine)
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
# optional: look through Session state to see if we want
# to emit a DELETE for orphan Tags
flag = False
for instance in session.dirty:
if isinstance(instance, Subreddit) and \
attributes.get_history(instance, 'keywords').deleted:
flag = True
break
for instance in session.deleted:
if isinstance(instance, Subreddit):
flag = True
break
# emit a DELETE for all orphan Tags. This is safe to emit
# regardless of "flag", if a less verbose approach is
# desired.
if flag:
session.query(Keyword).\
filter(~Keyword.subreddits.any()).\
delete(synchronize_session=False)
同样,尽管关键字没有附加到父级,但仍然存在。
我想要完成的事情
当数据库中的子项不再有父项时,我希望将它们从数据库中删除。我做错了什么?
我没有使用 auto_delete_orphans
,而是创建了一个可以在我想删除 children 时调用的方法。此方法检查有问题的 child,并查看它是否有任何 parent。如果它确实有 parent,我们保留它,但如果它没有 parent,我们然后删除 children.
下面是我实现这个方法的方法,假设 Subreddit
是 parent 而 Keyword
是 Subreddit 的 child。
def check_for_keyword_orphans(keyword):
# check if each keyword has an associated subreddit
if len(keyword.subreddits) == 0:
db.session.delete(keyword)
return True # keyword deleted
else:
return False # keyword has an associated subreddit
下面是我在 API 路线中使用该方法的方式:
keywords = subreddit.keywords
for keyword in keywords:
check_for_keyword_orphans(keyword)
db.session.commit()