views:

81

answers:

3

Would it be possible to calculate time online from a table of something like

logtype   time  
----------------
login     2:30  
logout    2:45  
login     3:20  
logout    4:50  
login     5:00  
login     5:10  
logout    6:00  

It would have extra logins because sometimes the server crashes and doesn't add logout. Can you do something like this in MySQL only or would you have to use possibly ColdFusion? And if so, how would you do it in MySQL/ColdFusion?

All I'm wanting is a (rough) total of time online, hours, days, minutes etc for a particular user.

+1  A: 

(See my notes above first, please.)

For the data that you would have, however, by pairing consecutive login/logout pairs, you could process the data to get the information that you need just using a simple dateDiff([datepart], [date 1], [date 2]) function in CF to figure out the difference, if you had already queried the data.

For the data handling, I would suggest adding another row to your table -- one that handles a Session ID. This will ensure proper pairing of your login/logout times. You then could set up your queries something like the following:

<cfquery name="getSession" datasource="[dsn]">
   SELECT DISTINCT sessionID
   FROM logTable
   WHERE userID = <cfqueryparam cfsqltype="cf_sql_integer" value="[inputted userID]" />
</cfquery>

...then, to get the individual login sessions' login times, run a loop like this to output them...

<cfoutput query="getSessions">
   <cfquery name="getSessionInfo" datasource="[dsn]">
      SELECT 
      (SELECT time 
         FROM logTable 
         WHERE sessionID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#getSessions.sessionID# />
         AND logType = <cfqueryparam cfsqltype="cf_sql_varchar" value="login" />)
      AS loginTime,
      (SELECT time 
         FROM logTable 
         WHERE sessionID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#getSessions.sessionID# />
         AND logType = <cfqueryparam cfsqltype="cf_sql_varchar" value="logout" />)
      AS logoutTime
      FROM logTable
   </cfquery>

   [ancillary output - use your DateDiff here with getSessionInfo.loginTime and getSessionInfo.logoutTime as your two date/times and use the data as you need]
</cfoutput>

My SQL on this may be a little off, but at least it points you in the right direction... Hope this helps... feel free to contact me off-list for more explanation.

Steve K.
No. Hitting the database once for every row in the query is bad on several levels. There's almost never a need to query within a loop. It's usually a sign you need to rethink the approach ..
Leigh
Eh... is there a way to do it without the additional row? I would be happy to just throw away login rows without a logout pair.
Bubby4j
If you were looking for individual times, there's really not much more that you could do with this. If you're looking for a TOTAL time, you could always use a MySQL TIMEDIFF and run a single query to return a total for each of these. Is that more of what you're looking for?
Steve K.
@Bubby4j - Can you answer the questions in the first post? ie What is this information used for, how much volume, etcetera? There are different ways to approach it. But it would help us to know the answers to those questions.
Leigh
@Leigh ... In other words, what I'm saying, is that you're either looping in CF, or you're looping in SQL, for each individual session. It's not going to matter much where you do the processing, although SQL will probably handle it faster. However, unless you're talking about thousands of sessions, the processing time would be negligible, especially if the DB is running on the localhost.
Steve K.
@Steve K - It does make a difference. Regarding *If you were looking for individual times, there's really not much more that you could do with this* Yes, there are ways you can structure and query the data without requiring a loop. But the goal is still unclear. At least to me. So I am still waiting for a complete answer to the previous questions ;-)
Leigh
@Leigh I would agree that it's not the best way to lay out the data. If it were me, I'd run a LoginTime and LogoutTime on one row tied to a sessionID and userID. It would greatly simplify the query and reduce processing time. It sounds to me though like Bubby is already processing the data in the way he originally laid out, in which case, conversion to something like that would be more of a process than just bringing in that data to view it, and would also complicate the related query, which is why I say, it's going to have to loop somewhere.
Steve K.
@Steve K - If I am understanding correctly, is still possible to do this without looping. But again, there may be multiple options ... if @Bubby4j would answer the questions asked ;-)
Leigh
All I'm wanting is a (rough) total of time online, hours, days, minutes etc for a particular user.
Bubby4j
A: 

In my current project I'm using a bit another definition of the "user activity". For me this is period between session star and last page request. I'm not tracking activity time in exactly this format, but if I needed to -- I would make it this way.

When session starts -- create entry in the table sessions which may look like this:

id (int) auto-incremented PK
sessionid (varchar) -- optional, session id string from ColdFusion
userid (int) FK
session_start (datetime) -- filled when created
last_request (datetime)

Primary key is stored into the user session when entry created.

When user makes request -- update the last_request attribute.

It is also possible to update this when onSessionEnd fired, but you'll need to substract the session timeout from current time to get the last request moment -- which makes this useless.

Additional benefit of this approach is that you can get the aggregated data per user very easily. For example, this will select number of seconds (not tested, may be buggy):

<cfquery datasource="mydsn" name="qGetUserData">
    select userid, sum(unix_timestamp(last_request) - unix_timestamp(session_start)) as timespan
    from sessions 
    group by userid
</cfquery>

Filtering by period is easy too -- simply add where session_start ... clause, same for filtering by specific user(s).

Sergii
A: 

As others have suggested, modifying the table structure is a better approach in the long run. Trying to pair up log records (with no matching key) is difficult, and sometimes impossible. A slightly modified structure would be easier to query, and would yield more accurate results. However, if you absolutely cannot modify the table, it might be possible to calculate a rough total - IF your record ID is numeric/sequential.

The assumption is a "logout" record is always inserted after a "login". So the logout's record ID would always be greater than that of the corresponding "login". A subquery could convert the login information into pairs: ie current and next login record. The corresponding "logout" record id would fall within that range. Once you have matched up login/logouts, use date/time functions to calculate the time elapsed in minutes. Then SUM() the values and format the results however you wish. Note: Obviously proper indexing of the table is a must.

That said, this method only provides an estimate. It has several flaws:

  • Some databases do not optimize subqueries very well. So even with indexes, the query performance can be quite poor if there is a lot of data involved.
  • Relies solely upon the record id for identification.
  • Does not account for stray logout records or erroneous/duplicates. So it may be less accurate than other methods
  • Requires more convoluted sql than alternative structures

Anyway, I am not sure of the MySQL syntax off the top of my head. But ignoring the MS Sql Server specific functions, this should be close. The performance in MS SQL was decent with proper indexes, and about 100K records.

SELECT  SUM(DateDiff(n, li.LogTime, lo.LogTime)) AS TotalTimeInMinutes
FROM    (
            SELECT  li.RecordID, li.UserID, li.LogType, li.LogTime,
                   (    SELECT MIN(t.RecordID) FROM  YourTable t
                        WHERE t.UserID   = li.UserID
                        AND   t.LogType  = li.logType
                        AND   t.RecordID > li.RecordID
                    ) AS NextRecordID
            FROM   YourTable li
            WHERE  li.UserID = 123
            AND    li.LogType = 'login'
        ) 
        li INNER JOIN YourTable lo ON lo.UserID = li.UserID
             AND lo.LogType = 'logout'
             AND lo.RecordID BETWEEN li.RecordID AND li.NextRecordID
Leigh