SQL:按顺序访问计数

时间:2016-01-28 03:09:34

标签: mysql sql

我有下表(访问次数):

id(int) | fb_id(varchar)|   flipbook(varchar) | 
----       ----------         ---------     
1           1123            november 2014
2           1124            november 2014
3           1123            december 2014
4           1124            december 2014
5           1123            december 2014
6           1123            january 2015
7           1126            january 2015
8           1125            february 2015
9           1123            february 2015
10          1124            march 2015
11          1125            march 2015
11          1123            march 2015

查询运行后,我希望得到以下结果:

sequence  count
  5         1  (1 user visited 5 flipbooks in a row: 1123)
  2         2  (2 users visited 2 flipbooks in a row: 1124, 1125)
  1         1  (1 user visited only 1 flipbook: 1126)

任何想法如何实现这一目标?

2 个答案:

答案 0 :(得分:2)

SQL难以实现查找用户序列访问的结果。

但是,如果您使用其他方法更容易,例如:创建或重用您的一个表:

users(fb_id, best_sequence, current_sequence, last_modified)
-------------------------------------------------------
1123         5              3                 2016-01

循环翻阅你的翻书,例如

SELECT * FROM visits WHERE fb_id=1123 AND flipbook >= .... 
(you might redesign data to make SQL easier here)

如果序列匹配,则将current_sequence更新为4,如果找到间隙,则将0更新为

如果current_sequence> best_sequence,设置best_sequence = current_sequence

您可以通过cron job,触发器或其他一些您觉得最舒服的方法来实现。

这是一个想法,并编写自己的代码。

答案 1 :(得分:2)

fb_id=1124仅“连续”访问了1本翻书。除非行id=4应该是“12月20日 14 ”而不是“12月20日 15 ”。

是的,这可以在MySQL中使用用户定义的变量来完成。 MySQL参考手册警告说,在同一语句中使用这种用户定义变量的行为是未定义的。

sequence   count  info
--------  ------  ---------------------------------------------
       5       1  (1 user visited 5 flipbooks in a row: 1123
       2       1  (1 user visited 2 flipbooks in a row: 1125
       1       2  (2 users visited only 1 flipbook: 1124,1126

该结果集由以下SQL查询生成:

SELECT d.seq AS `sequence`
     , COUNT(1) AS `count`
     , CONCAT('('
             ,COUNT(1)
             ,' user'
             ,IF(COUNT(1)>1,'s','')
             ,' visited'
             ,IF(d.seq>1,'',' only')
             ,' '
             ,d.seq
             ,' flipbook'
             ,IF(d.seq>1,'s in a row: ',': ')
             ,GROUP_CONCAT(d.fb_id ORDER BY d.fb_id)
       ) AS `info`
  FROM ( SELECT c.fb_id
              , MAX(c.cnt) AS seq
           FROM ( SELECT @cnt := IF(@prev_fb_id = v.fb_id AND PERIOD_DIFF(v.yyyymm,@prev_yyyymm)=1, @cnt + 1, 1) AS cnt
                       , @prev_yyyymm := v.yyyymm AS yyyymm
                       , @prev_fb_id  := v.fb_id AS fb_id
                    FROM ( SELECT @prev_fb_id := NULL
                                , @prev_yyyymm := NULL
                                , @cnt := 0
                         ) i
                   CROSS
                    JOIN ( SELECT t.fb_id
                                , DATE_FORMAT(STR_TO_DATE(CONCAT('01 ',t.flipbook),'%d %M %Y'),'%Y%m') AS yyyymm
                             FROM t
                            GROUP BY t.fb_id, yyyymm 
                            ORDER BY t.fb_id, yyyymm
                          ) v  
                ) c
          GROUP BY c.fb_id
       ) d
 GROUP BY d.seq
 ORDER BY d.seq DESC

<强>后续

源表的表名在最里面的内联视图中进入查询,别名为v。 (在上面的示例中,表名为t

要了解其工作原理,您可以只运行最内层内联视图的查询,以查看它返回的内容。最重要的是将flipbook列重新格式化为YYYYMM格式,并对行进行排序。 (稍后我们将使用PERIOD_DIFF函数计算flipbook值之间的月数。)

内联视图i仅用于初始化我们将要使用的用户定义变量。我们在最内层的内联视图查询中执行此操作,以便在外部查询中引用这些变量之前完成。它基本上等同于在查询运行之前立即运行单独的SET语句。 (我们不希望变量的任何剩余值与我们的结果相符。)

实现vi视图后(作为派生表),可以运行别名为v的内联视图中的查询。 (FROM子句中的视图查询基本上就像表一样。)

这个查询是神奇的地方。我们使用用户定义的变量来保留“previous”行的值,因此我们可以将其与当前行进行比较。如果当前行是针对同一个用户,并且恰好是前一行的一个月,我们将序列计数增加1,否则,我们将其设置为1.

一旦该查询完成,我们就有了一个派生表,我们可以将其用作另一个查询的行源。在此查询中,我们希望为每个用户找到该序列计数器的“最大”值。这将为每个用户提供最长的序列。

使用该集合,最外层的查询几乎是微不足道的...按最长的顺序按降序排序,并折叠行以获得具有相同最大序列值的用户数量。

要获得序列中fb_id的最高访问次数,我们可以累计该最里面的查看查询中的访问次数。 COUNT(1)SUM(1)会为我们提供每月的访问次数。

这可以提供给下一个查询。我们可以做同样的检查,以积累连续的月份。我们将累计访问总数,而不是递增1。

必须修改下一个查询。我们无法在MAX()周围包裹tot,因为我们无法保证总访问量来自同一个最长的序列。我们可能在5个月的时间内进行6次访问,但同一个用户可能在3个月内访问了8次。因此我们废弃MAX()函数,而是使用排序(从最高到最低)。我们将保留fb_id的第一行的值,并将其他值设置为NULL。然后,在最外面的查询中,我们可以使用MAX()聚合来忽略NULL,并从具有相同sequence值的所有用户中返回最高的总访问次数。

我们可以得到这个结果:

sequence   count  highest_tot
--------  ------  -----------
       5       1            6
       2       1            2
       1       2            1

来自这样的查询:

SELECT d.seq AS `sequence`
     , COUNT(1) AS `count`
     , MAX(FLOOR(d.tot)) AS `highest_tot`
  FROM ( SELECT IF(@c_fb_id=c.fb_id,NULL,c.cnt) AS seq
              , IF(@c_fb_id=c.fb_id,NULL,c.tot) AS tot
              , @c_fb_id := c.fb_id AS fb_id
           FROM ( SELECT @cnt := IF(@prev_fb_id = v.fb_id AND PERIOD_DIFF(v.yyyymm,@prev_yyyymm)=1, @cnt + 1, 1) AS cnt
                       , @tot := IF(@prev_fb_id = v.fb_id AND PERIOD_DIFF(v.yyyymm,@prev_yyyymm)=1, @tot + v.tot, v.tot) AS tot
                       , @prev_yyyymm := v.yyyymm AS yyyymm
                       , @prev_fb_id  := v.fb_id AS fb_id
                    FROM ( SELECT @prev_fb_id := NULL
                                , @prev_yyyymm := NULL
                                , @cnt := 0
                                , @tot := 0
                                , @c_fb_id := NULL
                         ) i
                   CROSS
                    JOIN ( SELECT t.fb_id
                                , DATE_FORMAT(STR_TO_DATE(CONCAT('01 ',t.flipbook),'%d %M %Y'),'%Y%m') AS yyyymm
                                , SUM(1) AS tot
                             FROM t
                            GROUP BY t.fb_id, yyyymm 
                            ORDER BY t.fb_id, yyyymm
                          ) v  
                ) c
          ORDER BY c.fb_id DESC, c.cnt DESC, c.tot DESC
       ) d
 WHERE d.seq IS NOT NULL
 GROUP BY d.seq
 ORDER BY d.seq DESC