【问题标题】:How to increase a counter in SQLAlchemy如何在 SQLAlchemy 中增加计数器
【发布时间】:2023-03-08 12:28:02
【问题描述】:

假设我有一个表tags,其中有一个字段count,表示有多少items被给定标签标记。

添加带有现有标签的新项目后,如何在 SQLAlchemy 中增加此计数器?

使用纯 SQL,我会执行以下操作:

INSERT INTO `items` VALUES (...)
UPDATE `tags` SET count=count+1 WHERE tag_id=5

但是我如何在 SQLAlchemy 中表达count=count+1

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    如果你有类似的东西:

    mytable = Table('mytable', db.metadata,
        Column('id', db.Integer, primary_key=True),
        Column('counter', db.Integer)
    )
    

    您可以像这样增加字段:

    m = mytable.query.first()
    m.counter = mytable.c.counter + 1
    

    或者,如果你有一些映射模型,你也可以这样写:

    m = Model.query.first()
    m.counter = Model.counter + 1
    

    两个版本都会返回您要求的 sql 语句。但是,如果您不包含该列而只写m.counter += 1,那么新值将在 Python 中计算(并且可能会发生竞争条件)。因此,请始终在此类计数器查询中包含上述两个示例中所示的列。

    【讨论】:

    • 谢谢。但是你能更多地解释一下比赛条件吗?我是否正确理解了第一个版本比第二个版本更安全?
    • 没有。我向您展示的两个版本完全相同(一个使用映射对象,另一个使用表)。但是带有+= 的第三个语句将导致SET counter=4 而不是SET counter=counter+1。所以你应该t use the third +=`版本。
    • 你能解释一下 mytable.c.counter 的来源吗?这是我不熟悉的语法,它似乎指的是表定义而不是选定的行'm'......所以我不清楚数学是如何工作的。
    • 如果在 Python 中增加实际值(例如 int 类型的 3),您将得到一个新整数(例如 4)。如果更多客户端同时执行此操作,您可能会丢失一些更新。 SqlAlchemy 的列类型有一个重载的 add 运算符,因此编写“column + 1”将导致一个对象告诉数据库(而不是 Python!)将列增加一。
    • 关于比赛条件的优点 - 很容易出错。
    【解决方案2】:

    如果您使用的是 SQL 层,那么您可以在更新语句中使用任意 SQL 表达式:

    conn.execute(tags.update(tags.c.tag_id == 5).values(count=tags.c.count + 1))
    

    ORM Query 对象也有一个更新方法:

    session.query(Tag).filter_by(tag_id=5).update({'count': Tag.count + 1})
    

    ORM 版本足够智能,如果它在会话中,它还可以更新对象本身的计数属性。

    【讨论】:

    • 那么 sqlalchemy 在一个查询中编译所有内容?还是先发送一个“SELECT”然后再发送一个“UPDATE”?
    【解决方案3】:

    我是按照 FastAPISQLAlchemydatabases(异步)的教程来到这里的。经过各种尝试,我终于在我的 crud.py / 控制层中使用以下代码。

    async def inc_counter():
        query = counters.select(counters.c.id==1)
        current_val = await database.fetch_val(query=query, column=1)
        query = counters.update().where(counters.c.id==1).values(counter=current_val+1).returning(counters.c.counter)
        return await database.execute(query=query)
    

    我的桌子是这样的:

    counters = Table('counters', metadata,
        Column('id', Integer, primary_key=True),
        Column('counter', Integer)
    )
    

    【讨论】:

    • 获取当前值和执行查询之间没有竞争条件吗?在这两个动作之间可以修改数据库还是这两件事是原子的?我目前正在寻找如何在同一事务中运行子查询数据库服务器端以避免我认为您在这里所做的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 2017-03-28
    • 1970-01-01
    • 2018-06-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多