1

I have some code to calculate the max amount of users to ever be logged on to an application simultaneously. The login table is structured as follows:

idLoginLog |  username  |        Time         |  Type  |
--------------------------------------------------------
     1     |  pauljones | 2013-01-01 01:00:00 |    1   |
     2     |  mattblack | 2013-01-01 01:00:32 |    1   |
     3     |  jackblack | 2013-01-01 01:01:07 |    1   |
     4     |  mattblack | 2013-01-01 01:02:03 |    0   |
     5     |  pauljones | 2013-01-01 01:04:27 |    0   |
     6     |  sallycarr | 2013-01-01 01:06:49 |    1   |

The code to find out the max users ever logged on simultaneously is as follows (there is a section to deal with users who do not explicitly log out i.e. if the application is killed without exiting properly):

SET @logged := 0;
SET @max := 0;

SELECT 
  idLoginLog, type, time,
  (@logged := @logged + IF(type, 1, -1)) AS logged_users,
  (@max := GREATEST(@max, @logged)) AS max_users
FROM ( -- Select from union of logs and records added for users not explicitely logged-out
  SELECT * from logs
  UNION
  SELECT 0 AS idLoginnLog, l1.username, ADDTIME(l1.time, '0:30:0') AS time, 0 AS type
  FROM -- Join condition matches log-out records in l2 matching a log-in record in l1
    logs AS l1
    LEFT JOIN logs AS l2
    ON (l1.username=l2.username AND l2.type=0 AND l2.time BETWEEN l1.time AND ADDTIME(l1.time, '0:30:0'))
  WHERE
    l1.type=1
    AND l2.idLoginLog IS NULL -- This leaves only records which do not have a matching log-out record
) AS extended_logs 
ORDER BY time;

SELECT @max AS max_users_ever;

http://sqlfiddle.com/#!2/9a114/34

The above code was acheived in the following stack overflow question: calculate most users ever online with MySQL

There is now a problem whereby the login entry has sometimes not been written to the table when users have logged on, so there is only a log out entry. This messes up the calculation completely. How can I update the query to ignore entries where there is not a prior "log in" entry? OR how can I add in "log-in" entries for say, 2 mins before any lone "log-out" entries, so that the above code can achieve a more reasonable result?

Community
  • 1
  • 1
pedromillers
  • 237
  • 1
  • 3
  • 13
  • Sorry, I know this won't help you with your question, but I think this kind of computing is best dealt with by your application. – Olivier Coilland Jun 02 '13 at 21:25
  • Well, it's true that as the conditions are getting more and more complex, using a single query is bad for maintenance. – Mifeet Jun 02 '13 at 21:28
  • Just to rule out simple solutions - is just deleting logout entries without a match an option? Or inserting corresonding login entries? Or would you rather handle it in your application as suggested by Olivier? – Mifeet Jun 02 '13 at 21:31
  • I intend to make some changes on application level to sort this out long term however was interested to know how I could find the answer (max simultaneous users) in the meantime. Deleting logout entries without a match would be fine, but preferably in a temporary copy of the table created solely for the query. – pedromillers Jun 02 '13 at 22:11
  • @pedromillers Hi, have you succeeded with the solution we came up the other day? – Mifeet Jun 05 '13 at 20:50

1 Answers1

0

If you need to detect which records represent a login without a matching logout record and vice versa, then it would be helpful to extended your tables with a unique session ID. Add a column session_id, generate its value on login, remember it in the session and put the same value to session_id for the logout error. It would simplify the queries a lot.


If you need a query that would add missing login records, try the following:

SELECT 0 AS idLoginnLog, l1.username, ADDTIME(l1.time, '-0:30:0') AS time, 1 AS type
FROM logs AS l1
  LEFT JOIN logs AS l2
  ON (l1.username=l2.username AND l2.type=1 AND l2.time BETWEEN ADDTIME(l1.time, '-0:30:0') AND l1.time)
WHERE
  l1.type=0
  AND l2.idLoginLog IS NULL

(Fiddle.) You can either insert the result to the table (INSERT INTO logs (...) SELECT ...) or add the query to the UNION in your original query.


As noted by @OlivierCoilland, the query is getting pretty complex and you can consider analysis on the application side. Because I guess the log table is pretty big, you shouldn't rely you'll fit all entries in memory. You would probably need some kind of "sliding window" technique.


The fourth option is deleting unmatched logout records. My solution needs a temporary table, so I do not paste the whole (pretty long) code here, just see the fiddle.

Mifeet
  • 12,949
  • 5
  • 60
  • 108
  • Regarding your session_id suggestion, do you mean I could then find all bad sessions by searching for session_id's that didn't appear in 2 rows? If so so, could you provide the query for that? – pedromillers Jun 02 '13 at 22:08
  • The other suggestion doesn't seem to work if I alter it to 30 seconds rather than minutes, do you know why? – pedromillers Jun 02 '13 at 22:09
  • Because pauljones in your sample data, for example, has a login and logout record 4 minutes apart. If you set it to 30 seconds, then you insert a new login record, but the old one will be left unmatched. You shouldn't use less than your session timeout. – Mifeet Jun 02 '13 at 22:11
  • I don't fully understand, where is the session timeout specified? I changed both occurences of "0:30:0" to "0:00:30" yet it gave a different result. Don't really understand why a time has to be specified. Some sessions can be less than 30 seconds (no idea why, some people may just log on to see who else is on and then go back off) where as some people may stay on the application for 10 hours. I just need to be able to delete 0 entries, if there is not a 1 entry prior, or if the users last entry was a 0 entry. Is this even possible? Thanks for your help so far :) – pedromillers Jun 02 '13 at 22:20
  • If you're using PHP, then session lifetiem is in [`session.cookie_lifetime`](http://www.php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime). See also [this answer](http://stackoverflow.com/a/3068758/2032064) – Mifeet Jun 02 '13 at 22:26
  • Why the "if the users last entry was a 0 entry" condition? – Mifeet Jun 02 '13 at 22:27
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31079/discussion-between-mifeet-and-pedromillers) – Mifeet Jun 02 '13 at 22:33
  • Sorry, I mean if it is like: Johndoe - 0 Johndoe - 1 Johndoe - 0 Johndoe - 0 Then the first and last entries will be deleted. The first because there is no prior "1" entry, the last, because the last johndoe entry is 0 when you would expect it to be 1. – pedromillers Jun 02 '13 at 22:33
  • chat session seems to have been killed? – pedromillers Jun 02 '13 at 23:22
  • I used your code in the chat to find out all missing 0's and missing 1's. I made a copy of the table and added all missing 0's and 1's to it, and then ran the code from the other night only get a much more unrealistic result of 1000+. I'll post more details in the chat! – pedromillers Jun 06 '13 at 23:20