I have employees with contracts in the database. I want to know if an employee is a new employee at a location. I have the following datastructure:
EmployeeId Index BeginDate Enddate HoursToWork LocationId
12133 1 2013-01-01 2014-01-01 10 1
12133 2 2013-06-01 2014-01-01 20 1
12133 3 2012-01-01 2014-01-01 5 1
As you can see, an employee can have more than 1 contract on a location. Then Endate can be null.
Per location and per Month or Quarter I want to see how many employees are started. I want to use #Startdate and #Enddate parameters for the period I want have the data.
There are to many cases I should take into account. Like, the Index field is not Always increased together with the Begindate, like you can see at Index = 3.
Example:
I want to know how many employees are started in january 2013.
In this case nothing because the first contract was started on 2012-01-01. There are two new contracts but this employee is not new for the location. But if index 3 was not existing then whis should be a new employee.
It can be that a employee have two contract that starts on the same date and if he doesnt have before a contract then it is 1 new employee.
I already tried the following, which works when an employee just has 1 contract. But if there are more than 1 contracts then it is hard to decide if the employee is new:
declare #Startdate datetime set #Startdate = '2013-01-01'
declare #Enddate datetime set #Enddate = '2013-12-31'
select EmployeeId, Index, BeginDate, Enddate, HoursToWork, LocationId
,(case
when BeginDate between #Startdate and #Enddate then 1
end) as NewEmployee
,(case
when Enddate between #Startdate and #Enddate then 1
end) as LeavingEmployee
from Contracts
Given the 3 records, this employee is not an new employee. I would like to have a output like:
LocationId NewEmployee
1 0
When I just have the first 2 records and I want know new employees in Janury 2013 then I expect:
LocationId NewEmployee
1 1
How about something like this for starting employee?
SELECT EmployeeID, LocationID, Min(StartDate)
FROM Contracts
GROUP BY EmployeeID, LocationID
HAVING Min(StartDate) between #Startdate and #Enddate
I would suggest something similar for Leavingemployee, but I would not spend much effort trying to get those into one query. It seems they are functionally different.
edit: es, it should be Min, not max. As for what is needed, i read "I want to see how many employees are started", and i didn't see much reason to complicate matters.
If additional data is needed, it's always possible to move this to a subquery and select * where ... in subquery, but if it's not needed...
In order to only deal with active contract (contracts that are active during the time interval for the query) we can set the following rules:
A contract is active somewhere during the period we are looking at if its startdate < the parameter enddate and its enddate > the parameter startdate.
Adding that to our query, we get
SELECT EmployeeID, LocationID, Min(StartDate)
FROM Contracts
WHERE Startdate <= #Enddate
AND Enddate >= #Startdate
GROUP BY EmployeeID, LocationID
HAVING Min(StartDate) between #Startdate and #Enddate
Related
I run into a question during working and I would really appreciate if anyone could give me some ideas.
We have a table which keeps tracking of tasks employee has finished. Table structure as below :
EmployeeNum | TaskID |Start Date of task | End Date of task
I want to calculate how many days each employee has invested in each task using this table. At first my code looks like this:
Select
EmployeeNum,TaskID,DateDiff(day,StartDate,EndDate)+1 as PureDay
from
TaskTable
Group by
EmployeeNum,TaskID
But then I found a problem that there are overlaps in the date range for each task.
For example, we have TaskA, TaskB, TaskC for one employee.
TaskA is from 2018-10-01 to 2018-10-05
TaskB from 2018-10-02 to 2018-10-07
TaskC from 2018-10-09 to 2018-10-10
In this way, the actual working days of this employee should be from 2018-10-01 to 2018-10-07, and then 2018-10-09 to 2018-10-10 which is 9 days. If I calculate date range of each task then add them together then actual working days become 5+6+2=13 days instead of 9.
I'm wandering if there could be any good ways to solve this overlapping problem ? Thank you very much for any ideas!
Following query will count how many working days each employee spent on each task ;
SELECT
EmployeeNum,
TaskID,
(DATEDIFF(dd, StartDate, EndDate) + 1)
-(DATEDIFF(wk, StartDate, EndDate) * 2)
-(CASE WHEN DATENAME(dw, StartDate) = 'Sunday' THEN 1 ELSE 0 END)
-(CASE WHEN DATENAME(dw, EndDate) = 'Saturday' THEN 1 ELSE 0 END) as PureDay
FROM
TaskTable
GROUP BY
EmployeeNum,
TaskID
See this link for on explanation on how this computation works.
Once you know the date when a task starts, you can use a cumulative sum to assign a group to each record and then simply aggregate by that group (and other information).
The following query should do what you want:
with starts as (
select sm.*,
(case when exists (select 1
from tb_TaskMaster sm2
where sm2.EmpID = sm.EmpID and
sm2.StartDate < sm.StartDate and
sm2.EndDate >= sm.StartDate
)
then 0 else 1
end) as isstart
from tb_TaskMaster sm
)
select EmpID, count(TaskId) as cnt_TaskID, min(StartDate) as StartDate, max(EndDate) as EndDate,
datediff(Day, min(StartDate), max(EndDate)) + 1 as PureDay
from (select s.*, sum(isstart) over (partition by EmpID order by StartDate) as grp
from starts s
) s
group by EmpID, grp
order by EmpID
In this db<>fiddle, you could find the DDL & DML for my example data and the working of the code.
You can try this.
Im not sure it will work all the way but you can give it a try :)
declare #table table (empid int,taskid nvarchar(50),startdate date, enddate date)
insert into #table
values
(1,'TaskA','2018-10-01','2018-10-05'),
(1,'TaskB','2018-10-02','2018-10-07'),
(1,'TaskC','2018-10-09','2018-10-10')
select *,case when comparedate > startdate then datediff(dd,comparedate,enddate) else datediff(dd,startdate,enddate)+1 end as countofworkingdays from (
Select empid,taskid,startdate,enddate,lag(enddate,1,'1900-01-01') over(partition by empid order by startdate) as CompareDate from #table
)x
Result
This eliminates overlapping ranges by adjusting the start date based on all previous end dates:
with maxEndDates as
( -- find the maximum previous end date
Select empid,taskid,startdate,enddate,
max(EndDate)
over (partition by EmpID
order by StartDate, EndDate desc
rows between unbounded preceding and 1 preceding) as maxEndDate
from TaskTable
),
daysPerTask as
( -- calculate the difference based on the adjusted start date to eliminate overlaping days
select *,
case when maxEndDate >= enddate then 0 -- range already fully covered
when maxEndDate > startdate then datediff(dd, maxEndDate, enddate) -- range partially overlapping
else datediff(dd, startdate, enddate)+1 -- new range
end as dayCount
from maxEndDates
)
-- get the final count
select EmpID, sum(dayCount)
from daysPerTask
group by EmpID;
See db<>fiddle
Thank you all very much for your responding and help. I found a solution during searching in Stackoverflow, the following is it's link:
T-SQL date range in a table split and add the individual date to the table
The Tally table suggested by Felix in the above question is a great way to solve my problem since I have millions of records and the real situation is really complicated.
Thank you all again for your help!
I seem to be asking a lot of questions on here recently, thanks in advance for any help!
First - I have a calendar table which may make this easier. Calendar table is pretty standard, a dateID, Date and varying different formats of that date.
I want to give the query a date, and for it to return a list of all employees and whether or not they were on holiday on that date.
LeaveTypeID is 1 for holiday 2 for sickness, 3 for compassionate, etc, etc, I'm only concerned with holiday at this point.
StartDate and EndDate are as they appear
StartMidday is whether or not the holiday starts at midday on the first day (so working the morning and starting the holiday at lunch), same for endmidday but for the end of the holiday period)
Would ideally like it to say whether that day is a half day as well if possible?
So I want my result to be something like (assuming I chose the date of the 4th Nov, 2016)
EmployeeID OnHoliday IsHalf
1 TRUE TRUE
2 FALSE FALSE
3 FALSE FALSE
4 FALSE FALSE
5 FALSE FALSE
Hopefully that's enough info. Thanks again in advance!
i think you can use between for this purpose:
declare #InputDate date='2016-09-16' --yur input date
select EmployeeId,
case when (select count(1) from EmployeeLeave
where (#InputDate between StartDate and EndDate)
and EmployeeLeave .EmployeeId=Employee .EmployeeId)>1 then 'True' else 'False' end as [OnHoliday],
case when ((select StartMidDay from EmployeeLeave
where (#InputDate between StartDate and EndDate)
and EmployeeLeave .EmployeeId=Employee .EmployeeId))=0.5 then 'True' else 'False' end as [IsHalf] ,
from Employee
I think I may have come up with a solution by myself
declare #DateAdd int=0 --add Days From Monday
declare #LeaveType int=1 -- Leave Type
declare #SelctedDate date = DATEADD(DD, #DateAdd, CAST('2016-11-21' AS DATE)) --Week Commencing
select
(CASE
WHEN (leaveTemp.StartMidday = '0.5' AND leaveTemp.StartDate = #SelctedDate) THEN '4:00'
WHEN (leaveTemp.EndMidday = '0.5' AND leaveTemp.EndDate = #SelctedDate) THEN '4:00'
WHEN LeaveID is not null THEN '8:00'
ELSE NULL
END ) as HolMon
from Employee left join
(select employeeID, LeaveID, StartDate, EndDate, StartMidday, EndMidday
from EmployeeLeave
where LeaveTypeID=#LeaveType and #SelctedDate between startdate and enddate
) as leaveTemp
on Employee.EmployeeID = leaveTemp.EmployeeID
ORDER BY Employee.EmployeeForename, Employee.EmployeeID
This seems to bring back what I was after but is the best way to do it?
How do I get list of active employees who were active in a specific date range (1-JAN-2014 TO 31-MAR-2014)
My table is like
Table:
employeeheader
empid
firstname
lastname
emphistory
empid
begindate
enddate
If enddate is null that means employee is still active.
SELECT * FROM employeeheader
JOIN emphistory ON employeeheader.empid = emphistory.empid
WHERE begindate <= <range_start> AND (enddate is null OR enddate >= <range_end>)
This gives all the employees which worked completely during a specific range.
Something like this might work - I didn't actually test it.
where begindate < the day after your end date
and nvl(enddate, sysdate) >= your start date
Since oracle dates include the time of day, you want to account for it, just in case.
This query can help
SELECT * FROM employeeheader
WHERE empid IN
(SELECT empid FROM emphistory
WHERE (enddate IS NULL OR enddate>= EndDateParameter)
AND begindate<=BeginDateParameter)
ETL Question here.
For a given table that includes entries with a start and end date, what is the optimal method to retrieve counts for each day, including those days that may not have an entry within the scope of the start end date.
Given Table Example
Stock
ID StartDate EndDate Category
1 1/1/2013 1/5/2013 Appliances
2 1/1/2013 1/10/2013 Appliances
3 1/2/2013 1/10/2013 Appliances
Output required
Available
Category EventDate Count
Appliances 1/1/2013 2
Appliances 1/2/2013 3
...
...
Appliances 1/10/2013 2
Appliances 1/11/2013 0
...
...
One method I know of, which takes FOREVER, is to create a Table variable, and run a While Block iterating through the start and end of the range I wish to retrieve, then execute a query like so..
Insert into #TempTable (Category,EventDate,Count)
FROM Stock
Where #CurrentLoopDate BETWEEN StartDate AND EndDate
Another method would be to create a table or temp table of dates in the range I want populated, and join it with a BETWEEN function.
Insert into #TempTable (Category,EventDate,Count)
FROM DateTable
INNER JOIN Stock ON DateTable.[Date] BETWEEN StartDate AND EndDate
Yet other methods are similar but use SSIS, but essentially are the same as the above two solutions.
Any GURU's know of a more efficient method?
Have you tried using a recursive CTE?
WITH Dates_CTE AS (
SELECT [ID]
,[StartDate]
,[EndDate]
,[Category]
FROM [dbo].[Stock]
UNION ALL
SELECT [ID]
,DATEADD(D, 1, [StartDate])
,[EndDate]
,[Category]
FROM Dates_cte d
WHERE DATEADD(D, 1, [StartDate]) <= EndDate
)
SELECT StartDate AS EventDate
,Category
,COUNT(*)
FROM Dates_CTE
GROUP BY StartDate, Category
OPTION (MAXRECURSION 0)
That should do the trick ;-)
I'm having trouble writing a query that will tell me if a given car is booked for a specified date period. My table is called Bookings and has CarID, StartDate and EndDate columns. I want to return a row if the car is booked for the period that the user enters. At the moment I have this query, which I got from the internet and some tinkering:
SELECT *
FROM Bookings
WHERE BookingID NOT IN
(SELECT BookingID
FROM Bookings
WHERE
(StartDate <= user_start_date AND EndDate >= user_start_date) OR
(StartDate <= user_end_date AND EndDate >= user_end_date) OR
(StartDate >= user_start_date AND EndDate <= user_end_date)) AND
(CarID = 7)
Here, I'm using user_start_date as my start date from the user and user_end_date as the end date from the user, and 7 for the car. However, the logic doesn't seem to work. Even when I verify there's absolutely no clashes in the dates it always returns rows.
How can I ammend this query so it works?
Thanks
Assuming that none of the dates can be null, and the start dates are less than the end dates, the following query should list any bookings for car 7 that overlap the given date range:
SELECT *
FROM Bookings
WHERE CarID = 7
AND StartDate < user_end_date
AND EndDate > user_start_date