2011-06-07 49 views
1

想象一下具有两列的表格id(Integer)和mark(布尔值)。该表格可以有任意数量的行,但其中的一行应该将mark列设置为True在SQLAlchemy的表中标记单个表格

如果我修改数据库以标记另一个条目的markTrue,那么系统应该先取消标记先前的条目,然后标记所请求的条目。

你将如何在Python/SQLAlchemy中处理这个问题?

+1

为什么不在数据库中使用触发器来做呢?这似乎更加防错。 – 2011-06-07 18:03:01

+6

有一个只能标记一行的列是不是非常有效,特别是如果你有很多很多的行。你可能会考虑用单行来创建一个单独的表,它存储哪个'id'是'标记''id'。通过这种方式,当条目被“标记”时,您只需要更改一个值,而不必担心“未标记”多个条目。 – Raceyman 2011-06-07 20:20:55

+0

一个愚蠢的(但大多是便携式的)强制执行约束的方法是使用一个具有唯一约束的可为空的CHAR(0)。由于只有一个长度为零的字符串,因此只有一行可能具有非空值。 – SingleNegationElimination 2011-06-13 01:59:59

回答

3

上面的两条评论对他们都是有道理的。一个触发器是一个很好的方法来做到这一点,而且“许多错误的,一个真实的”模式表明,也许可以使用不同的表来引用“真实”行,甚至可以将整个“真实”行引用到别处。这里通常的模型是你的表存储版本信息,“真”代表当前的“版本”。我通常要么从父记录引用“当前”版本,要么对所有“非当前”行使用一个名为“history”的单独表。

无论如何,让我们看看在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