SQL Alchemy 默认值函数,用于在一组唯一的父子记录中模拟自动增量

Posted

技术标签:

【中文标题】SQL Alchemy 默认值函数,用于在一组唯一的父子记录中模拟自动增量【英文标题】:SQL Alchemy default value function for simulating autoincrement within a unique group of parent-child records 【发布时间】:2010-12-24 15:07:44 【问题描述】:

我有一个小问题,我认为 SQL Alchemy 应该可以轻松处理它,但我似乎无法正确解决。我有两张表,一张是父表,另一张是子表。对于每个子记录,它需要一个唯一的 ID,但仅限于唯一父记录的上下文。

我正在使用声明式基础方法。

我使用 FK 和关系函数设置父子关系。我想要实现的是获得类似于伪自动增量函数的东西,它将查找类别唯一名称组中的最大 CategoryID 值并将其递增一。我尝试使用各种默认函数,但遇到的问题是在插入时无法指定 CategoryUniqueName。我找不到传递 CategoryItems.CategoryUniqueName 的当前值的方法,以便在尝试选择类似 func.max(CategoryItems.CategoryID) 时应用正确的过滤器。如果我对查询进行硬编码,它就可以正常工作。这就是我认为应该可行的方法,但是我再次找不到为过滤器指定唯一值的方法。

unique_group='my_group'
result=con.query(func.max(CategoryItems.CategoryID)).filter(and_(
        CategoryItems.CategoryUniqueName==unique_group, 
    )).one()

类如下所示。非常感谢有关如何在标准 SQL Alchemy 中完成此操作的一些指导。我知道我总是可以查找值并直接在同一个事务中指定它,但我正在尝试提出一种独立的 SQL Alchemy 方法,它不需要其他地方的额外逻辑。

class Category(Base):
    __tablename__ = 'parent_table'
    __table_args__ = 'mysql_engine':'InnoDB', 'useexisting':True

    CategoryUniqueName = Column(Unicode(255), primary_key=True)
    CategoryGroupName = Column(Unicode(255), nullable=False)
    CategoryGroupMemo = Column(UnicodeText)
    SortOrder = Column(Integer, index=True)
    IsLocked = Column(Boolean, default=0)

class CategoryItems(Base):
    __tablename__ = 'child_table'
    __table_args__ = 'mysql_engine':'InnoDB', 'useexisting':True

    CategoryUniqueName = Column(Unicode(255), ForeignKey(Category.CategoryUniqueName), primary_key=True)
    CategoryID = Column(Integer, primary_key=True, autoincrement=False)
    CategoryName = Column(Unicode(255), nullable=False, index=True)
    CategoryMemo = Column(UnicodeText)
    CategoryImage = Column(Unicode(255))
    CategoryFlex1 = Column(Unicode(255), index=True)
    CategoryFlex2 = Column(Unicode(255), index=True)
    CategoryFlex3 = Column(Unicode(255), index=True)
    SortOrder = Column(Integer, index=True)

    category_group = relation(
        Category, 
        backref=backref(
            'items', 
            order_by=SortOrder, 
            collection_class=ordering_list('SortOrder'), 
            cascade="all, delete, delete-orphan"
    ))

【问题讨论】:

【参考方案1】:

我看到了 3 条路要走:

    最明显和有据可查的。使用 before_insert() 钩子替换插入的参数创建映射器扩展。 将函数作为default 参数传递。使用 context 参数调用此函数,其中包含您需要的所有数据:context.compiled_parameters[0]['CategoryUniqueName']context.connection。 在server_default 参数中传递FetchedValue() 并使用触发器来完成工作服务器端。

所有这些解决方案都有 ddaa 提到的竞争条件。如果出现竞争条件,您的代码不会破坏数据库状态,但在正确定义主键时会因异常而失败(您的代码并非如此!)。在极少数情况下,某些应用程序失败(在 Web 应用程序中显示 500 页)可能是可以接受的。

请注意,您已将CategoryID 定义为主键。这将不允许对 CategoryUniqueName 列的不同值重复使用相同的数字。您必须将其更改为 2 列的复合主索引。

【讨论】:

【参考方案2】:

感谢丹尼斯的洞察力,你是正确的。我使用了选项 1 和 2,它们工作得非常好。 context 参数是选项 2 的关键。我没有意识到它是自动传递的。我确实注意到的一件事是,即使在单个用户提交多条记录的情况下,选项 1 也会引入竞争条件。我认为这与冲洗和节省时间有关。但是选项 2 效果很好。

这是现在从默认参数调用的小函数:

def getNextId(context):
    unique_name=context.compiled_parameters[0]['CategoryUniqueName']
    sql = """
        SELECT MAX(CategoryID)
        FROM child_table
        WHERE CategoryUniqueName='%s'""" % (unique_name, )

    result = context.connection.execute(sql).fetchone()[0]
    if result > 0:
         return result + 1
    else:
        return 1 

【讨论】:

对不起。我是 Stack Overflow 的新手。刚刚接受了你上面的回答。【参考方案3】:

那么,您想要实现的是让每组具有不同 CategoryUniqueName 的 CategoryItem 的 CategoryId 分别自动递增?

如果这是正确的,那么您当前的方法(在您要添加到的 CategoryItems 子集中获取 CategoryId 的当前最大值)就被打破了。它有一个内在的竞争条件:并发插入将使用相同的 CategoryId。

您真的需要单独增加您的 CategoryId 吗?为什么不直接使用正常的自动增量功能。给定 CategoryUniqueName 的序列 CategoryId 会有漏洞,但这真的有问题吗?

如果您需要连续的序列号,则需要使用一些手动锁定来防止竞争条件。

【讨论】:

我明白你对比赛条件的看法。如果我可以锁定事务以防止竞争条件,有没有办法将 CategoryItems.CategoryUniqueName 的当前值传递给函数?我认为在插入之前将当前字段值传递给函数的能力对于其他事情(例如基于字段生成哈希)是有益的。要回答全局自动增量方法问题......是的,我可以更改为,但它需要一些不平凡的努力来处理基于当前 CategoryID 值的现有逻辑。不反对,但更愿意避免。

以上是关于SQL Alchemy 默认值函数,用于在一组唯一的父子记录中模拟自动增量的主要内容,如果未能解决你的问题,请参考以下文章

ORM查询多对多一,Flask sql-alchemy

SQL Alchemy - 从一个实体上的多个一对一关系中删除孤儿[关闭]

在一组值上集成两个变量的函数

在 SQL Alchemy ORM 中使用辅助连接

SQL - 确保在一组关键密钥对中表示的两个实体都存在于最终数据集中的有效方法

SQL ALCHEMY - 如何返回 query(model).all() 作为值的元组而不是模型实例