我有下表(访问次数):
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)
任何想法如何实现这一目标?
答案 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语句。 (我们不希望变量的任何剩余值与我们的结果相符。)
实现v
和i
视图后(作为派生表),可以运行别名为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