Natalie King
Natalie King

Reputation: 35

MS SQL GROUPED SUM

I currently have an MS SQL query which calculates the length of time that each User has been logged in to a system during one day. The table I am extracting this information from records each log in/log out as a separate record. Currently, my MS SQL code is as follows:

SELECT 
    CAST(DateTime As Date), 
    UserID, 
    MIN(DateTime),
    MAX(DateTime),
    DATEDIFF(SS, MIN(DateTime), MAX(DateTime))
FROM 
    LoginLogoutData 
WHERE 
    CAST(DateTime AS DATE) = '01/01/2015' 
GROUP BY 
    CAST(DateTime As Date), 
    UserID

This works as required and creates a table similar to the below.

    Date        UserID  FirstLogIn   FinalLogOut    LoggedInTime
  .........     ......  ..........   ............   ............
  01/01/2015    ABC     07:42:57     14:57:13         26056    
  01/01/2015    DEF     07:45:49     13:57:56         22326  

This works fine for one day's-worth of data. However, if I wanted to calculate the length of time that someone was logged into the system for during a larger date range, e.g. a week or month, this would not work; it would calculate the length of time between the user's log in on the first day and their log out on the final day.

Basically, I would like my code to calculate (Max(DateTime) - Min(DateTime)) FOR EACH DAY then sum all these values together into one simple table grouped only by UserId. I would then be able to set my date range as I please and receive the correct results.

So I would have a table as follows:

  UserId     LoggedInTime
 ........   ............. 
    ABC         563287
    DEF         485823
    GEH         126789

I assume I need to use a GROUP BY within the MIN() function but I don't have much experience with this yet.

Does anyone have any experience with this? Any help would be greatly appreciated.

Thank you.

Upvotes: 1

Views: 282

Answers (4)

Sarath Subramanian
Sarath Subramanian

Reputation: 21271

Since you are already calculating LoggedInTime for each date, the following query would be necessary

SELECT USERID,SUM(LoggedInTime) LoggedInTime
FROM YOURTABLE
GROUP BY USERID

UPDATE

Since you have one record for login and the next record for logout(irrespective of date),we can use the concept of SELF JOIN(a table that joins itself) to get logout datetime for corresponding login time.

DECLARE @FROMDATE DATETIME='2014-01-01 07:42:57.000'
DECLARE @TODATE DATETIME='2015-02-01 07:42:57.000'    

;WITH CTE AS
(    
    -- ROW_NUMBER() is taken as logic for self joining. 
    SELECT ROW_NUMBER() OVER(ORDER BY USERID,[DATETIME]) RNO,* 
    FROM #TEMP
    WHERE [DATETIME] >= @FROMDATE AND [DATETIME] < @TODATE 
)
,CTE2 AS
(
    -- Since we are using SELF JOIN,we will get the next row's 
    -- datetime(ie, Logout time for corresponding login time)
    SELECT C1.*,C2.[DATETIME] [DATETIME2],
    DATEDIFF(SS, C1.[DateTime], C2.[DateTime]) SECONDSDIFF 
    FROM CTE C1
    LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
    WHERE C1.RNO%2 <> 0
    -- Since we get the next row's datetime in current row,
    -- we omit each logout time's row
)
SELECT USERID,SUM(SECONDSDIFF) SECONDSDIFF
FROM CTE2

Upvotes: 0

SDillon
SDillon

Reputation: 313

Here is an example of how to do this with working sample data and the T-SQL included.

 -- original table you described
 CREATE TABLE LoginLogoutData  (UserID int, DateTime DateTime)

 GO

 -- clean any previous sample records
 TRUNCATE TABLE LoginLogOutData

 /*local variables for iteration*/
 DECLARE @i int = 1
 DECLARE @n int
 DECLARE @entryDate DateTime = GETDATE()

 --populate the table with some sample data
 /* for each of the  five sample users, generate sample login and logout 
  data for 30 days. Each login and logout are simply an hour apart for demo purposes. */
 SET NOCOUNT ON

 -- iterate over 5 users (userid)
 WHILE (@i <= 5)
 BEGIN

 --set the initial counter for the date loop 
 SET @n = 1

--dated entry loop
WHILE (@n <= 30)
BEGIN
   -- increment to the next day
   SET @entryDate = DateAdd(dd,@n,GETDATE())

        --logged in entry
        INSERT INTO LoginLogoutData (DateTime, UserID)          
        SELECT @entryDate,@i 

        -- logged out entry
        INSERT INTO LoginLogoutData (DateTime, UserID)
        SELECT DateAdd(hh,1,@entryDate),@i 

    --increment counter 
    SET @n = @n+1
 END

 --increment counter 
 SET @i=@i+1
END

GO


/* demonstrate that for each user each day has entries and that
the code calculates (Max(DateTime) - Min(DateTime)) FOR EACH DAY 
*/
SELECT UserID, 
   MIN(DateTime) AS LoggedIn, 
   MAX(DateTime) AS LoggedOut, 
   DATEDIFF(SS, MIN(DateTime), MAX(DateTime)) AS LoginTime
FROM LoginLogoutData 
GROUP BY CAST(DateTime As Date), UserID

/*this is a table variable used to support the "sum all these values together into one 
simple table grouped only by UserId*/
DECLARE @SummedUserActivity AS TABLE (UserID int, DailyActivity int)



 -- group the subtotals from each day per user 
   INSERT INTO @SummedUserActivity (UserID, DailyActivity)
   SELECT UserID, DATEDIFF(SS, MIN(DateTime), MAX(DateTime))
   FROM LoginLogoutData 
   GROUP BY CAST(DateTime As Date), UserID

   -- demonstrate the sum of the subtotals grouped by userid
   SELECT UserID, SUM(DailyActivity) AS TotalActivity
   FROM @SummedUserActivity
   GROUP BY UserID

Upvotes: 0

Gordon Linoff
Gordon Linoff

Reputation: 1269443

First you need to aggregate by date, and then by larger units of time. For instance, for the year to date:

SELECT UserId, SUM(diffsecs)
FROM (SELECT CAST(DateTime As Date) as thedate, UserID,
             DATEDIFF(second, MIN(DateTime), MAX(DateTime)) as diffsecs
      FROM LoginLogoutData
      GROUP BY CAST(DateTime As Date), UserID
     ) ud
WHERE thedate between '2015-01-01' and getdate();

Upvotes: 1

Chamal
Chamal

Reputation: 1449

You can use another group by statement to the first query like below:

Select UserID,SUM(LoggedTime)
FROM
(
SELECT CAST(DateTime As Date), 
       UserID, MIN(DateTime), 
       MAX(DateTime), 
       DATEDIFF(SS, MIN(DateTime), MAX(DateTime)) AS LoggedTime
FROM LoginLogoutData 
WHERE CAST(DateTime AS DATE) = '01/01/2015'
GROUP BY CAST(DateTime As Date), UserID
) As temp
GROUP BY UserID

Here you can change the where clause to match the data range. It will first calculate logged time for each day and then get the sum of all days per user.

Upvotes: 0

Related Questions