Creating a new variable based on condition SQL

73 Views Asked by At

I am working on a project and have a table with the following data:

date time username type session_time
2023-08-28 07:56:50.0000000 aavery35 login NULL
2023-08-28 08:07:53.0000000 aavery35 view NULL
2023-08-28 08:08:48.0000000 aavery35 update NULL
2023-08-28 08:08:48.0000000 aavery35 update NULL
2023-08-28 08:08:48.0000000 aavery35 update NULL

I am trying to find a way to update the session_time variable to find where date = date and username = username then subtract logout:time - login:time

part of the problem is that every user does not necessarily have a logout time. In that instance i would like to go to their last action of that day and create a new row for that date, time, and username and create a logout instance.

I don't think this is possible with all the 'type' variables being in the same column. So instead i have created three new variables login_time, logout_time, session_time. I then ran the following code :

Alter Table ##remote_users
Add session_time date;

Alter Table ##remote_users
Add login_time time;

Alter Table ##remote_users
Add logout_time time;

Update ##remote_users
set login_time = time
where username = username AND date = date AND Type = 'login';

Update ##remote_users
set logout_time = time
where username = username AND date = date AND Type = 'logout';

Update ##remote_users
set session_time = login_time - logout_time
where username = username AND date = date;

select date, time, username, type, login_time, logout_time
from ##remote_users
WHERE type = 'login' OR type = 'logout' OR type = 'sessiontimeout'
order by username 

This was close because it created the variables that i needed but the session_time update did not work it gave the following error:

Msg 8117, Level 16, State 1, Line 57
Operand data type time is invalid for subtract operator.

Additionally, users without a logout time get a null value for teh variable logout_time.

1

There are 1 best solutions below

12
siggemannen On BEST ANSWER

You can do something like this:

select cast(date as date) as date, cast(time as time) as time, type, cast(session_time as datetime) as session_time, username
,   CAST(NULL AS TIME) AS login_time
,   CAST(NULL AS TIME) AS logout_time
into #remote_users
from 
(
    VALUES  (N'2023-08-28', N'07:56:50.0000000', N'aavery35', N'login', NULL)
    ,   (N'2023-08-28', N'08:07:53.0000000', N'aavery35', N'view', NULL)
    ,   (N'2023-08-28', N'08:08:48.0000000', N'aavery35', N'update', NULL)
    ,   (N'2023-08-28', N'08:08:48.0000000', N'aavery35', N'update', NULL)
    ,   (N'2023-08-28', N'17:08:48.0000000', N'aavery35', N'logout', NULL)
    ,   (N'2023-08-29', N'03:08:48.0000000', N'aavery35', N'login', NULL)
    ,   (N'2023-08-29', N'08:08:48.0000000', N'aavery35', N'update', NULL)
    ,   (N'2023-08-30', N'08:08:48.0000000', N'aavery35', N'login', NULL)
    ,   (N'2023-08-30', N'10:08:48.0000000', N'aavery35', N'logout', NULL)
    ,   (N'2023-08-30', N'13:08:48.0000000', N'aavery35', N'secret', NULL)
) t (date,time,username,type,session_time)

-- #1 Last time version
update  t
set login_time = loginTimeNew
,   logout_time = t.logoutTimeNew
,   session_time = CAST(dateadd(ms, datediff(ms, logintimenew, logouttimenew) , '19000101') AS TIME)
FROM    (
    select  min(time) OVER(PARTITION BY username, date) as loginTimeNew
    ,   Max(time) OVER(PARTITION BY username, date) as logoutTimeNew
    ,   *
    FROM    #remote_users t
    ) t


-- #2 "Real" times version
update  t
set login_time = loginTimeNew
,   logout_time = t.logoutTimeNew
,   session_time = CAST(dateadd(ms, datediff(ms, ISNULL(loginTimeRealNew,logintimenew), ISNULL(logoutRealTimeNew,logouttimenew)) , '19000101') AS TIME)
FROM    (
    select  min(time) OVER(PARTITION BY username,date) as loginTimeNew
    ,   Max(time) OVER(PARTITION BY username,date) as logoutTimeNew
    ,   min(case when type = 'login' then time end) OVER(PARTITION BY username, date) as loginTimeRealNew
    ,   Max(case when type = 'logout' then time end) OVER(PARTITION BY username, date) as logoutRealTimeNew
    ,   *
    FROM    #remote_users t
    ) t

I wrote two versions, #1 is the simple one that takes min and max time to create logintimeNew and logoutTimeNew columns, and then does a time diff manipulation to create the "time" part of a session time.

This assumes that first date per day is login, and last day if logout. If you really want to find the "session time" parts, you can use the #2 version. That one does a conditional aggregation to find login / logout rows, and falls back to first / last rows if those are missing

To explain the details:

min(time) OVER(PARTITION BY username, date) is a window aggregation that calculates minumum time grouped (partitioned) by user and date. This helps us get the needed date values per day.

CAST(dateadd(ms, datediff(ms, logintimenew, logouttimenew) , '19000101') AS TIME) this complex formula is to convert difference of two days in terms of "time" datatype. There's probably a better way, but this version calculates difference in milliseconds and then creates a date by adding milliseconds to the "zero date in sql server", which is 19000101. Then, by converting this to TIME, one gets the time part of the difference.