查找SQL记录中的并发用户数

时间:2009-07-12 21:40:49

标签: sql-server tsql

我有以下结构表:

UserID   StartedOn          EndedOn
1        2009-7-12T14:01    2009-7-12T15:01 
2        2009-7-12T14:30    2009-7-12T14:45
3        2009-7-12T14:47    2009-7-12T15:30
4        2009-7-12T13:01    2009-7-12T17:01
5        2009-7-12T14:15    2009-7-12T18:01
6        2009-7-12T11:01    2009-7-12T19:01
1        2009-7-12T16:07    2009-7-12T19:01

我需要找到在线的最大并发用户数。在上表中,结果将是5,因为用户set1 = {1,2,4,5,6}和set2 = {1,3,4,5,6}在同一时期在线。

您是否知道如何仅使用T-SQL来计算此内容?

7 个答案:

答案 0 :(得分:9)

显然,当用户开始或结束一段时间时,并发用户的数量才会发生变化,因此足以确定开始和结束期间的并发用户数。因此,重用Remus提供的测试数据(谢谢Remus):

DECLARE @Table TABLE 
(
  UserId int, 
  StartedOn datetime,
  EndedOn datetime
);

insert into @table (UserId, startedOn, EndedOn)
select 1, '2009-7-12 14:01', '2009-7-12 15:01'
union all select 2, '2009-7-12 14:30', '2009-7-12 14:45'
union all select 3, '2009-7-12 14:47', '2009-7-12 15:30'
union all select 4, '2009-7-12 13:01', '2009-7-12 17:01'
union all select 5, '2009-7-12 14:15', '2009-7-12 18:01'
union all select 6, '2009-7-12 11:01', '2009-7-12 19:01'
union all select 1, '2009-7-12 16:07', '2009-7-12 19:01';

SELECT MAX(ConcurrentUsers) FROM(
SELECT COUNT(*) AS ConcurrentUsers FROM @table AS Sessions 
JOIN 
(SELECT DISTINCT StartedOn AS ChangeTime FROM @table
) AS ChangeTimes
ON ChangeTime >= StartedOn AND ChangeTime < EndedOn 
GROUP BY ChangeTime
) AS ConcurrencyAtChangeTimes
-------
5

BTW使用DISTINCT本身并不是一个错误 - 只是滥用DISTINCT。 DISTINCT只是一个工具,在这种情况下使用它是完全正确的。

编辑:我正在回答OP的问题:“如何才能使用T-SQL来计算这个问题”。 请注意,问题没有提及表现。

如果问题是:“如果数据存储在SQL Server中,确定最大并发的最快方法是什么”,我会提供不同的答案,如下所示:

考虑以下备选方案

  1. 写一个光标
  2. 编写CLR光标
  3. 在客户端上写一个循环
  4. 使用具有正确游标的RDBMS,例如Oracle或PostgreSql
  5. 为获得最佳性能,请以不同方式设计您的表格,以便您可以在一个索引搜索中检索答案。如果我需要提供最佳性能,这就是我在系统中所做的事情。
  6. 如果问题是“使用T-SQL查询确定最大并发的最快方法是什么”,我可能根本不会回答。原因是:如果我需要非常好的性能,我不会在T-SQL查询中解决这个问题。

答案 1 :(得分:3)

您可以按日期顺序订购所有活动,并计算登录的当前用户的正在运行的聚合:

DECLARE @Table TABLE 
(
  UserId int, 
  StartedOn datetime,
  EndedOn datetime
);

insert into @table (UserId, startedOn, EndedOn)
select 1, '2009-7-12 14:01', '2009-7-12 15:01'
union all select 2, '2009-7-12 14:30', '2009-7-12 14:45'
union all select 3, '2009-7-12 14:47', '2009-7-12 15:30'
union all select 4, '2009-7-12 13:01', '2009-7-12 17:01'
union all select 5, '2009-7-12 14:15', '2009-7-12 18:01'
union all select 6, '2009-7-12 11:01', '2009-7-12 19:01'
union all select 1, '2009-7-12 16:07', '2009-7-12 19:01';

with cte_all_events as (
select StartedOn as Date
    , +1 as Users
    from @Table
union all 
select EndedOn as Date
    , -1 as Users
    from @Table),
cte_ordered_events as (
select Date
    , Users
    , row_number() over (order by Date asc) as EventId
    from cte_all_events)
, cte_agg_users as (
  select Date
    , Users
    , EventId
    , (select sum(Users) 
        from cte_ordered_events agg
        where agg.EventId <= e.EventId) as AggUsers
    from cte_ordered_events e)
select * from cte_agg_users


2009-07-12 11:01:00.000 1   1   1
2009-07-12 13:01:00.000 1   2   2
2009-07-12 14:01:00.000 1   3   3
2009-07-12 14:15:00.000 1   4   4
2009-07-12 14:30:00.000 1   5   5
2009-07-12 14:45:00.000 -1  6   4
2009-07-12 14:47:00.000 1   7   5
2009-07-12 15:01:00.000 -1  8   4
2009-07-12 15:30:00.000 -1  9   3
2009-07-12 16:07:00.000 1   10  4
2009-07-12 17:01:00.000 -1  11  3
2009-07-12 18:01:00.000 -1  12  2
2009-07-12 19:01:00.000 -1  13  1
2009-07-12 19:01:00.000 -1  14  0

一旦你有了这个,找到最大并发会话的数量是微不足道的。如你所见,有两个时刻你有5个用户,在14:30(当用户2登录时)和14:47(当用户3登录时)。只需替换从CTE中选择的最后一个查询即可获得实际最大值:

select top(1) AggUsers 
    from cte_agg_users
    order by AggUsers desc

此解决方案使用CTE,因此它只适用于SQL 2k5,如果您仍在SQL 2000上,则必须使用派生表而不是CTE重写它。

答案 2 :(得分:1)

我尝试了AlexKuznetsov的解决方案但结果是49 :(

我的解决方案:

/* Create temporary table and set all dates into 1 column,
so we can sort by this one column */
DECLARE @tmp table (
    Dates datetime,
    IsStartedDate bit )

INSERT INTO @tmp
    SELECT StartedOn, 1 FROM stats
    UNION ALL
    SELECT EndedOn, 0 FROM stats

DECLARE @currentlogins int, @highestlogins int, @IsStartedDate bit;
SET @currentlogins = 0;
SET @highestlogins = 0;

DECLARE tmp_cursor CURSOR FOR 
SELECT IsStartedDate FROM @tmp
ORDER BY Dates ASC

OPEN tmp_cursor

/* Step through every row, if it's a starteddate increment @currentlogins else decrement it
When @currentlogins is higher than @highestlogins set @highestlogins to the new highest value */
FETCH NEXT FROM tmp_cursor 
INTO @IsStartedDate

WHILE @@FETCH_STATUS = 0
BEGIN
    IF (@IsStartedDate = 1)
    BEGIN
        SET @currentlogins = @currentlogins + 1;
        IF (@currentlogins > @highestlogins)
            SET @highestlogins = @currentlogins;
    END
    ELSE
        SET @currentlogins = @currentlogins - 1;

    FETCH NEXT FROM tmp_cursor 
    INTO @IsStartedDate
END

CLOSE tmp_cursor
DEALLOCATE tmp_cursor

SELECT @highestlogins AS HighestLogins

答案 3 :(得分:0)

我使用整数而不是日期时间字段来完成工作,但我相信以下sql代码段可以获得您想要的内容。

基本上,我使用自联接比较了每个用户的开始和结束日期。如果用户A在用户B和用户B之前或同时在用户A结束之前或与用户A同时启动的同时启动,则它们同时运行。因此,我发现用户具有最大并发用户数(并且因为我在自连接中将它们排除在外,所以自己添加了1)。

我注意到每个用户都有多行。请注意下面的sql假定同一个用户不能同时运行多个实例(同时)。如果这个假设不成立,我希望你有一个额外的列,每行是唯一的。在整个sql例程中使用此列而不是UserId。

我让你真的很亲密。我希望这有帮助。祝你好运。

DECLARE @Table TABLE 
(
  UserId int, 
  StartedOn int,
  EndedOn int
)

Insert Into @Table
Select 1, 1, 3
union
Select 2, 2, 4
union
Select 3, 3, 5
union
Select 4, 4, 6
union
Select 5, 7, 8
union
Select 6, 9, 10
union
Select 7, 9, 11
union
Select 8, 9, 12
union
Select 9, 10, 12
union
Select 10, 10, 13

--Select * from @Table

Select 
    A.UserId, 
    Count(B.UserId) + 1 as 'Concurrent Users'
FROM @Table A, @Table B
WHERE A.StartedOn <= B.StartedOn
AND B.StartedOn <= A.EndedOn
AND A.UserId != B.UserId
Group By A.UserId
Order By Count(B.UserId) Desc

答案 4 :(得分:0)

天真的做法:
当用户使用

登录时,您可以测试当前是否有其他用户b登录
a.StartedOn BETWEEN b.StartedOn AND b.EndedOn

有人必须成为“最多并发用户”的“最终登录” 如果您现在浏览所有记录(作为a)并检查当时登录的用户数量(b),然后订购列表(desc),则第一个结果是最大并发用户数。

SELECT
  a.id, a.UserId, a.StartedOn, a.EndedOn,  
  (  
    SELECT    
      Count(*)      
    FROM    
      logons as b      
    WHERE    
      a.StartedOn BETWEEN b.StartedOn AND b.EndedOn            
  ) as c
FROM
  logons as a 
ORDER BY
  c desc

现在阅读Database development mistakes made by application developers以了解这是多么低效(甚至是错误);-)
例如你有一个大的临时表,按顺序操作,没有任何索引来帮助sql server。

(顺便说一句:我用MySQL测试了这个,因为我现在手头没有SQL服务器)

答案 5 :(得分:0)

这不是解决方案。因为,在发布这个帖子时,最受欢迎的解决方案对于较少的行有一个非常讨厌的CROSS JOIN,对于大量的行有一个非常令人讨厌的TRIANGULAR JOIN,我想我会发布一些代码来制作更多大量的测试数据供人们进行测试。让比赛开始吧。 ; - )

DROP TABLE #Table
GO
WITH
cteStartedOn AS
(
 SELECT TOP 100000 --LOOK!  Change this number to vary the number of rows you're testing with.
        UserID = ABS(CHECKSUM(NEWID()))%1000,
        StartedOn = RAND(CHECKSUM(NEWID()))*DATEDIFF(dd,'2012','2013')+CAST('2012' AS DATETIME)
   FROM sys.all_columns ac1, sys.all_columns ac2
)
 SELECT UserID, StartedOn,
        EndedOn = DATEADD(ss,ABS(CHECKSUM(NEWID()))%36000,StartedOn) --10 hours max
   INTO #Table
   FROM cteStartedOn;

答案 6 :(得分:-2)

你在那张桌子上自我加入