【问题标题】:SQL - state machine - reporting on historical data based on changesetSQL - 状态机 - 基于变更集报告历史数据
【发布时间】:2010-09-15 23:27:51
【问题描述】:

我想记录用户状态,然后能够根据我们保存的更改记录进行历史报告。我正在尝试在 SQL 中执行此操作(使用 PostgreSQL),并且我有一个用于记录用户更改的建议结构,如下所示。

CREATE TABLE users (
  userid SERIAL NOT NULL PRIMARY KEY, 
  name VARCHAR(40), 
  status CHAR NOT NULL
);

CREATE TABLE status_log (
  logid SERIAL, 
  userid INTEGER NOT NULL REFERENCES users(userid), 
  status CHAR NOT NULL, 
  logcreated TIMESTAMP
);

这是我根据数据提出的表结构。

对于状态字段,“a”代表活跃用户,“s”代表暂停用户,

INSERT INTO status_log (userid, status, logcreated) VALUES (1, 's', '2008-01-01'); 
INSERT INTO status_log (userid, status, logcreated) VALUES (1, 'a', '2008-02-01'); 

因此,该用户在 1 月 1 日被暂停,并在 2 月 1 日再次活跃。

如果我想在 2008 年 1 月 15 日获得暂停的客户列表,则应该显示用户 ID 1。如果我在 2008 年 2 月 15 日收到一份暂停的客户名单,那么用户 ID 1 应该不会出现。

1) 这是为此类查询构建数据的最佳方式吗?

2) 我如何查询此结构或您建议的修改结构中的数据,以便我可以简单地获得一个日期(比如 1 月 15 日)并在 SQL 中找到在该日期具有活动状态的客户列表只要?这是 SQL 的工作吗?

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    这可以做到,但如果您存储每个日志的结束日期,效率会高得多。对于您的模型,您必须执行以下操作:

    select l1.userid
    from status_log l1
    where l1.status='s'
    and l1.logcreated = (select max(l2.logcreated)
                         from status_log l2
                         where l2.userid = l1.userid
                         and   l2.logcreated <= date '2008-02-15'
                        );
    

    有了额外的列,它会更像:

    select userid
    from status_log
    where status='s'
    and logcreated <= date '2008-02-15'
    and logsuperseded >= date '2008-02-15';
    

    (对任何语法错误表示歉意,我不知道 Postgresql。)

    解决菲尔提出的一些进一步问题:

    用户可能会从活跃状态变为暂停状态、取消状态、再次活跃状态。这是一个简化的版本,实际上,还有更多的状态,人们可以直接从一个状态移动到另一个状态。

    这将出现在表格中,如下所示:

    userid  from       to         status
    FRED    2008-01-01 2008-01-31 s
    FRED    2008-02-01 2008-02-07 c
    FRED    2008-02-08            a
    

    我使用 null 作为当前记录的“到”日期。我本可以使用像 2999-12-31 这样的未来日期,但在某些方面 null 更可取。

    此外,当前状态也没有“结束日期”,所以我认为这会稍微影响您的查询?

    是的,我的查询必须重写为

    select userid
    from status_log
    where status='s'
    and logcreated <= date '2008-02-15'
    and (logsuperseded is null or logsuperseded >= date '2008-02-15');
    

    这种设计的一个缺点是,每当用户的状态发生变化时,您都必须结束他们当前的 status_log 的日期并创建一个新的。不过,这并不难,而且我认为查询优势可能超过了这一点。

    【讨论】:

      【解决方案2】:

      Postgres 是否支持分析查询?这将给 2008-02-15 上的活跃用户

      select userid
      from
      (
      select logid, 
             userid, 
             status, 
             logcreated,
             max(logcreated) over (partition by userid) max_logcreated_by_user
      from   status_log
      where  logcreated <= date '2008-02-15'
      )
      where  logcreated = max_logcreated_by_user
        and  status     = 'a'
      /
      

      【讨论】:

      【解决方案3】:

      @Tony “结束”日期不一定适用。

      用户可能会从活跃状态变为暂停状态、取消状态、再次活跃状态。这是一个简化的版本,实际上,还有更多的状态,人们可以直接从一个状态移动到另一个状态。

      此外,当前状态也没有“结束日期”,所以我认为这会稍微影响您的查询?

      【讨论】:

        【解决方案4】:

        @菲尔

        我喜欢 Tony 的解决方案。它似乎最恰当地模拟了所描述的情况。任何特定用户都具有给定时间段(一分钟、一小时、一天等)的状态,但它是持续时间的,而不是瞬间的。由于您想知道谁在特定时间段内处于活动状态,因此将信息建模为持续时间似乎是最好的方法。

        我不确定其他状态是否存在问题。如果某人处于活动状态,然后被暂停,然后被取消,然后再次处于活动状态,那么这些状态中的每一个都将适用于给定的持续时间,不是吗?这可能是很短的持续时间,例如几秒钟或一分钟,但它们仍然会持续很长时间。

        您是否担心某个人的状态在一天内可能会发生多次变化,但您想知道谁在一天内处于活跃状态?如果是这样,那么您只需要更具体地定义在特定日期活跃的含义。如果他们在当天的任何时间都活跃就足够了,那么托尼的回答就可以了。如果它们必须在给定的一天内活动一段时间,则可以修改 Tony 的解决方案以简单地确定时间长度(以小时、分钟或天为单位),并在 WHERE 子句中添加更多限制检索适当的日期、状态和处于该状态的时间长度。

        至于当前状态没有“结束日期”,只要结束日期可以为空就没有问题。只需使用类似“WHERE enddate

        【讨论】:

          猜你喜欢
          • 2015-07-11
          • 1970-01-01
          • 2021-11-20
          • 2021-05-19
          • 1970-01-01
          • 2019-02-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多