2011-06-07 46 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