Question

Good day all.

I have this environment: on a Mysql db, everytime a user logins on a site, it is created a new row, with his name, and the time when he login. Since the system is mutual exclusive, there will be only a user at a given time, and if a new user arrive, the one logged is logged off.

now they have asked to me to calculate the total time of all users on the system, so basically, i have to sum together all the time differences from a login and its next one.

user  |       timestamp     |
------------------------------
alpha | 2013-01-19 03:14:07
beta  | 2013-01-20 11:24:04
alpha | 2013-01-21 02:11:37
alpha | 2013-01-21 03:10:31    <---- a user could login twice, it is normal
gamma | 2013-01-21 11:24:04
beta  | 2013-01-21 11:25:00

I would like to ask your opinion, since there are a lot of logins, which is the best way to calculate the total logged time of a user? in this example "gamma" will have a login time of 56 seconds, and the last login of beta could be ignored, since it will be online at the time of the execution of this check. so "beta" will have only one entry.

is there a way yo calculate it via query? or is better to add a column "time online" and let the sistem calculate each time a user logout how much time has spent online?

Was it helpful?

Solution

This requires a self-join if you are to do it in MySQL. It's a pain in the neck to do a self-join because MySQL has no built in rownum function. But it's still doable.

First, we need to create a subquery to create a virtual table simulating SELECT rownum, user, timestamp FROM login which we can do like this. http://sqlfiddle.com/#!2/bf6ef/2/0

SELECT @a:=@a+1 AS rownum, user, timestamp
    FROM (
        SELECT user, timestamp
          FROM login
         ORDER BY timestamp
    ) C,
    (SELECT @a:=0) s

Next, we need to do a self-join of this virtual table to a copy of itself. What we want in this result set is a list of all the consecutive pairs of rows in the table. That query is a hairball -- it puts the structured in structured query language. But it works. Here it is: http://sqlfiddle.com/#!2/bf6ef/4/0

SELECT first.user AS fuser, 
       first.timestamp AS ftimestamp,
       second.user AS suser,
       second.timestamp as stimestamp,
       TIMESTAMPDIFF(SECOND, first.timestamp, second.timestamp) AS timeloggedin

  FROM (
       SELECT @a:=@a+1 AS rownum, user, timestamp
         FROM (
             SELECT user, timestamp
               FROM login
           ORDER BY timestamp
              ) C,
          (SELECT @a:=0) s
        ) AS first
  JOIN (
       SELECT @b:=@b+1 AS rownum, user, timestamp
         FROM (
             SELECT user, timestamp
               FROM login
           ORDER BY timestamp
              ) C,
          (SELECT @b:=0) s
        ) AS second ON first.rownum+1 = second.rownum

The whole trick for comparing consecutive rows is the

SELECT (virtual_table) AS first
  JOIN (virtual_table) AS second ON first.rownum+1 = second.rownum

query pattern. The rownum+1 = rownum thing gathers rows with consecutive row numbers together.

Next, we need to summarize the result of that query to get the total time logged in for each user. That will work like this:

  SELECT user, SUM(timeloggedin) AS timeloggedin
    FROM (
          /* the self-joined query */
         ) AS selfjoin
   GROUP BY user
   ORDER BY user

That looks like this: http://sqlfiddle.com/#!2/bf6ef/5/0

This is the whole query put together.

SELECT user, SUM(timeloggedin) AS timeloggedin
  FROM (
      SELECT first.user AS user, 
             TIMESTAMPDIFF(SECOND, first.timestamp, second.timestamp) AS timeloggedin
        FROM (
             SELECT @a:=@a+1 AS rownum, user, timestamp
         FROM (
                   SELECT user, timestamp
                     FROM login
                 ORDER BY timestamp
                    ) C,
                (SELECT @a:=0) s
              ) AS first
        JOIN (
             SELECT @b:=@b+1 AS rownum, user, timestamp
               FROM (
                   SELECT user, timestamp
                     FROM login
                 ORDER BY timestamp
                    ) C,
                (SELECT @b:=0) s
              ) AS second ON first.rownum+1 = second.rownum
         ) AS selfjoin
   GROUP BY user
   ORDER BY user

It's not real intuitive for somebody used to procedural, algorithmic, thinking. But this is the way you do this kind of consecutive-row comparison in SQL.

OTHER TIPS

try with this...less or more is the solution of your problem...

    CREATE TABLE `matteo` (
      `user` varchar(20) DEFAULT NULL,
      `timestamp` int(11) DEFAULT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

    INSERT INTO matteo(user, `timestamp`) VALUES ('alpha', 7);
    INSERT INTO matteo(user, `timestamp`) VALUES ('beta', 9);
    INSERT INTO matteo(user, `timestamp`) VALUES ('alpha', 17);
    INSERT INTO matteo(user, `timestamp`) VALUES ('alpha', 27);
    INSERT INTO matteo(user, `timestamp`) VALUES ('gamma', 77);
    INSERT INTO matteo(user, `timestamp`) VALUES ('beta', 97);

    select a.*,b.*,b.`timestamp`-a.`timestamp` as delta
    from
    (SELECT @rownum := @rownum + 1 AS id,t.*
          FROM matteo t,(SELECT @rownum := 0) r) a
    join
    (SELECT @rownum2 := @rownum2 + 1 AS id,t.*
          FROM matteo t,(SELECT @rownum2 := 0) r) b 
    where a.id=b.id-1

:-) see u monday!!!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top