mysql - 需要帮助在 mysql 中使用 if 条件和自动增量编写此更新查询

标签 mysql if-statement sql-update

开始之前的背景......

表架构:

用户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/

相关文章:

python - 用户输入并陷入 while 循环

java - 将java语法折叠成单行

mysql - Go 中的 MySQL 错误

php - 选择按天分组的命中,包括命中数为 0 的天数

mysql - 数据库未配置。可用:[] (ActiveRecord::AdapterNotSpecified)

javascript - 简写 if/else 语句 : foo? foo:bar vs foo ||酒吧

php - mysql_affected_rows 返回 0 但数据库已更改

Java/JDBC - 使用 TableModels 显示单个数据

php - 如何修复 "load column of a database with double click on a td tag with jquery in a drop down menu"

postgresql - 连接字符串而不是仅仅替换它