在MySQL / SQLAlchemy中计算事件持续时间的最佳方法是什么?

时间:2018-03-28 06:49:12

标签: python mysql events sqlalchemy

实体的事件以下列方式存储:

class Event(db.Model):
    __tablename__ = 'event'
    id = db.Column(db.Integer, primary_key=True)
    entity_id = db.Column(db.Integer, db.ForeignKey('entity.id'))
    mode = db.Column(db.Integer)
    timestamp = db.Column(db.DateTime, default=datetime.datetime.utcnow)
    duration = db.Column(db.Integer, nullable=False, default=0, server_default=db.text('0'))

记录在事件开始时添加,当然没有持续时间。如果存在不同模式的先前事件,则需要设置持续时间。

一个mode None的示例,最终结果断言:

assert [(x.timestamp, x.duration, x.mode) for x in events] == [
    (datetime.datetime(2018, 2, 22, 10, 23, 45), 120L, 0L),
    (datetime.datetime(2018, 2, 22, 10, 25, 45), 172800L, 1L),
    (datetime.datetime(2018, 2, 23, 10, 25, 45), 0L, None),
    (datetime.datetime(2018, 2, 24, 10, 25, 45), 0L, 2L)
]

持续时间用于所涉及的统计报告,因此最好将它们存储起来。事件在创建后是只读的(除了可能稍后设置持续时间),事件表是一种日志。另一个假设是,小持续时间并不重要,因此如果发生一些短事件突发,它不会伤害接近零的持续时间,但是,长事件必须有持续时间(除非事件尚未结束)。当然,这仍然是有效的并发问题。

(可能)用持续时间更新旧事件的最佳方法是什么?

我想到的一些选项是:

  1. __init__调用类方法,该方法查询同一实体的上一个事件,如果找到则设置其持续时间
  2. 使用SQLAlchemy的事件来添加新事件
  3. 一些聪明的表格安排,用于计算与存储时间相同的效率水平的持续时间

1 个答案:

答案 0 :(得分:1)

这不是最好的方式,也不是非常聪明,但它允许计算持续时间而不实现它们。我以为我会把它作为一种好奇心来提供。

使用window functions - 来自MySQL 8 - 可以计算给定窗口中相邻行之间的差异:

SELECT
  id,
  entity_id,
  mode,
  "timestamp",
  CASE
    WHEN NOT same_mode THEN
      COALESCE(
        TIMESTAMPDIFF(SECOND,
                      "timestamp",
                      LEAD("timestamp") OVER w),
        0)
    ELSE 0
  END AS duration
FROM (
  SELECT
    *,
    mode IS NULL OR
      LAG(mode) OVER v IS NOT NULL AND
      mode = LAG(mode) OVER v AS same_mode
  FROM event
  WINDOW v AS (PARTITION BY entity_id ORDER BY "timestamp")) e
WINDOW w AS (PARTITION BY entity_id, same_mode
             ORDER BY "timestamp")
ORDER BY "timestamp", ISNULL(mode), mode;

SQLAlchemy中的相同:

In [3]: lagged_mode = db.func.lag(Event.mode).\
   ...:     over(partition_by=Event.entity_id,
   ...:          order_by=Event.timestamp)

In [4]: subquery = db.session.query(
   ...:         Event,
   ...:         ((Event.mode == None) |
   ...:          (lagged_mode != None) &
   ...:          (Event.mode == lagged_mode)).label('same_mode')).\
   ...:     subquery()

In [5]: event_alias = db.aliased(Event, subquery)

In [6]: led_timestamp = db.func.lead(event_alias.timestamp).\
   ...:     over(partition_by=(event_alias.entity_id,
   ...:                        subquery.c.same_mode),
   ...:          order_by=event_alias.timestamp)

In [7]: query = db.session.query(
   ...:         event_alias,
   ...:         db.case(
   ...:             [(~subquery.c.same_mode,
   ...:               db.func.coalesce(
   ...:                   db.func.timestampdiff(db.text('second'),
   ...:                                         event_alias.timestamp,
   ...:                                         led_timestamp),
   ...:                   0))],
   ...:             else_=0).label('duration')).\
   ...:     order_by(event_alias.timestamp,
   ...:              db.func.isnull(event_alias.mode),
   ...:              event_alias.mode)
   ...:             

使用最新版本的SQLAlchemy,可以将duration映射为query-time SQL expression,然后由上述查询提供。