2013-08-07 6 views
8

Или как я могу заставить эту работу работать?Как реализовать нулевой коалесцирующий оператор в SQLAlchemy?

У меня есть Interval объект:

class Interval(Base): 
    __tablename__ = 'intervals' 
    id = Column(Integer, primary_key=True) 
    start = Column(DateTime) 
    end = Column(DateTime, nullable=True) 
    task_id = Column(Integer, ForeignKey('tasks.id')) 

@hybrid_property #used to just be @property 
def hours_spent(self): 
    end = self.end or datetime.datetime.now() 
    return (end-start).total_seconds()/60/60 

и Целевую:

class Task(Base): 
    __tablename__ = 'tasks' 
    id = Column(Integer, primary_key=True) 
    title = Column(String) 
    intervals = relationship("Interval", backref="task") 

@hybrid_property # Also used to be just @property 
def hours_spent(self): 
    return sum(i.hours_spent for i in self.intervals) 

Добавить все типичные настройки кода, конечно.

Теперь, когда я пытаюсь сделать session.query(Task).filter(Task.hours_spent > 3).all()

Я получаю NotImplementedError: <built-in function getitem> от sum(i.hours_spent... линии.

Итак, я смотрел this part документации и предположил, что может быть какой-то способ, которым я могу написать что-то, что будет делать то, что я хочу. This part также выглядит так, как будто он может быть полезен, и я буду смотреть на него, ожидая ответа здесь;)

ответ

5

SQLAlchemy недостаточно умен, чтобы построить дерево выражений SQL из этих операндов, вы должны использовать явно propname.expression декоратор, чтобы обеспечить его. Но тогда возникает еще одна проблема: нет переносного способа конвертировать интервал в часы в базе данных. Вы должны использовать TIMEDIFF в MySQL, EXTRACT(EPOCH FROM ...)/3600 в PostgreSQL и т. Д. Я предлагаю изменить свойства, чтобы вернуть timedelta и сравнить яблоки с яблоками.

from sqlalchemy import select, func 


class Interval(Base): 
    ... 

    @hybrid_property 
    def time_spent(self): 
     return (self.end or datetime.now()) - self.start 

    @time_spent.expression 
    def time_spent(cls): 
     return func.coalesce(cls.end, func.current_timestamp()) - cls.start 


class Task(Base): 
    ... 

    @hybrid_property 
    def time_spent(self): 
     return sum((i.time_spent for i in self.intervals), timedelta(0)) 

    @time_spent.expression 
    def hours_spent(cls): 
     return (select([func.sum(Interval.time_spent)]) 
      .where(cls.id==Interval.task_id) 
      .label('time_spent')) 

Окончательный запрос:

session.query(Task).filter(Task.time_spent > timedelta(hours=3)).all() 

, который переводит (на PostgreSQL бэкэндом):

SELECT task.id AS task_id, task.title AS task_title 
FROM task 
WHERE (SELECT sum(coalesce(interval."end", CURRENT_TIMESTAMP) - interval.start) AS sum_1 
FROM interval 
WHERE task.id = interval.task_id) > %(param_1)s 
3

В качестве простого примера функции COALESCE SQLAlchemy, это может помочь: Handling null values in a SQLAlchemy query - equivalent of isnull, nullif or coalesce.

Вот несколько ключевых строк кода с этого поста:

from sqlalchemy.sql.functions import coalesce 
my_config = session.query(Config).order_by(coalesce(Config.last_processed_at, datetime.date.min)).first() 
+2

Я думаю, что эта ссылка будет работать лучше: http://progblog10.blogspot.com/2014/06/handling-null-values -in-sqlalchemy.html – Kirk

+0

Вы правы! Спасибо за исправление этой ссылки. –