Mysql 无循环会话的页面视图

Mysql 无循环会话的页面视图,mysql,sql,Mysql,Sql,我有一个具有挑战性的SQL问题:假设您有一个页面视图表,如下所示: CREATE TABLE pageviews ( id INT(11) NOT NULL AUTO_INCREMENT, user_id INT(11) NOT NULL, timestamp DATETIME NOT NULL, PRIMARY KEY (id) ) CREATE TABLE sessions ( id INT(11) NOT NULL AU

我有一个具有挑战性的SQL问题:假设您有一个页面视图表,如下所示:

CREATE TABLE pageviews (
  id          INT(11) NOT NULL AUTO_INCREMENT,
  user_id     INT(11) NOT NULL,
  timestamp   DATETIME NOT NULL,
  PRIMARY KEY (id)
)
CREATE TABLE sessions (
  id          INT(11) NOT NULL AUTO_INCREMENT,
  user_id     INT(11) NOT NULL,
  started_at  DATETIME NOT NULL,
  ended_at    DATETIME NOT NULL,
  PRIMARY KEY (id)
)
在这个表中,有大量的记录>1亿条。根据此数据,您希望生成另一个如下所示的表:

CREATE TABLE pageviews (
  id          INT(11) NOT NULL AUTO_INCREMENT,
  user_id     INT(11) NOT NULL,
  timestamp   DATETIME NOT NULL,
  PRIMARY KEY (id)
)
CREATE TABLE sessions (
  id          INT(11) NOT NULL AUTO_INCREMENT,
  user_id     INT(11) NOT NULL,
  started_at  DATETIME NOT NULL,
  ended_at    DATETIME NOT NULL,
  PRIMARY KEY (id)
)
规则是,会话是任意数量的页面浏览的任意序列,其中不包含任何大于30分钟的间隔

现在,我已经使用一个存储过程生成了这个表,该存储过程使用一个循环来获取会话:

DELIMITER |
CREATE PROCEDURE generate_sessions() 
BEGIN
  TRUNCATE sessions;

  INSERT INTO sessions
  SELECT NULL, p.user_id, p.timestamp, p.timestamp FROM pageviews p
  LEFT JOIN pageviews2 p2 ON p2.user_id = p.user_id AND p2.timestamp > p.timestamp AND p2.timestamp < DATE_ADD(p.timestamp, INTERVAL 30 MINUTE)
  WHERE p2.id IS NULL;

  REPEAT    
    UPDATE sessions s
    LEFT JOIN pageviews p ON p.user_id = s.user_id AND p.timestamp < s.started_at AND p.timestamp > DATE_SUB(s.started_at, INTERVAL 30 MINUTE)
    SET s.started_at = p.timestamp
    WHERE p.id IS NOT NULL;
  UNTIL ROW_COUNT() = 0 END REPEAT;
END |
基本上,该过程首先获取任何会话的最新页面视图,将其插入表中,然后迭代回溯,直到所有会话完成


不用说,这是难以置信的缓慢。任何人都有更好的解决方案,最好只涉及一个查询?

这是MySQL中的一个难题。你真的需要这个窗口函数

但是,有一种方法。首先,您需要定义每个会话。为此,请查找页面浏览间隔大于半小时的间隔。下面的查询向后看,因此称为PrevSessionEnd

接下来,由于时间在增加,请为在给定页面视图上或之前出现的用户的所有页面视图选择此值的最大值。结果应该是,每个页面视图都会在会话期间获得一个常量值。第一个为空,第二个为第一个会话的最大时间戳,依此类推

然后,按此数量分组

select USER_ID, MIN(timestamp) as started_at, MAX(timestamp) as ended_at
from (select pv.*,
             (select MAX(prevSessionEnd)
              from (select pv.*,
                           (select timestamp
                            from pageviews pv2
                            where pv2.useid = pv.user_id and pv2.timestamp < pv.timestamp and
                                  (pv.timestamp - pv2.timestamp) > 0.5/24
                            order by timestamp
                            limit 1
                          ) as PrevSessionEnd
                    from pageviews pv
                   ) pv2
              where pv.user_id = pv2.user_id and pv2.timestamp <= pv.timestamp
             ) as SessionGrouper
      from pageviews pv
     ) pv
group by user_id, SessionGrouper
此特定查询尚未测试,因此可能存在语法错误

我把最后的插页留给你


反过来,如果你在pageviewsuser_id上有一个索引,timestamp,那么这将运行得更快。子查询只能使用此索引解决。

很好的解决方案。不幸的是,它似乎并不比循环解决方案快,至少对于我的200万页面浏览量的示例数据来说是如此。任何关于如何提高绩效的建议都将受到欢迎。其中一个建议是建立一个索引。另一种方法是删除任何无关的列—例如,如果表中有URL/URI,这些列可能会占用大量空间。如果表结构如您在问题中所描述的那样,那么就没有多少节省。