Найти количество одновременных пользователей в записях SQL - PullRequest
12 голосов
/ 13 июля 2009

У меня есть таблица следующей структуры:

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 ]

9 голосов
/ 13 июля 2009

Очевидно, что число одновременных пользователей изменяется только тогда, когда пользователь начинает или заканчивает период, поэтому достаточно определить количество одновременных пользователей во время начала и окончания. Итак, повторное использование тестовых данных, предоставленных 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

Кстати, использование DISTINCT само по себе не является ошибкой - злоупотребляет только DISTINCT. DISTINCT - это просто инструмент, использование которого в этом контексте совершенно правильно.

Редактировать: Я отвечал на вопрос ОП: «Как можно рассчитать это, используя только T-SQL». Обратите внимание, что в вопросе не упоминается производительность.

Если бы вопросы были такими: «Какой самый быстрый способ определить максимальный параллелизм, если данные хранятся в SQL Server», я бы дал другой ответ, примерно такой:

Рассмотрим следующие альтернативы

  1. Написать курсор
  2. Написать курсор CLR
  3. Написать цикл на клиенте
  4. Используйте СУБД с приличными курсорами, такими как Oracle или PostgreSql
  5. Для максимальной производительности создайте таблицу по-другому, чтобы вы могли получить ответ одним поиском по индексу. Это то, что я делаю в своей системе, если мне нужно добиться максимальной производительности.

Если бы вопрос заключался в том, «какой самый быстрый способ определить максимальный параллелизм с помощью запроса T-SQL», я бы, вероятно, не ответил вообще. Причина: если бы мне нужна была действительно хорошая производительность, я бы не решил эту проблему в запросе T-SQL.

3 голосов
/ 13 июля 2009

Вы можете упорядочить все события по порядку дат и вычислить текущую совокупность текущих пользователей, вошедших в систему:

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.

1 голос
/ 13 июля 2009

Я попробовал решение Алекса Кузнецова, но результат был 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
0 голосов
/ 10 февраля 2013

Это НЕ решение. Так как во время этой публикации наиболее одобренное решение имело действительно неприятное 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;
0 голосов
/ 13 июля 2009

Наивный подход:
Вы можете проверить, вошел ли другой пользователь 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

А теперь прочитайте Ошибки при разработке баз данных, сделанные разработчиками приложений , чтобы увидеть, насколько это неэффективно (или даже неправильно); -)
например у вас есть большая временная таблица, в которой порядок заказов работает без индекса, чтобы помочь серверу sql.

(и кстати: я проверил это с MySQL, потому что у меня сейчас нет сервера sql)

0 голосов
/ 13 июля 2009

Я выполнил работу, используя целые числа, а не поля даты и времени, но я считаю, что следующий фрагмент SQL-кода даст вам то, что вы хотите.

По сути, я сравнил дату начала и окончания каждого пользователя по отношению друг к другу, используя самостоятельное соединение. Если пользователь A запускался до или одновременно с пользователем B, а пользователь B начинался до или одновременно с завершением пользователя A, они работают одновременно. Таким образом, я нашел пользователя с максимальным числом одновременных пользователей (и добавил 1 для себя, так как я исключил их в самосоединении.)

Я заметил, что у вас есть несколько строк для каждого пользователя. Пожалуйста, обратите внимание, что sql ниже предполагает, что один и тот же пользователь не может запускать несколько экземпляров одновременно (одновременно). Если это предположение не выполняется, я надеюсь, что у вас есть дополнительный столбец, который уникален для каждой строки. Используйте этот столбец, а не UserId во всей подпрограмме sql.

Я очень близко познакомил тебя. Надеюсь, это поможет. Желаем удачи.

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
0 голосов
/ 13 июля 2009

вы сами присоединяетесь к этому столу

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...