T-SQL count number of events in a time window - sql-server

I have a Login Table like this:
Date,UserID
2020-08-01,1
2020-09-01,1
2020-09-07,2
2020-10-01,3
2020-10-12,4
2020-10-25,1
Basically, every time a user logins to my application, it register the date and the userID. I need to know the number of active users. To me an active user is one that has login in the previous thirty days of every day in the last week. For instance, let's say we are counting this week which goes from 2020-10-09 (October 9 2020) to 2020-10-15 (October 15 2020). Then I would go like this:
Check UserID = 1:
Has user login anytime between ('2020-10-09' - 30) and '2020-10-09'
Has user login anytime between ('2020-10-10' - 30) and '2020-10-10'
And so on until 2020-10-15
Then, it goes the same for each user ID.
In other words, I check the number of users that have login in a sliding Window of 30 days during a week. I hope this makes sense.
This is what I had in mind:
DROP TABLE IF EXISTS #ActiveUsers
CREATE TABLE #ActiveUsers (UserID bigint)
DECLARE #lowerDate Datetime='2020-10-09'
DECLARE #upperDate Datetime='2020-10-15'
DECLARE #activeSpan int=30
WHILE(#lowerDate <= #upperDate)
BEGIN
SELECT e.UserID INTO #ActiveUsers FROM
(SELECT DISTINCT(UserID) FROM logins WHERE Date >= (#lowerDate - #activeSpan)) AS e
WHERE e.UserID NOT IN (SELECT UserID FROM #ActiveUsers)
SET #lowerDate = #lowerDate + 1
END
PRINT SELECT COUNT(*) FROM #ActiveUsers
My reasoning was like this:
Check unique UserIDs in the timeframe.
Insert the unique ones in the temporary table as ling as they have not been previously inserted
Count the total after looping through the selected dates.
But besides being quiet inelegant I have not been able to make it work.
I would appraciate any advice.
Thank you!

I'm not sure if I understand the requirement - but let me confirm my understanding
Let's assume your 'upperDate' is 7 October. That means you would like to check that a user had done all of the following
Had logged in between 1 Sep and 1 October
Had logged in betweem 2 Sep and 2 October
...
Had logged in between 7 Sep and 7 October
Now, imagine three users, all who had logged in only once
The first logged in on 20 September
The second logged in on 3 September
The third logged in on 3 October
Their results would be as follows
The first would count as active as they were identified by all 7 tests.
The second would not count as active as they fail the tests from '4 Sep to 4 Oct' and later.
The third would not count as active as they fail the tests from '1 Sep to 1 Oct' and '2 Sep to 2 Oct'.
In other words - I think all you need to do is find the logins between your upperdate minus 30 days, to upperdate minus 7 days.
As such you wouldn't need a loop - the easiest check is
DECLARE #upperDate Datetime = '20201015';
DECLARE #activeSpan int = 30;
SELECT DISTINCT(UserID)
FROM logins
WHERE [Date] >= DATEADD(day, - #activeSpan, #upperDate)
AND [Date] <= DATEADD(day, -7, #upperDate);

Related

Get count of latest consecutive daily logins

I have a SQL table containing a list of the daily logins of the subscribers to my site. The rows contain the user id and the date and time of the first login of each day, which means there is a maximum of one record per day for each member.
Is there a way to use SQL to get a count of the number if consecutive daily logins for each member, that is the latest login streak?
I could do this programmatically (C#) by going through each record for a user in reverse order and stop counting when a day is missing, but I was looking for a more elegant way to do this through a SQL function. Is this at all possible?
Thanks!
Answer from comment
You can use Lag function https://msdn.microsoft.com/en-IN/library/hh231256.aspx
If your database compatibility level is lower than 110 you cant use Lag function
The following code must get the latest streak of logins for you (only when there is record for 1st login of the day)
suppose if your table of dates for a single user is
pk_id dates
----------- -----------
27 2017-04-02
28 2017-04-03
29 2017-04-04
30 2017-04-05
31 2017-04-06
44 2017-04-09
45 2017-04-10
46 2017-04-11
47 2017-04-12
48 2017-04-13
then
SELECT ROW_NUMBER() OVER(ORDER BY dates desc) AS Row#,dates into #temp1 FROM
yourTable where userid = #userid
select top 1 a.Row# As LatestStreak from #temp1 a inner join #temp1 b on a.Row# = b.Row#-1
where a.dates <> DATEADD(DAY,1,b.dates) order by a.Row# asc
this gives you 5, I have used Inner Join so that it wont have server compatibility issue
or you can use this, you can get the dates in the last streak too if you use c.dates instead of count(*)
SELECT COUNT(*)
FROM
(SELECT MAX (a.dates) AS Latest
FROM #yourtable a
WHERE DATEADD(DAY,-1,dates)
NOT IN (SELECT dates FROM #yourtable)) AS B
JOIN #yourtable c
ON c.dates >= b.Latest
This solution is probably similar to what you want:
How do I group on continuous ranges
It has a link to the motivating explanation here:
Things SQL needs: SERIES()
The main idea is that if after you have grouped by individual id's, and ordered by dates, the difference between date and current row is an invariant within each series of consecutive dates. So you group by user and this invariant (and min date within this group). And then you can group by user and pick the count of the 2nd column and only pick the max count.

SQL Select Highest Surge of Activity in period of time

I am working on a database dealing with information regarding red-light violations from the beginning of the year until present-day.
The table I'm working with is Violations[TicketID, CameraID, DateOfViolation]
I want to find a CameraID with the most amount of tickets serviced (which amounts to a new entry in Violations) in, say, the last 7 days. If there is no activity in the last 7 days, I want null to be returned (i.e. don't open the range to be the last 14 days).
What is the command for this? I'm not sure how to even begin adding constraint for past 7 days. I am connecting to a Microsoft SQL Server.
select top 1 CameraID
from violations
where DateOfViolation >= dateadd(day, -7, getdate())
group by CameraID
order by count(*) desc

Getting Birthdays worldwide with time difference and DST

I have a user table with a birthday field and I am trying to get all the user ids where their birthday is today. The hard part is to account for time difference between countries and DST.
This is what I have so far:
SELECT *
FROM Users WITH(NOLOCK)
WHERE MONTH(Birthday) = MONTH(GETDATE())
AND DAY(Birthday) BETWEEN DAY(GETDATE()) - 2 AND DAY(GETDATE()) + 2
I am including the -/+ 2 days to make sure I have birthdays for people who might be several hours ahead of or behind our server time. I'm not sure where to go from here.
How can I make sure I get the right users?
EDIT:
I am using SQL Server 2005 and know the location of a user based on their bill country.
you may need to work it out using datetiemoffset:
http://msdn.microsoft.com/en-us/library/bb630289.aspx
The example below creates a test table in [tempdb] that has 48 hours of dates.
If my server is running on GMT time, I can convert all the dates stored in the table to GMT by using the offsetting to zero the constant.
-- Just playing around
use tempdb;
go
-- A simple table with dates
create table my_birthday
(
-- simple id
my_id int identity(1,1) primary key,
-- store date with offset
my_date datetimeoffset
);
-- Data with GMT offset
declare #cnt int = -48;
while #cnt < 49
begin
insert into my_birthday (my_date) values ( DATEADD(HH, -#cnt, SYSDATETIMEOFFSET()) );
set #cnt += 1;
end;
-- Pick todays data
select * from my_birthday
where datediff(d, my_date, switchoffset(CONVERT(datetimeoffset, '2013-12-12'), '+00:00') ) = 0
Looking at the output, we get 5 extra days on 12-11 since I am in the EST time zone and loose 5 days at the end of day. But we still only get 24 hours which is correct.
In short, you need the datetimeoffset data type and the switchoffset() function for this solution.

T-SQL Group by / Order by + SQL Server Reporting Service Parameters

select
Users.UserId
,Users.FirstName + ' ' + Users.LastName AS
,[Month]
,[Day]
,x.[Przych]
,x.[Wych]
,x.[Przych] + [Wych] as [Ogół]
from
(select
CaseActionHistory.UserId
,month(CaseActionHistory.DateAdded) AS [Month]
,day(CaseActionHistory.DateAdded) AS [Day]
,sum(case when CaseActionHistory.CaseActionDefinitionId in (14,15,16) then 1 else 0 end) AS [Przych]
,sum(case when CaseActionHistory.CaseActionDefinitionId in (20,21,22,23,26) then 1 else 0 end) AS [Wych]
from CaseActionHistory
where CaseActionHistory.CaseActionDefinitionId in (14,15,16,20,21,22,23,26)
group by month(CaseActionHistory.DateAdded),day(CaseActionHistory.DateAdded), CaseActionHistory.UserId
order by month(CaseActionHistory.DateAdded) DESC,day(CaseActionHistory.DateAdded) DESC
OFFSET 0 rows
) AS x
inner join Users on x.UserId = Users.UserId
I'm trying to run out something out of this. My problem is: the results of such query are displayed like this:
User Month Day X
User1 7 31 6
User2 7 31 7
User3 7 31 9
User1 7 30 8
User2 7 30 7
User3 7 30 8
User4 7 31 10
User5 7 31 20
User6 7 31 23
User4 7 30 5
User5 7 30 7
User6 7 30 65
So in fact few Users are grouped into small groups which are displayed first, then 2nd group, etc. so I suppose there's a problem either with group by or order by.
As an addition I'd like to ask a question concerning parameters in SQL Server. Out of code below I'd like to set up 3 parameters:
User
Month
Day
But my problem is that when I set up details of parameter and I run it some values are multiplied. Same user is multiplied few times, same month, same day etc. also report itself is not reacting on any kind of change within parameters.
The main idea of a report is to show the number of phone calls done by every employee, each day, every month and to be able to compare results with others.
Calls are splitted into: outgoing and incoming. After every phone call employee adds to system an information regarding what phone call it was and what they managed to do during this phone call.
So in fact we are working on 2 tables in this case:
CaseActionHistory and Users
So the plan was to show the number of phone calls (incoming, outgoing and sum of those) for every day for every person.
Since CaseActionHistory table consits only ID of User which done the action and I'd like to show the person's name (which is placed in Users table obviously).
The problem is that the report should show around 20 Users one by one, so for 31st of June 20 Users one by one, for 30th 20 Users one by one etc, but it shows like 5 Users for 31st, then same Users for 30th, then next 5 Users for 31st etc (link to image showing the situation below)
http://img801.imageshack.us/img801/2088/7blu.png
Columns are:
UserId (to be replaced with UserName) Day Month Incming Outgoing Sum
The rows on the bottom for 31st July should be on the top of the list but they are not.
Welcome to stack overflow. A few things:
If your data is 'multiplying' may you just do a 'distinct' in your main select statement and determine if it is not your dataset repeating?
How are you applying parameters? They should be predicates and have nothing to do with groupings except indirectly. You may limit a set by a parameter but it will not affect your grouping directly, generally speaking.
What do you want to group by? Year, Month, (detail)?
SSRS starts out with a data connection (Data Source). You then apply a Data Set which is a query or proc. Which you have a query. You can apply a predicate to the main dataset with a 'where User = #User' or 'where User in (#User)'. The parameter will automatically be created if you do this in your dataset first with the '#(something)' creating a text parameter of said name. You see the Built In Fields, Parameters, Images, Data Sources, DataSets in the 'Report Data' view which you will use constantly in SSRS creation with 'Business Intelligence Development Studio'.
So a simple example would be that you want to add a group for the Day to your '(Details)' row. If you create a 'Table' element from the 'Toolbox' it will only have a single row called Details. Fill it with your X value and user. In the 'Design' surface at the bottom you will see 'Row Groups'. Right click on your 'Details' row and choose add parent group. Group by 'Day' check 'Add Group Header'. You now have another row that will be grouping by Day. You can repeat this process on the newly created group for a group for 'Month' and so and so on.

SQL Server Retrieving Recurring Appointments By Date

I'm working on a system to store appointments and recurring appointments. My schema looks like this
Appointment
-----------
ID
Start
End
Title
RecurringType
RecurringEnd
RecurringTypes
---------------
Id
Name
I've keeped the Recurring Types simple and only support
Week Days,
Weekly,
4 Weekly,
52 Weekly
If RecurringType is null then that appointment does not recur, RecurringEnd is also nullable and if its null but RecurringType is a value then it will recur indefinatly. I'm trying to write a stored procedure to return all appointments and their dates for a given date range.
I've got the stored procedure working for non recurring meetings but am struggling to work out the best way to return the recurrences this is what I have so far
ALTER PROCEDURE GetAppointments
(
#StartDate DATETIME,
#EndDate DATETIME
)
AS
SELECT
appointment.id,
appointment.title,
appointment.recurringType,
appointment.recurringEnd,
appointment.start,
appointment.[end]
FROM
mrm_booking
WHERE
(
Start >= #StartDate AND
[End] <= #EndDate
)
I now need to add in the where clauses to also pick up the recurrences and alter what is returned in the select to return the Start and End Dates for normal meetings and the calculated start/end dates for the recurrences.
Any pointers on the best way to handle this would be great. I'm using SQL Server 2005
you need to store the recurring dates as each individual row in the schedule. that is, you need to expand the recurring dates on the initial save. Without doing this it is impossible to (or extremely difficult) to expand them on the fly when you need to see them, check for conflicts, etc. this will make all appointments work the same, since they will all actually have a row in the table to load, etc. I would suggest that when a user specifies their recurring date, you make them pick an actual number of recurring occurrences. When you go to save that recurring appointment, expand them all out as individual rows in the table. You could use a FK to a parent appointment row and link them like a linked list:
Appointment
-----------
ID
Start
End
Title
RecurringParentID FK to ID
sample data:
ID .... RecurringParentID
1 .... null
2 .... 1
3 .... 2
4 .... 3
5 .... 4
if in the middle of the recurring appointments schedule run, say ID=3, they decide to cancel them, you can follow the chain and delete the remaining ID=3,4,5.
as for expanding the dates, you could use a CTE, numbers table, while loop, etc. if you need help doing that, just ask. the key is to save them as regular rows in the table so you don't need to expand them on the fly every time you need to display or evaluate them.
I ended up doing this by creating a temp table of everyday between the start and end date along with their respective day of the week. I limited the recurrence intervals to weekdays and a set amount of weeks and added where clauses like this
--Check Week Days Reoccurrence
(
mrm_booking.repeat_type_id = 1 AND
#ValidWeeklyDayOfWeeks.dow IN (1,2,3,4,5)
) OR
--Check Weekly Reoccurrence
(
mrm_booking.repeat_type_id = 2 AND
DATEPART(WEEKDAY, mrm_booking.start_date) = #ValidWeeklyDayOfWeeks.dow
) OR
--Check 4 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 3 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*4) = 0
) OR
--Check 52 Weekly Reoccurences
(
mrm_booking.repeat_type_id = 4 AND
DATEDIFF(d,#ValidWeeklyDayOfWeeks.[Date],mrm_booking.start_date) % (7*52) = 0
)
In case your interested I built up a table of the days between the start and end date using this
INSERT INTO #ValidWeeklyDayOfWeeks
--Get Valid Reoccurence Dates For Week Day Reoccurences
SELECT
DATEADD(d, offset - 1, #StartDate) AS [Date],
DATEPART(WEEKDAY,DATEADD(d, offset - 1, #StartDate)) AS Dow
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY s1.id) AS offset
FROM syscolumns s1, syscolumns s2
) a WHERE offset <= DATEDIFF(d, #StartDate, DATEADD(d,1,#EndDate))
Its not very elegant and probably very specific to my needs but it does the job I needed it to do.

Resources