2014-09-19 60 views
2

使用SqlAlchemy,是否可以構建只更新第一個匹配行的查詢?使用SqlAlchemy和PostgreSQL更新..限制1

以我爲例,我需要更新最新的日誌條目:

class Log(Base): 
    __tablename__ = 'logs' 
    id = Column(Integer, primary_key=True) 
    #... 
    analyzed = Column(Boolean) 

session.query(Log) \ 
    .order_by(Log.id.desc()) \ 
    .limit(1) \ 
    .update({ 'analyzed': True }) 

其結果都是:

InvalidRequestError: Can't call Query.update() when limit() has been called

這是有道理的,因爲UPDATE ... LIMIT 1是一個MySQL特有的功能(帶給出的解決方案here

但是我怎樣才能對PostgreSQL做同樣的事?可能使用the subquery approach

+1

最好的解決方案取決於每個併發事務是否應該根據'ORDER BY'或***下一個***行,還沒有鎖定或者單個,隨機/任意行匹配一些標準。 – 2014-09-21 01:43:58

回答

5

subquery recipe是正確的方法,現在我們只需要用SqlAlchemy建立這個查詢。

讓我們先從子查詢:

sq = ssn.query(Log.id) \ 
    .order_by(Log.id.desc()) \ 
    .limit(1) \ 
    .with_for_update() 

而且現在as_scalar() 與例如使用它從update() docs

from sqlalchemy import update 

q = update(Log) \ 
    .values({'analyzed': True}) \ 
    .where(Log.id == sq.as_scalar()) 

打印查詢看看結果:

UPDATE logs 
SET analyzed=:analyzed 
WHERE logs.id = (
    SELECT logs.id 
    FROM logs ORDER BY logs.id DESC 
    LIMIT :param_1 
    FOR UPDATE 
) 

享受!

+2

...如果您嘗試將其用於排隊,請記住併發。它不是原子的,所以多個會話可以獲取並更新同一行。 – 2014-09-20 12:04:59

1

添加

WHERE analyzed <> :analyzed 

,以防止在同一行被更新多次。或者

WHERE analyzed IS DISTINCT FROM :analyzed 

if if NULL values are allowed。向外部UPDATE添加相同的條件,這幾乎是總是在任何情況下,一個好主意avoid empty updates

併發交易被ROW SHARE阻止FOR UPDATE在第一筆交易完成後即被喚醒。由於更改的行不再通過WHERE條件,子查詢不返回任何行,並且沒有任何發生。

雖然後來交易鎖定一個新行更新...

你可以使用advisory locks,而無需等待隨時更新下解鎖行。我加入了更多的鏈接的答案:

或者考慮PGQ實現一個隊列。