2014-09-19 5 views
2

С SqlAlchemy можно ли построить запрос, который будет обновлять только первую соответствующую строку?UPDATE .. LIMIT 1 с SqlAlchemy и PostgreSQL

В моем случае, мне нужно обновить самую последнюю запись в журнале:

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', или *** следующую *** строку, еще не заблокированную или одну, случайная/произвольная строка, соответствующая некоторым критериям. –

ответ

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

... и если вы пытаетесь использовать его для очередей, помните о параллелизме. Это не атомный, поэтому несколько сеансов могут захватывать и обновлять одну и ту же строку. –

1

Добавить

WHERE analyzed <> :analyzed 

предотвратить ту же строку из обновляется несколько раз. Или

WHERE analyzed IS DISTINCT FROM :analyzed 

если NULL значения допустимы. Добавьте такое же условие к внешнему UPDATE, а это почти всегда хорошая идея в любом случае до avoid empty updates.

Параллельные транзакции, блокируемые блокировкой ROW SHARE от FOR UPDATE, просыпаются, как только заканчивается первая транзакция. Так как измененная строка больше не проходит условие WHERE, подзапрос возвращает строку и ничего не происходит.

В то время как более поздние операции блокировки новой строки, чтобы обновить ...

Вы можете использовать advisory locks всегда обновлять следующую разблокирована строку без ожидания. Я добавил еще в связанном ответ:

Или рассмотрим PGQ для реализации очереди.