开始之前的背景......
表架构:
用户ID |事件日期 |时差
其中“ActivityDate”是用户事件的时间戳 “Time_diff”是下一个事件和当前事件之间的时间戳差异,以秒为单位 一般来说,但对于用户的最后一次记录事件,因为没有下一次事件,我将 Time_diff 设置为 -999
例如:
UserId | ActivityDate | Time_diff
| 1 | 2012-11-10 11:19:04 | 12 |
| 1 | 2012-11-10 11:19:16 | 11 |
| 1 | 2012-11-10 11:19:27 | 3 |
| 1 | 2012-11-10 11:19:30 | 236774 |
| 1 | 2012-11-13 05:05:44 | 39 |
| 1 | 2012-11-13 05:06:23 | 77342 |
| 1 | 2012-11-14 02:35:25 | 585888 |
| 1 | 2012-11-20 21:20:13 | 1506130 |
...
| 1 | 2013-06-13 06:32:48 | 1616134 |
| 1 | 2013-07-01 23:28:22 | 5778459 |
| 1 | 2013-09-06 20:36:01 | -999 |
| 2 | 2008-08-01 04:59:33 | 622 |
| 2 | 2008-08-01 05:09:55 | 38225 |
| 2 | 2008-08-01 15:47:00 | 31108 |
| 2 | 2008-08-02 00:25:28 | 28599 |
| 2 | 2008-08-02 08:22:07 | 163789 |
| 2 | 2008-08-04 05:51:56 | 1522915 |
| 2 | 2008-08-21 20:53:51 | 694678 |
| 2 | 2008-08-29 21:51:49 | 2945291 |
| 2 | 2008-10-03 00:00:00 | 172800 |
| 2 | 2008-10-05 00:00:00 | 776768 |
| 2 | 2008-10-13 23:46:08 | 3742999 |
我刚刚添加了字段“session_id”
改变表 so_time_diff 添加列 session_id int(11) 不为空;
我的实际问题...
我想根据以下逻辑为上述每条记录更新此字段:
for first record: set session_id = 1
from second record:
if previous_record.UserId == this_record.UserId AND previous_record.time_diff <=3600
set this_record.session_id = previous_record.session_id
else if previous_record.UserId == this_record.UserId AND previous_record.time_diff >3600
set this_record.session_id = previous_record.session_id + 1
else if previous_record.UserId <> this_record.UserId
set session_id = 1 ## for a different user, restart
简单来说,
如果同一用户的两条记录在3600秒的time_interval内,则分配相同的sessionid,如果不增加sessionid,如果是不同的用户,则重新计算sessionid。
我以前从未在更新查询中编写过逻辑。这可能吗?非常感谢任何指导!
最佳答案
是的,这是可能的。如果 time_diff 在后面的记录上而不是在前面的记录上会更容易,但我们可以让它工作。 (我们真的不需要存储的 time_diff。)
让它工作的“诀窍”实际上是编写一个 SELECT 语句。如果您有一个 SELECT 语句返回要更新的行的键和要分配的值,那么将其变成 UPDATE 是微不足道的。
获取 SELECT 语句的“技巧”是利用 MySQL 用户变量,并且依赖于 MySQL 的非保证行为。
这是声明的框架:
SELECT @prev_userid AS prev_userid
, @prev_activitydate AS prev_activitydate
, @sessionid AS sessionid
, @prev_userid := t.userid AS userid
, @prev_activitydate := t.activitydate AS activitydate
FROM (SELECT @prev_userid := NULL, @prev_activitydate := NULL, @sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
(我们希望有一个索引ON mytable (userid, activitydate)
,这样查询就可以从索引中得到满足,而不需要昂贵的“Using filesort”操作。)
让我们稍微解压一下。首先,三个 MySQL 用户变量由别名为 i
的内联 View 初始化。我们并不关心它返回什么,我们只关心它初始化了用户变量。因为我们在 JOIN 操作中使用它,所以我们也关心它只返回一行。
当第一行被处理时,我们有之前分配给用户变量的值,我们将当前行的值分配给它们。当处理下一行时,前一行的值在用户变量中,我们将当前行值分配给它们,依此类推。
查询中的“ORDER BY”很重要;以正确的顺序处理行至关重要。
但这只是一个开始。
下一步是比较当前行和上一行的 userid 和 activitydate 值,并决定我们是否处于相同的 sessionid,或者是否是不同的 session ,我们需要将 sessionid 递增 1。
SELECT @sessionid := @sessionid +
IF( t.userid = @prev_userid AND
TIMESTAMPDIFF(SECOND,@prev_activitydate,t.activitydate) <= 3600
,0,1) AS sessionid
, @prev_userid := t.userid AS userid
, @prev_activitydate := t.activitydate AS activitydate
FROM (SELECT @prev_userid := NULL, @prev_activitydate := NULL, @sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
您可以使用存储在现有 time_diff
列中的值,但是在检查当前行时需要前一行的值,这样就只是另一个 MySQL 用户变量,检查@prev_time_diff,而不是计算时间戳差异(如我上面的示例。)(我们可以将其他表达式添加到选择列表中,以使调试/验证更容易...
, @prev_userid=t.userid
, TIMESTAMPDIFF(SECOND,@prev_activitydate,t.activitydate)
注意SELECT 列表中表达式的顺序很重要;表达式按照它们出现的顺序进行评估...如果我们在检查之前将当前行的用户标识值分配给用户变量,这将不起作用...这就是为什么这些分配在 SELECT 列表中排在最后.
一旦我们有一个看起来不错的查询,它会返回一个“sessionid”值,我们希望将其分配给具有匹配用户 ID 和事件日期的行,我们可以在多表更新语句中使用它。
UPDATE (
-- query that generates sessionid for userid, activityid goes here
) s
JOIN so_time_diff t
ON t.userid = s.userid
AND t.activitydate = s.activity_date
SET t.sessionid = s.sessionid
(如果有很多行,这可能需要很长时间。对于 5.6 之前的 MySQL 版本,我相信派生表(别名为 s
)不会有任何索引在其上创建。希望MySQL将派生表s
作为JOIN操作的驱动表,并对目标表进行索引查找。)
跟进
我完全错过了为每个用户在 1 处重新启动 sessionid 的要求。为此,我将修改分配给@sessionid 的表达式,只是拆分 userid 和 activitydate 的条件测试。如果userid与上一行不同,则返回1。否则,根据activitydate的比较,返回@sessionid的当前值,或者当前值加1。
像这样:
SELECT @sessionid :=
IF( t.userid = @prev_userid
, IF( TIMESTAMPDIFF(SECOND,@prev_activitydate,t.activitydate) <= 3600
, @sessionid
, @sessionid + 1 )
, 1 )
AS sessionid
, @prev_userid := t.userid AS userid
, @prev_activitydate := t.activitydate AS activitydate
FROM (SELECT @prev_userid := NULL, @prev_activitydate := NULL, @sessionid := 1) i
JOIN so_time_diff t
ORDER BY t.userid, t.activitydate
注意这些陈述都没有经过测试,这些陈述仅经过案头检查;我已经无数次成功地使用了这个模式。
关于mysql - 需要帮助在 mysql 中使用 if 条件和自动增量编写此更新查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23689401/