【问题标题】:How to remove SQLAlchemy Many-To-Many Orphans from database?如何从数据库中删除 SQLAlchemy 多对多孤儿?
【发布时间】:2021-07-27 06:40:00
【问题描述】:

上下文

我有一个用 SQLAlchemy 编写的简单 MySQL 数据库。以下是我的两个模型,Subreddit 和 Keyword,它们具有多对多关系,以及它们的关联表:

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 应该返回 [test_keyword1, test_keyword2, test_keyword3]。

问题

当我删除 test_subreddit 时,test_keyword1、test_keyword2、test_keyword3 仍然保留在数据库中。

我知道,对于多对多关系,技术上没有父级,因此根据这篇文章,级联在技术上不起作用: https://stackoverflow.com/a/803584/10426919.

我试过的

我点击了这个链接:https://github.com/sqlalchemy/sqlalchemy/wiki/ManyToManyOrphan

此链接提供了一个库函数,可以解决我的确切问题。

但是,当通过以下方式集成到我的模型文件中时,该功能不起作用:

方法#1:

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 工作台中检查我的数据库时,Subreddit test_subreddit 被删除了,但关键字 [test_keyword1, test_keyword2, test_keyword3] 仍在数据库中的关键字表下。

方法#2:

我尝试将库函数所基于的实际函数也集成到我的代码中:

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)

同样,尽管没有附加到任何父级,但关键字仍然存在。

我想要完成的事情

当数据库中的孩子不再有父母时,我希望将他们从数据库中删除。我做错了什么?

【问题讨论】:

  • 我无法重现您的问题。 This code 对我来说很好。
  • @Gord,这很有趣。我想知道我使用位于 RDS 中的数据库,而不是文件的本地数据库,是否会导致 auto_delete_orphans 出现问题。其他差异可能是您如何使用声明性基础,而我使用 SQLAlchemy 的 db.Model 来创建我的表。无论如何,我有一个绕过auto_delete_orphans 的解决方案。

标签: sql sqlalchemy many-to-many parent-child


【解决方案1】:

我没有使用auto_delete_orphans,而是创建了一个可以在我想删除子项时调用的方法。此方法检查有问题的孩子,并查看它是否有父母。如果它确实有父级,我们将其保留,但如果它没有父级,则我们删除子级。

鉴于Subreddit 是父级,Keyword 是 Subreddit 的子级,这是我实现此方法的方式。

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()

【讨论】:

    猜你喜欢
    • 2010-10-18
    • 1970-01-01
    • 2016-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多