MySQL查询计算特定值的最大连续更新

时间:2015-10-27 21:08:03

标签: php mysql sql

我提前道歉 - 我不是数据库专家,所以我可能错过了一个非常明显的答案。

我有一个查询,用于计算特定状态的用户连续签到的次数。有5个登记入住状态,我们需要计算状态'U'的连续签到(有5种可能的状态:'U','O','D','F','M') 。如果有人签入任何其他状态,则条纹将停止。在经过多次学习,测试和失败之后,我想出了以下查询,以便主要基于this stackoverflow threadthis stack overflow thread返回任何用户的最长条纹(感谢这些帖子/解决方案的作者!)。

SELECT tmi, static_streak, MAX(count) AS consec
FROM (
    SELECT @prev_tmi := @tmi AS prev_tmi,
            @prev_s := @status AS prev_s,
            @tmi := rtmc.team_member_id AS tmi,
            @status := rtmc.status AS status,
            @count := if(@prev_s = @status AND @status = 'U', @count + 1, 1) AS count
    FROM readiness_team_member_checkins rtmc
    CROSS JOIN (SELECT @count:=0) var_init
    LEFT JOIN readiness_sessions rs ON rtmc.readiness_sessions_id = rs.readiness_sessions_id 
    WHERE rtmc.team_member_id = 83
    ORDER BY rtmc.readiness_sessions_id, tmi
)
AS sub 
GROUP BY tmi

以下是表格/测试数据:

CREATE TABLE IF NOT EXISTS `readiness_sessions` (
  `readiness_sessions_id` int(11) NOT NULL AUTO_INCREMENT,
  `session_date` date NOT NULL,
  `session_level` varchar(3) DEFAULT NULL,
  PRIMARY KEY (`readiness_sessions_id`,`session_date`)
);

INSERT INTO `readiness_sessions` (`readiness_sessions_id`,
                                  `session_date`, 
                                  `session_level`
                                  ) VALUES
(1, '2015-09-02', '4'),
(2, '2015-09-03', '4'),
(6, '2015-09-04', '4'),
(7, '2015-09-05', '4'),
(8, '2015-09-06', '4'),
(10, '2015-09-07', '4'),
(11, '2015-09-07', '3'),
(12, '2015-09-08', '4'),
(13, '2015-09-09', '4'),
(14, '2015-09-29', '4'),
(15, '2015-09-30', '4'),
(16, '2015-10-01', '3'),
(17, '2015-10-02', '4'),
(18, '2015-10-06', '4'),
(19, '2015-10-20', '4');

CREATE TABLE IF NOT EXISTS `readiness_team_member_checkins` (
  `readiness_team_member_checkins_id` int(11),
  `readiness_sessions_id` int(11),
  `readiness_team_checkins_id` int(11),
  `team_member_id` int(11),
  `status` enum('U','D','O','M','F','X')

);

INSERT INTO `readiness_team_member_checkins` 
  (`readiness_team_member_checkins_id`, 
   `readiness_sessions_id`, 
   `team_member_id`, 
   `status`) VALUES
(1, 1, 83, 'U'),
(2, 2, 83, 'O'),
(3, 6, 83, 'U'),
(4, 7, 83, 'U'),
(5, 8, 83, 'U'),
(6, 10, 83, 'M'),
(7, 11, 83, 'U'),
(8, 12, 83, 'U'),
(9, 13, 83, 'U'),
(10, 14, 83, 'U'),
(11, 15, 83, 'U'),
(12, 16, 83, 'D'),
(13, 17, 83, 'U'),
(14, 18, 83, 'U'),
(15, 19, 83, 'U');

我现在有一个附加功能,需要在某些情况下考虑第二个状态。

对新流程的要求基本上表明,“U”或“D”的签到状态不会破坏连续计数,但只有“U”会增加计数 - “D”将被忽略但不会中断连续计数。我尝试使用其他用户定义的变量来压缩上述查询,但我对这个级别的mysql并不熟悉。

包含的数据应该为当前实现返回5,并且对于附加要求将返回8。

我有两个问题:

  1. 当前查询通常会返回正确的数据,但有时会返回错误的数字,我需要重新加载查询并获得正确的数字。我假设这是由于用户定义的变量,我基本上学习了上周使用的变量。有什么暗示吗?我一直无法一再重现这个问题......

  2. 关于我如何干净地调整上述内容以获得“U”的最长连续条纹的任何提示都不会被二级要求中描述的“D”中断?

  3. SQL小提琴here

3 个答案:

答案 0 :(得分:1)

我使用您问题中的数据更新您的sqlfiddle

@count := if(condition1, true1, false1)

condition1 = ( @prev_s = 'U' OR @prev_s = 'D') <- mean i will continue chain
true1 = if(condition2, true2, false2)
false1 = 1    <- start new chain

condition2 = (@status = 'U')
true2  = @count + 1   <- increase count
false2 = if(condition3, true3, false3)

condition3 = (@status = 'D')
true3 = @count   <- keep the chain count
false3 = 1       <- not 'U' or 'D' reset count

SQL FIDDLE DEMO

SELECT tmi, MAX(count) AS consec
    FROM (
      SELECT @prev_tmi := @tmi AS prev_tmi,
          @prev_s := @status AS prev_s,
          @tmi := rtmc.team_member_id AS tmi,
          @status := rtmc.status AS status,
          @count := if(@prev_s = 'U' OR @prev_s = 'D',
                       if(@status = 'U',
                          @count + 1, 
                          if(@status = 'D', @count, 1)
                         ),
                       1
                      ) AS count
      FROM readiness_team_member_checkins rtmc
      CROSS JOIN (SELECT @count:=0) var_init
      LEFT JOIN readiness_sessions rs ON rtmc.readiness_sessions_id = rs.readiness_sessions_id 
      WHERE rtmc.team_member_id = 83 AND rs.session_level in (3,4)
      ORDER BY rtmc.readiness_sessions_id, tmi
    )
AS sub 
GROUP BY tmi

部分输出

| prev_tmi | prev_s | tmi | status | count |
|----------|--------|-----|--------|-------|
|       83 | (null) |  83 |      U |     1 |
|       83 |      U |  83 |      O |     1 |
|       83 |      O |  83 |      U |     1 |
|       83 |      U |  83 |      U |     2 |
|       83 |      U |  83 |      U |     3 |
|       83 |      U |  83 |      M |     1 |
|       83 |      M |  83 |      U |     1 |
|       83 |      U |  83 |      U |     2 |
|       83 |      U |  83 |      U |     3 |
|       83 |      U |  83 |      U |     4 |
|       83 |      U |  83 |      U |     5 |
|       83 |      U |  83 |      D |     5 |  <- skip count / no break chain
|       83 |      D |  83 |      U |     6 |
|       83 |      U |  83 |      U |     7 |
|       83 |      U |  83 |      U |     8 |

答案 1 :(得分:1)

以下查询计算给定成员的所有序列:

SELECT team_member_id, grp, count(*) AS consec
FROM (SELECT rtmc.*,
             (@grp := if(@ms = concat_ws(':', rtmc.team_member_id, rtmc.status), @grp,
                         if(@ms := concat_ws(':', rtmc.team_member_id, rtmc.status), @grp + 1, @grp + 1)
                         )
             ) as grp
      FROM readiness_team_member_checkins rtmc CROSS JOIN
           (SELECT @grp := 0, @ms := '') params  
      WHERE rtmc.team_member_id = 83
      ORDER BY rtmc.readiness_sessions_id, team_member_id
     ) s
GROUP BY team_member_id, grp;

如果您只想要最长的话,可以添加order by count(*) desc limit 1

关于使用变量的说明:

  • 不要在同一select中分配变量并在不同的表达式中使用它们。 MySQL文档明确警告不要这样做;这可能是为什么你的查询有时会工作但并非总是如此。
  • 最好初始化SQL语句中的所有变量。我在名为params的子查询中这样做。
  • 查询中似乎没有使用表格readiness_sessions,所以我删除了它。
  • 要只获取一个状态,请在外部查询中添加where status = 'U'

答案 2 :(得分:1)

在阅读第1部分的戈登答案之后,我得到了第2部分的另一个版本

我创建了一个虚拟列以将'D', 'U'作为单个组加入

并为COUNT()

替换SUM()

<强> SqlFiddleDemo

SELECT team_member_id, grp, status, SUM(CASE WHEN status = 'U' THEN 1 ELSE 0 END ) AS consec
FROM (SELECT rtmc.*,
             @ms,
             (@grp := if(@ms = concat_ws(':', rtmc.team_member_id, rtmc.dummy_status), @grp,
                         if(@ms := concat_ws(':', rtmc.team_member_id, rtmc.dummy_status), @grp + 1, @grp + 1)
                         )
             ) as grp
      FROM (SELECT rtmc.*, CASE 
                               WHEN status = 'D' THEN 'U'
                               ELSE status
                           END as dummy_status
            FROM 
            readiness_team_member_checkins rtmc 
            ) rtmc
      CROSS JOIN
           (SELECT @grp := 0, @ms := '') params  
      WHERE rtmc.team_member_id = 83
      ORDER BY rtmc.readiness_sessions_id, team_member_id
     ) s
GROUP BY team_member_id, grp, status;

输出

| team_member_id | grp | status | consec |
|----------------|-----|--------|--------|
|             83 |   1 |      U |      1 |
|             83 |   2 |      O |      0 |
|             83 |   3 |      U |      3 |
|             83 |   4 |      M |      0 |
|             83 |   5 |      U |      8 |
|             83 |   5 |      D |      0 |