使用MySQL计算最多在线用户数

5

我有一个表格,记录用户登录和退出时间(他们登录的应用程序是VB,与MySQL服务器通信)。该表格看起来像下面的例子:

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   |

所以每次用户登录时,它都会向表中添加一个新行,其中包含他们的用户名和时间戳。类型为“1”表示登录。当他们退出时,同样的情况发生,只是类型为“0”。
存在一些问题,如果用户强制退出应用程序,则似乎永远不会显示已注销,因为这显然绕过了提交注销查询(类型“0”)的过程。但请忽略它,并假设我找到了解决该问题的方法。
我想知道要运行的查询(可能每周运行一次)来计算任何一次同时登录的用户最多有多少人。这是否可能?对我来说,这似乎是一个巨大的数学/ SQL 挑战!该表目前有约 30k 行。
哇!谢谢大家!我已经改编了mifeet的答案,得到了我所需的最短代码。简直不能相信我可以用这么少的代码完成它,我以为我必须使用蛮力或重新设计我的数据库!
set @mx := 0;
select time,(@mx := @mx + IF(type,1,-1)) as mu from log order by mu desc limit 1;

提示:如果Type是+1或-1,那么你可以在另一列中保持一个运行总和,或者之后计算一个总和。当然,你可以通过进行一些数学计算来解决这个问题,但使用+/- 1更容易看到解决方案。 - Paul
你试过什么了吗? - RandomUs1r
1
谢谢,但是如何将+1和-1写入行中呢?是Type=-1 / Type=+1还是Type='1' / Type ='-1' - 我猜数据类型需要是INT? - pedromillers
3个回答

3
你可以使用MySQL变量来计算当前已登录访问者的累计值,然后获取最大值:
SET @logged := 0;
SET @max := 0;

SELECT 
     idLoginLog, type, time,
    (@logged := @logged + IF(type, 1, -1)) as logged_users,
    (@max := GREATEST(@max, @logged))
FROM logs
ORDER BY time;

SELECT @max AS max_users_ever;

(SQL Fiddle)


编辑:我还有一个建议,如何处理没有明确注销的用户。假设您认为用户在30分钟后自动注销:

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

SELECT 
     -- Same as before
     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;

(Fiddle)


难以置信。这是天赋还是我也有一天能变得如此伟大? - pedromillers
@pedromillers:你需要花费数小时与MySQL打交道并搜寻良好的解释才能学会这些东西,所以小心你的愿望!;-) - flaschenpost
到目前为止,我已经花了大约1000小时的奋斗时间,我原以为会变得不那么疼痛,但明显它变得更加痛苦了! :) - pedromillers
抱歉,我对此还不熟悉。你已经得到了你的勾选!但是我实际上并没有得到我的结果。代码没问题,但结果完全不现实。数据库中有很多注销条目,但没有登录条目。我需要一种方法来省略这些条目,或者在两分钟前插入一个登录条目。如果你愿意接受挑战,我已经将这个问题放在这里:http://stackoverflow.com/questions/16887331/insert-or-omit-entries-in-mysql-query-based-on-conditions-of-results - pedromillers

1
如果Type为+1表示登录,-1表示注销,而您添加了注销条目,则可以执行以下操作:
CREATE TABLE usage
SELECT a.Time AS Time, SUM(b.Type) AS Users
FROM logons AS a, logons AS b
WHERE b.Time < a.Time;

在某些SQL中,您需要将其拆分为多个语句。我相信MySQL有INSERT SELECT,但不确定是否有CREATE...SELECT

此表格可让您查看任何时间的使用情况。

要获取最大值,Bill建议

SELECT * from Usage
[WHERE if you want a time range]
ORDER BY Users DESC LIMIT 1;  

请注意,最大值可能不唯一,这会引发各种问题。可能有多个时间您的用户数量达到了最大值...... 您可以通过将 LIMIT 1 更改为 LIMIT 5LIMIT 10 并查看表格来探索此问题。

是的,MYSQL有CREATE TABLE...SELECT。您可以使用SUM(IF(b.Type=1,1,-1)) - Bill Karwin
@Bill 是的,那样做就可以了。 - Paul
哇 - 是否可以找出这是什么日期/时间? - pedromillers
SELECT * FROM usage ORDER BY Users DESC LIMIT 1 - Bill Karwin
1
它将需要O(n^2)的时间,即如果表格变为原来的两倍,查询所需的时间将增加4倍。 - flaschenpost

0

[已移除:统计每个用户的登录次数]

编辑:在澄清问题并且只是为了好玩(那个小表格不需要太多真正的优化)之后,我会这样做:

create table NumberUsers (Time datetime, number int, key(Time));

你可以保留那个表格,因为旧的登录不会改变。请在你开始之前用一个时间和0(零)个用户初始化该表格:

insert into NumberUsers ('2010-01-01 00:00:00', 0);

那曾经是工作。现在是每周的工作:

set @count := (select number from NumberUsers order by Time desc limit 1);
set @lastCalculate := (select max(Time) from NumberUsers);

insert into NumberUsers select Time, @count := @count + if(Type=0, -1, 1) 
from loginList where Time > @lastCalculate order by Time;

这应该很快,因为它只需要进行一次扫描。


是的,抱歉,我指的是同时登录的用户数量。当用户登录时,我有一个屏幕显示当前所有已登录的用户 - 我想在下面添加一个文本标签,显示“最多在线用户数为X,日期为dd-mm-yyyy”。 - pedromillers
好的,你的问题对我来说不是很明确。@mifeets的解决方案基本上与我的解决方案相同,只是我的解决方案存储旧结果并仅读取自上次“weekly”计划以来的条目,而不依赖于“weekly”。 - flaschenpost

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接