在SQLAlchemy中标记表中的单行

2 投票
1 回答
1740 浏览
提问于 2025-04-16 19:07

想象一下,有一个表格,里面有两列,分别是 id(整数类型)和 mark(布尔类型)。这个表格可以有很多行,但其中只能有一行的 mark 列是 True

如果我把数据库修改成把另一行的 mark 设置为 True,那么系统应该先把之前那行的 mark 取消,然后再把新请求的那行的 mark 设置为 True

你会怎么用 Python 和 SQLAlchemy 来实现这个功能呢?

1 个回答

4

上面这两条评论都有道理。触发器是一种不错的解决方案,同时“很多假,一个真”的模式也暗示可以用另一张表来引用“真实”的那一行,甚至可能整个“真实”的那一行在别的地方。通常情况下,你的表是用来存储版本信息的,而“真实”代表当前的“版本”。我通常会从一个父记录中引用“当前”版本,或者使用一个叫“历史”的单独表来存放所有“非当前”的行。

无论如何,让我们看看在SQLAlchemy中实现你所要求的功能的最快方法。我们将通过ORM事件来做类似于INSERT/UPDATE触发器的操作:

from sqlalchemy import Column, Integer, Boolean, create_engine
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
Base = declarative_base()

class Widget(Base):
    __tablename__ = 'widget'
    id = Column(Integer, primary_key=True)
    is_current_widget = Column(Boolean, default=False,
                        nullable=False)

@event.listens_for(Widget, "after_insert")
@event.listens_for(Widget, "after_update")
def _check_current(mapper, connection, target):
    if target.is_current_widget:
        connection.execute(
            Widget.__table__.
                update().
                values(is_current_widget=False).
                where(Widget.id!=target.id)
        )

e = create_engine('sqlite://', echo=True)
Base.metadata.create_all(e)

s = Session(e)

w1, w2, w3, w4, w5 = [Widget() for i in xrange(5)]
s.add_all([w1, w2, w3, w4, w5])
s.commit()

# note each call to commit() expires
# the values on all the Widgets so that
# is_current_widget is refreshed.

w2.is_current_widget = True
s.commit()

assert w2.is_current_widget
assert not w5.is_current_widget

w4.is_current_widget = True
s.commit()

assert not w2.is_current_widget
assert not w5.is_current_widget
assert w4.is_current_widget

# test the after_insert event

w6 = Widget(is_current_widget=True)
s.add(w6)
s.commit()

assert w6.is_current_widget
assert not w5.is_current_widget
assert not w4.is_current_widget

撰写回答