SQL get the active users based on the join and leave date - sql-server

I have the following table:
Person
userID
Name
date_created
date_left
My rows:
-- Datetime format MM-DD-YYYY
userID Name date_created date_left
1 aa 01-01-2018 NULL
2 bb 01-01-2018 11-15-2018
3 cc 06-20-2018 NULL
4 dd 03-03-2018 11-13-2018
5 ee 11-08-2018 11-30-2018
6 ff 12-01-2018 NULL
I also have a invoice period date (month and year).
What I'm trying to do is to get a list of active users based on the invoice period!.
MY IDEAL RESULT:
If invoice month = 11 and year = 2018:
users 1,3,5 should be shown
Reason user 5 is selected:
If a user join and leaves in the same month, he is seen as active but if a user leaves on the month and has a join date smaller than the invoice month, it's not active (since it left on the month of invoicing but joined earlier)
if invoice month = 12 and year = 2018:
users 1,3,6 should be shown
Therefor:
I also want to retrieve the results for the users that are already left (and check if they were active based on the passed month and year). This means that I also want to be able to dynamically check the users that were active
This is my query:
GO
DECLARE #month int, #year int;
SET #month = 12;
SET #year = 2018;
SELECT * FROM PERSON
WHERE (month(date_created) = #month -1 ) OR (MONTH(date_created) < #month AND YEAR(date_created) = #year AND date_left = null)
but this still returns everyone.
What am I doing wrong?

Something like this should work:
DECLARE #month int, #year int, #dateToCheck datetime;
SET #month = 12;
SET #year = 2018;
SET #dateToCheck = dateadd(month, 1, datefromparts(#year, #month, 1))
SELECT *
FROM PERSON
WHERE date_created < #dateToCheck
AND (date_left is null or date_left >= #dateToCheck)
So if your month is 11 and year 2018 #datetocheck will be 2018-12-01 and you will get anyone created on or before 2018-11-30 and have a null date_left or a date left on or after 2018-12-01.
If your month is 12 and year 2018, #datetocheck will be 2018-12-31 and you will get anyone created on or before 2018-12-31 and that have a date_left = null or a date left on or after 2019-01-01

Assuming that you want to compare with the current date. Then try like this
SELECT * FROM Person
WHERE DATEDIFF(m,date_created,GETDATE())=1 AND date_left IS NULL
The active users are considered as the one who is date_created is a month before

Related

T-SQL results per day for 1 year between time range

I need to return count on results per day between a time range that will overlap days.
So from 8 PM to 8 AM for every day that starts on M-F return a count for that time period. And do that for the entire year.
This is what I have, and I could do a simple and between if I only wanted on day but I'm not sure how to iterate through days especially when the start is on one day and the end on the next but skip the ones that Start on a Saturday or Sunday.
SELECT TOP (50)
ClientVisit.visittype,
ClientVisit.location_id,
ClientVisit.visittype_id,
Location.location_desc,
Location.location_code,
ClientVisit.timein,
ClientVisit.timeout,
ClientVisit.visit_dateday
FROM
ClientVisit
INNER JOIN
Location ON ClientVisit.location_id = Location.location_id
WHERE
(ClientVisit.visittype Like '%Open Chart%'
OR ClientVisit.visittype LIKE '%Diag%')
AND (Location.location_code = 'Access-505'
OR Location.location_code = 'Access-hosp')
AND (ClientVisit.timein BETWEEN #param1 AND #param2)
Filtering days of week and hours of the day is easy enough. Does this group by get at what you're trying to accomplish for the counts?
SELECT CAST(ClientVisit.timein AS DATE) AS DT, COUNT(*)
FROM ClientVisit INNER JOIN Location
ON ClientVisit.location_id = Location.location_id
WHERE
(ClientVisit.visittype Like '%Open Chart%' OR ClientVisit.visittype LIKE '%Diag%')
AND (Location.location_code = 'Access-505' OR Location.location_code = 'Access-hosp')
-- Use date params rather than datetime
AND CAST(ClientVisit.timein AS DATE) BETWEEN #param1 AND #param2
-- M-F assuming ##DATEFIRST is Sunday (7)
AND DATEPART(weekday, ClientVisit.timein) BETWEEN 2 AND 6
-- time of day. won't include the instant of 8:00:00am
AND ( DATEPART(hour, ClientVisit.timein) BETWEEN 8 AND 23
OR DATEPART(hour, ClientVisit.timein) BETWEEN 0 AND 7)
GROUP BY CAST(ClientVisit.timein AS DATE);
If you need to treat the hours from 8PM to 8AM as a single shift then you can adjust the times prior to so that times after midnight are treated as part of the preceeding day:
WITH AdjustedVisit AS (
SELECT *, DATEADD(hour, -8, timein) AS adjustedin FROM ClientVisit)
-- Use date params rather than datetime
WHERE CAST(timein AS DATE) BETWEEN #param1 AND #param2
)
SELECT CAST(v.adjustedin AS DATE) AS DT, COUNT(*)
FROM AdjustedVisit AS v INNER JOIN Location AS l
ON v.location_id = l.location_id
WHERE
(v.visittype Like '%Open Chart%' OR v.visittype LIKE '%Diag%')
AND (l.location_code = 'Access-505' OR l.location_code = 'Access-hosp')
-- M-F assuming ##DATEFIRST is Sunday (7)
AND DATEPART(weekday, v.adjustedin) BETWEEN 2 AND 6
-- time of day. won't include the instant of 8:00:00am
AND DATEPART(hour, v.adjustedin) BETWEEN 12 AND 23
GROUP BY CAST(v.adjustedin AS DATE);

Creating a date from Week of month and Day of week in SQL server

I have to get/create date from the user input of week of month (week number in that month - 1st,2nd,3rd,4th and last) and day of week (sunday,monday..) in SQL server.
Examples:
4th Sunday of every month, Last Friday of every month, First Monday etc.
I was able to do it easily in .net but SQL server does seem limited in the date functions.
I am having to use lot of logic to get the date. To calculate the date using the above two parameters I had to use lot of datepart function.
Any suggestions on how to come up with the optimal SQL query for such a function?
I created a function other day for another OP GET Month, Quarter based on Work Week number
This function takes the current year as default it can be further modified to take Year as a parameter too.
an extension to that function can produce the results you are looking for ....
WITH X AS
(
SELECT TOP (CASE WHEN YEAR(GETDATE()) % 4 = 0 THEN 366 ELSE 365 END)-- to handle leap year
DATEADD(DAY
,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1
, CAST(YEAR(GETDATE()) AS VARCHAR(4)) + '0101' )
DayNumber
From master..spt_values
),DatesData AS(
SELECT DayNumber [Date]
,DATEPART(WEEKDAY,DayNumber) DayOfTheWeek
,DATEDIFF(WEEK,
DATEADD(WEEK,
DATEDIFF(WEEK, 0, DATEADD(MONTH,
DATEDIFF(MONTH, 0, DayNumber), 0)), 0)
, DayNumber- 1) + 1 WeekOfTheMonth
FROM X )
SELECT * FROM DatesData
WHERE DayOfTheWeek = 6 -- A function would expect these two parameters
AND WeekOfTheMonth = 4 -- #DayOfTheWeek and #WeekOfTheMonth
Here is a general formula:
declare #month as datetime --set to the first day of the month you wish to use
declare #week as int --1st, 2nd, 3rd...
declare #day as int --Day of the week (1=sunday, 2=monday...)
--Second monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select dateadd(
day,
((7+#day) - datepart(weekday, #month)) % 7 + 7 * (#week-1),
#month
)
You can also find the last, 2nd to last... etc with this reverse formula:
--Second to last monday in August 2015
set #month = '8/1/2015'
set #week = 2
set #day = 2
select
dateadd(
day,
-((7+datepart(weekday, dateadd(month,1,#month)-1)-#day)) % 7 - 7 * (#week-1),
dateadd(month,1,#month)-1
)

Stored Procedure to check if four weeks with less then two records

I am working on a stored procedure. The input is going to be a date and ID and the procedure is going to set a value to true if there are 4 weeks with less then 2 inputs per week.
It has to take in consideration that I might pass an early date with no records in the database.
I couldn't format the code. I don't know why?
So far I got that with previous help from you guys:
CREATE proc [dbo].[sp_test] (#id int, #d date)
as
declare #WeekFirstRecord as int
declare #WeeksWithNoRecords as int
SET #WeekFirstRecord = datepart(week,(select Min(ZeroPointIncidentDate) from EmployeeZeroPointIncidents where ZeroPointIncidentDate > #d))
SET #WeeksWithNoRecords = datepart(week, #WeekFirstRecord) - datepart(week, #d)
select case when sum(c) + #WeeksWithNoRecords >= 4 then 'true' else 'false' end status
from (
select c = count(*) over (partition by EmpId, datepart(week, ZeroPointIncidentDate))
from EmployeeZeroPointIncidents
where EmpId = #id and ZeroPointIncidentDate >= #d
) a
where c = 1
In my data only the weeks with the stars have less than two inputs and if I pass the date 7-7-2015 is going to set the output value to true
Any help will be appreciated. Do I need to iterate through every record and set a counter if less then two inputs or there is an easier way ?
ID Date
1 7-7-2015
2 6-23-2015
3 6-12-2015
1 7-8-2015
1 7-14-2015 *
1 7-21-2015 *
1 7-27-2015
1 7-28-2015
1 7-29-2015
1 7-30-2015
1 8-3-2015 *
1 8-11-2015 *
If I had week Jul 13 - no records week Jul 20 - no records week Jul 27 - 2 records Week Aug 3 - no records Week Aug 10 - 2 records Week Aug 17 - no records And pass Jul 12 date should return true, if I pass jul 15 should return false
I had to see your sample data set from your last question along with explanation from your last question as well as the explanation given in this question to come up with this solution.
When you ask a question here put yourself in the reader's shoes and see if the question makes any sense, anyway I hope this solution will get you what you want. cheers
CREATE PROCEDURE get_output
#Date DATE
,#ID INT
,#Output INT OUTPUT -- 1 true , 0 false
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Count INT;
SELECT #Count = COUNT(*)
FROM TableName
WHERE CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, #Date), 0) AS DATE)
= CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
AND ID = #ID
GROUP BY CAST(DATEADD(WEEK, DATEDIFF(WEEK, 0, [Date]), 0) AS DATE)
IF (#Count < 2)
SET #Output = 1;
ELSE
SET #Output = 0;
END

GROUP BY DAY, CUMULATIVE SUM

I have a table in MSSQL with the following structure:
PersonId
StartDate
EndDate
I need to be able to show the number of distinct people in the table within a date range or at a given date.
As an example i need to show on a daily basis the totals per day, e.g. if we have 2 entries on the 1st June, 3 on the 2nd June and 1 on the 3rd June the system should show the following result:
1st June: 2
2nd June: 5
3rd June: 6
If however e.g. on of the entries on the 2nd June also has an end date that is 2nd June then the 3rd June result would show just 5.
Would someone be able to assist with this.
Thanks
UPDATE
This is what i have so far which seems to work. Is there a better solution though as my solution only gets me employed figures. I also need unemployed on another column - unemployed would mean either no entry in the table or date not between and no other entry as employed.
CREATE TABLE #Temp(CountTotal int NOT NULL, CountDate datetime NOT NULL);
DECLARE #StartDT DATETIME
SET #StartDT = '2015-01-01 00:00:00'
WHILE #StartDT < '2015-08-31 00:00:00'
BEGIN
INSERT INTO #Temp(CountTotal, CountDate)
SELECT COUNT(DISTINCT PERSON.Id) AS CountTotal, #StartDT AS CountDate FROM PERSON
INNER JOIN DATA_INPUT_CHANGE_LOG ON PERSON.DataInputTypeId = DATA_INPUT_CHANGE_LOG.DataInputTypeId AND PERSON.Id = DATA_INPUT_CHANGE_LOG.DataItemId
LEFT OUTER JOIN PERSON_EMPLOYMENT ON PERSON.Id = PERSON_EMPLOYMENT.PersonId
WHERE PERSON.Id > 0 AND DATA_INPUT_CHANGE_LOG.Hidden = '0' AND DATA_INPUT_CHANGE_LOG.Approved = '1'
AND ((PERSON_EMPLOYMENT.StartDate <= DATEADD(MONTH,1,#StartDT) AND PERSON_EMPLOYMENT.EndDate IS NULL)
OR (#StartDT BETWEEN PERSON_EMPLOYMENT.StartDate AND PERSON_EMPLOYMENT.EndDate) AND PERSON_EMPLOYMENT.EndDate IS NOT NULL)
SET #StartDT = DATEADD(MONTH,1,#StartDT)
END
select * from #Temp
drop TABLE #Temp
You can use the following query. The cte part is to generate a set of serial dates between the start date and end date.
DECLARE #ViewStartDate DATETIME
DECLARE #ViewEndDate DATETIME
SET #ViewStartDate = '2015-01-01 00:00:00.000';
SET #ViewEndDate = '2015-02-25 00:00:00.000';
;WITH Dates([Date])
AS
(
SELECT #ViewStartDate
UNION ALL
SELECT DATEADD(DAY, 1,Date)
FROM Dates
WHERE DATEADD(DAY, 1,Date) <= #ViewEndDate
)
SELECT [Date], COUNT(*)
FROM Dates
LEFT JOIN PersonData ON Dates.Date >= PersonData.StartDate
AND Dates.Date <= PersonData.EndDate
GROUP By [Date]
Replace the PersonData with your table name
If startdate and enddate columns can be null, then you need to add
addditional conditions to the join
It assumes one person has only one record in the same date range
You could do this by creating data where every start date is a +1 event and end date is -1 and then calculate a running total on top of that.
For example if your data is something like this
PersonId StartDate EndDate
1 20150101 20150201
2 20150102 20150115
3 20150101
You first create a data set that looks like this:
EventDate ChangeValue
20150101 +2
20150102 +1
20150115 -1
20150201 -1
And if you use running total, you'll get this:
EventDate Total
2015-01-01 2
2015-01-02 3
2015-01-15 2
2015-02-01 1
You can get it with something like this:
select
p.eventdate,
sum(p.changevalue) over (order by p.eventdate asc) as total
from
(
select startdate as eventdate, sum(1) as changevalue from personnel group by startdate
union all
select enddate, sum(-1) from personnel where enddate is not null group by enddate
) p
order by p.eventdate asc
Having window function with sum() requires SQL Server 2012. If you're using older version, you can check other options for running totals.
My example in SQL Fiddle
If you have dates that don't have any events and you need to show those too, then the best option is probably to create a separate table of dates for the whole range you'll ever need, for example 1.1.2000 - 31.12.2099.
-- Edit --
To get count for a specific day, it's possible use the same logic, but just sum everything up to that day:
declare #eventdate date
set #eventdate = '20150117'
select
sum(p.changevalue)
from
(
select startdate as eventdate, 1 as changevalue from personnel
where startdate <= #eventdate
union all
select enddate, -1 from personnel
where enddate < #eventdate
) p
Hopefully this is ok, can't test since SQL Fiddle seems to be unavailable.

How do I take a day of the year and 'bucket it' into weeks of the year in Microsoft SQL? Used in manufacturing scenarios for material requirements

I have a need to create a gross requirements report that takes how much supply and demand of a item in inventory from a start date onwards and 'buckets' it into different weeks of the year so that material planners know when they will need a item and if they have enough stock in inventory at that time.
As an example, today’s date (report date) is 8/27/08. The first step is to find the date for the Monday of the week the report date falls in. In this case, Monday would be 8/25/08. This becomes the first day of the first bucket. All transactions that fall before that are assigned to week #0 and will be summarized as the beginning balance for the report. The remaining buckets are calculated from that point. For the eighth bucket, there is no ending date so any transactions after that 8th bucket start date are considered week #8.
WEEK# START DATE END DATE
0.......None..........8/24/08
1.......8/25/08.......8/31/08
2.......9/1/08.........9/7/08
3.......9/8/08.........9/14/08
4.......9/15/08.......9/21/08
5.......9/22/08.......9/28/08
6.......9/29/08.......10/5/08
7.......10/06/08.....10/12/08
8.......10/13/08......None
How do I get the week #, start date, end date for a given date?
I've always found it easiest and most efficient (for SQL Server) to construct a table with one row for every week into the future through your domain horizon; and join to that (with a "WHERE GETDATE() >= MONDATE AND NOT EXISTS (SELECT 1 FROM table WHERE MONDATE < GETDATE())".
Anything you try to do with UDF's will be much less efficient and I find more difficult to use.
You can get Monday for any given date in a week as:
DATEADD(d, 1 - DATEPART(dw, #date), #date)
and you can write a stored procedure with the following body
-- find Monday at that week
DECLARE #currentDate SMALLDATETIME
SELECT #currentDate = DATEADD(d, 1 - DATEPART(dw, #date), #date)
-- create a table and insert the first record
DECLARE #weekTable TABLE (Id INT, StartDate SMALLDATETIME, EndDate SMALLDATETIME)
INSERT INTO #weekTable VALUES (0, NULL, #currentDate)
-- increment the date
SELECT #currentDate = DATEADD(d, 1, #currentDate)
-- iterate for 7 more weeks
DECLARE #id INT
SET #id = 1
WHILE #id < 8
BEGIN
INSERT INTO #weekTable VALUES (#id, #currentDate, DATEADD(d, 6, #currentDate))
SELECT #currentDate = DATEADD(ww, 1, #currentDate)
SET #id = #id + 1
END
-- add the last record
INSERT INTO #weekTable VALUES (8, #currentDate, NULL)
-- select the values
SELECT Id 'Week #', StartDate 'Start Date', EndDate 'End Date'
FROM #weekTable
When I pass
#date = '20080827'
to this procedure, I get the following
Week # Start Date End Date
0 NULL 2008-08-24 00:00:00
1 2008-08-25 00:00:00 2008-08-31 00:00:00
2 2008-09-01 00:00:00 2008-09-07 00:00:00
3 2008-09-08 00:00:00 2008-09-14 00:00:00
4 2008-09-15 00:00:00 2008-09-21 00:00:00
5 2008-09-22 00:00:00 2008-09-28 00:00:00
6 2008-09-29 00:00:00 2008-10-05 00:00:00
7 2008-10-06 00:00:00 2008-10-12 00:00:00
8 2008-10-13 00:00:00 NULL
--SQL sets the first day of the week as sunday and for our purposes we want it to be Monday.
--This command does that.
SET DATEFIRST 1
DECLARE
#ReportDate DATETIME,
#Weekday INTEGER,
#NumDaysToMonday INTEGER,
#MondayStartPoint DATETIME,
#MondayStartPointWeek INTEGER,
#DateToProcess DATETIME,
#DateToProcessWeek INTEGER,
#Bucket VARCHAR(50),
#DaysDifference INTEGER,
#BucketNumber INTEGER,
#NumDaysToMondayOfDateToProcess INTEGER,
#WeekdayOfDateToProcess INTEGER,
#MondayOfDateToProcess DATETIME,
#SundayOfDateToProcess DATETIME
SET #ReportDate = '2009-01-01'
print #ReportDate
SET #DateToProcess = '2009-01-26'
--print #DateToProcess
SET #Weekday = (select DATEPART ( dw , #ReportDate ))
--print #Weekday
--print DATENAME(dw, #ReportDate)
SET #NumDaysToMonday =
(SELECT
CASE
WHEN #Weekday = 1 THEN 0
WHEN #Weekday = 2 THEN 1
WHEN #Weekday = 3 THEN 2
WHEN #Weekday = 4 THEN 3
WHEN #Weekday = 5 THEN 4
WHEN #Weekday = 6 THEN 5
WHEN #Weekday = 7 THEN 6
END)
--print #NumDaysToMonday
SET #MondayStartPoint = (SELECT DATEADD (d , -1*#NumDaysToMonday, #ReportDate))
--print #MondayStartPoint
SET #DaysDifference = DATEDIFF ( dd , #MondayStartPoint , #DateToProcess )
--PRINT #DaysDifference
SET #BucketNumber = #DaysDifference/7
--print #BucketNumber
----Calculate the start and end dates of this bucket------
PRINT 'Start Of New Calc'
print #DateToProcess
SET #WeekdayOfDateToProcess = (select DATEPART ( dw , #DateToProcess ))
print #WeekdayOfDateToProcess
SET #NumDaysToMondayOfDateToProcess=
(SELECT
CASE
WHEN #WeekdayOfDateToProcess = 1 THEN 0
WHEN #WeekdayOfDateToProcess = 2 THEN 1
WHEN #WeekdayOfDateToProcess = 3 THEN 2
WHEN #WeekdayOfDateToProcess = 4 THEN 3
WHEN #WeekdayOfDateToProcess = 5 THEN 4
WHEN #WeekdayOfDateToProcess = 6 THEN 5
WHEN #WeekdayOfDateToProcess = 7 THEN 6
END)
print #NumDaysToMondayOfDateToProcess
SET #MondayOfDateToProcess = (SELECT DATEADD (d , -1*#NumDaysToMondayOfDateToProcess, #DateToProcess))
print #MondayOfDateToProcess ---This is the start week
SET #SundayOfDateToProcess = (SELECT DATEADD (d , 6, #MondayOfDateToProcess))
PRINT #SundayOfDateToProcess
The problem I see with the one bucket at a time approach is that its hard to make it scale,
If you join into a user defined function you will get better performance, you could use this a a starting point
Why not use a combination of DATEPART(year, date-column) and DATEPART(week, date-column) and group by these values. This works if the week in DATEPART is aligned on Mondays as ISO 8601 requires. In outline:
SELECT DATEPART(year, date_column) AS yyyy,
DATEPART(week, date_column) AS ww,
...other material as required...
FROM SomeTableOrOther
WHERE ...appropriate filters...
GROUP BY yyyy, ww -- ...and other columns as necessary...

Resources