在SQLAlchemy中标记表中的单行
想象一下,有一个表格,里面有两列,分别是 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