Dividing monthly rate between Start and End Dates - sql

I have Monthly rate for each employee in one table which I need to distribute equally based on the Employee Start and End date in the particular project.
For e.g. The monthly rate for employee X is $4300 which is stored in Table A. The employee start & end date in the project is '2017-11-03' & '2017-12-12' respectively and is stored in Table B. I need the employee cost for both Nov and Dec months.
Appreciate any help on this.
Edits:
Table 1:Employee Details
Col1-Employee name
Col2-Start date
Col3- End date
Table 2: Rate Card
Col1-Location
Col2-Grade
Col3- Cost Per Month
Table 3: Employee Billing
Col1-Year
Col2-Month
Col3- Employee
Col4- Cost per month
So if Employee worked from 4-Nov-17 to 31-Dec-17 and Cost per month is $4000, then in Billing table there should be 2 entries like:
2017,Nov,Emp1,(4000/30)*((30-4)+1)
2017,Dec,Emp1,4000
I was ale to insert 2 entries in table 3 based on input from table 1 but not able to calculate the customized cost
case
when (webResource_Details_ESS_recurrent_entries.recurrent_month<>DATEPART(mm, l.start_date) or
webResource_Details_ESS_recurrent_entries.recurrent_month<>DATEPART(mm, l.end_date))
then
l.Employee_Cost
when DATEPART(day, l.start_date) <> 1 and Eomonth(l.start_date) = Eomonth(l.end_date) then
(Datediff(day, l.start_date, l.end_date) *
(l.Employee_Cost / Datediff(day,Eomonth(l.start_date), Dateadd(month,1, Eomonth(l.start_date))) ) )
when DATEPART(day, l.start_date) <> 1 and Eomonth(l.start_date) <> Eomonth(l.end_date) then ( Datediff(day, l.start_date, Eomonth(l.start_date)) *
( l.Employee_Cost /Datediff(day, Eomonth(l.start_date), Dateadd(month, 1,Eomonth(l.start_date))) ) )
when DATEPART(day, l.end_date) <> (DATEPART(day, DATEADD(second,-1,DATEADD(month, DATEDIFF(month,0,l.end_date)+1,0)))) and Eomonth(l.start_date) <> Eomonth(l.end_date) then (Datediff(day, ( Dateadd(month, Datediff(month, 0,l.end_date),0) ),l.end_date)
*(l.Employee_Cost /Datediff(day, Eomonth(l.end_date),Dateadd(month, 1, Eomonth(l.end_date)))))
end as Employee_Cost

SELECT em.employeed,
CASE
WHEN Eomonth(startdate) = Eomonth(enddate)
THEN (Datediff(day, startdate, enddate) *
(rate / Datediff(day,Eomonth(startdate), Dateadd(month,1, Eomonth(startdate))) ) )
ELSE ( ( Datediff(day, startdate, Eomonth(startdate)) *
( rate /Datediff(day, Eomonth(startdate), Dateadd(month, 1,Eomonth(startdate))) ) ) +
(Datediff(day, ( Dateadd(month, Datediff(month, 0,enddate),0) ),enddate)
*(rate /Datediff(day, Eomonth(enddate),Dateadd(month, 1, Eomonth(enddate))))))
END AS wage
FROM employeemaster em
INNER JOIN employeerate AS er
ON ( em.employeed = er.employeed )
First Case will handle case when both start date and end date are in same month
eomonth(startdate) = eomonth(enddate) then (datediff(day,startdate,enddate)*(rate/datediff(day, eomonth(startdate), dateadd(month, 1, eomonth(startdate)))))
This calculate days and rate for month of start date
datediff(day,startdate,eomonth(startdate)) * (rate/datediff(day, eomonth(startdate), dateadd(month, 1, eomonth(startdate)))))
This calculate days and rate for month of end date
(datediff(day,(DATEADD(month, DATEDIFF(month, 0, enddate), 0)),enddate)* rate/datediff(day, eomonth(enddate), dateadd(month, 1, eomonth(enddate))))

Related

How to spread month to day with amount value divided by total days per month

I have data with an amount of 1 month and want to change it to 30 days.
if 1 month the amount is 20000 then per day is 666.67
The following are sample data and results:
Account
Project
Date
Segment
Amount
Acc1
1
September 2022
Actual
20000
Result :
I need a query using sql server
You may try a set-based approach using an appropriate number table and a calculation with windowed COUNT().
Data:
SELECT *
INTO Data
FROM (VALUES
('Acc1', 1, CONVERT(date, '20220901'), 'Actual', 20000.00)
) v (Account, Project, [Date], Segment, Amount)
Statement for all versions, starting from SQL Server 2016 (the number table is generated using JSON-based approach with OPENJSON()):
SELECT d.Account, d.Project, a.[Date], d.Segment, a.Amount
FROM Data d
CROSS APPLY (
SELECT
d.Amount / COUNT(*) OVER (ORDER BY (SELECT NULL)),
DATEADD(day, CONVERT(int, [key]), d.[Date])
FROM OPENJSON('[1' + REPLICATE(',1', DATEDIFF(day, d.[Date], EOMONTH(d.[Date]))) + ']')
) a (Amount, Date)
Statement for SQL Server 2022 (the number table is generated with GENERATE_SERIES()):
SELECT d.Account, d.Project, a.[Date], d.Segment, a.Amount
FROM Data d
CROSS APPLY (
SELECT
d.Amount / COUNT(*) OVER (ORDER BY (SELECT NULL)),
DATEADD(day, [value], d.[Date])
FROM GENERATE_SERIES(0, DATEDIFF(day, d.[Date], EOMONTH(d.[Date])))
) a (Amount, Date)
Notes:
Both approaches calculate the days for each month. If you always want 30 days per month, replace DATEDIFF(day, d.[Date], EOMONTH(d.[Date])) with 29.
There is a rounding issue with this calculation. You may need to implement an additional calculation for the last day of the month.
You can use a recursive CTE to generate each day of the month and then divide the amount by the number of days in the month to achive the required output
DECLARE #Amount NUMERIC(18,2) = 20000,
#MonthStart DATE = '2022-09-01'
;WITH CTE
AS
(
SELECT
CurrentDate = #MonthStart,
DayAmount = CAST(#Amount/DAY(EOMONTH(#MonthStart)) AS NUMERIC(18,2)),
RemainingAmount = CAST(#Amount - (#Amount/DAY(EOMONTH(#MonthStart))) AS NUMERIC(18,2))
UNION ALL
SELECT
CurrentDate = DATEADD(DAY,1,CurrentDate),
DayAmount = CASE WHEN DATEADD(DAY,1,CurrentDate) = EOMONTH(#MonthStart)
THEN RemainingAmount
ELSE DayAmount END,
RemainingAmount = CASE WHEN DATEADD(DAY,1,CurrentDate) = EOMONTH(#MonthStart)
THEN 0
ELSE CAST(RemainingAmount-DayAmount AS NUMERIC(18,2)) END
FROM CTE
WHERE CurrentDate < EOMONTH(#MonthStart)
)
SELECT
CurrentDate,
DayAmount
FROM CTE
In case you want an equal split without rounding errors and without loops you can use this calculation. It spreads the rounding error across all entries, so they are all as equal as possible.
DECLARE #Amount NUMERIC(18,2) = 20000,
#MonthStart DATE = '20220901'
SELECT DATEADD(DAY,Numbers.i - 1,#MonthStart)
, ShareSplit.Calculated_Share
, SUM(ShareSplit.Calculated_Share) OVER (ORDER BY (SELECT NULL)) AS Calculated_Total
FROM (SELECT DISTINCT number FROM master..spt_values WHERE number BETWEEN 1 AND DAY(EOMONTH(#MonthStart)))Numbers(i)
CROSS APPLY(SELECT CAST(ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) * 0.01
+ CASE
WHEN Numbers.i
<= ABS((#Amount - (ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) / 100.0 * DAY(EOMONTH(#MonthStart)))) * 100)
THEN 0.01 * SIGN(#Amount - (ROUND(#Amount * 100 / DAY(EOMONTH(#MonthStart)),0) / 100.0 * DAY(EOMONTH(#MonthStart))))
ELSE 0
END AS DEC(18,2)) AS Calculated_Share
)ShareSplit

How can I get count of sql query using WHERE clause ( SQL Server )

I have this query that can return the leaveID , Id and day .
select DAY,employeeId,(select lv.EmployeeId
from employee_Leaves lv
where Date between lv.start_date and lv.end_date and lv.EmployeeId=c.employeeId and c.isActive=1) as Leave
from employee_c c,holiday hl,Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1))
where DATENAME(DD, Weekday) IN (select dayId+1 from days) and Date not between hl.startDate and hl.endDate and c.isActive=1 order by employeeID,DAY
the output of this query is something like this:
What I'm searching for is , return the count of the leaveID ( where LeaveID is null ) depending on the employee.
I suppose that the count of leaveID for employee 1 that have NULL values is 19 and 25 for the employee 2.
So , the result expected should be something like this :
employeeId leavecount
1 19
2 25
I have tried this query , but it returns the count of all leaves for the all employees , what I need is the leavecount for every employee.
select count(*) from (select (select lv.EmployeeId from employee_Leaves lv where Date between lv.start_date and lv.end_date and lv.EmployeeId=c.employeeId and c.isActive=1) as leaveID from employee_c c,holiday hl,Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' AS datetime))+1, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+1, -1))
where DATENAME(DD, Weekday) IN (select dayId+1 from days) and Date not between hl.startDate and hl.endDate and c.isActive=1 ) sc
How can I do that?
Just use conditional aggregation:
select employeeId,
sum(case when leaveID is null then 1 else 0 end)
from employee_leave el
group by employeeId;
You can use group by on your result to have your desired output:
select employeeid , count(*) from
(
select DAY,employeeId,(select lv.EmployeeId
from employee_Leaves lv
where Date between lv.start_date and lv.end_date and lv.EmployeeId=c.employeeId and c.isActive=1) as Leave
from employee_c c,holiday hl,Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1))
where DATENAME(DD, Weekday) IN (select dayId+1 from days) and Date not between hl.startDate and hl.endDate and c.isActive=1
) t
where leave is null
group by employeeid

year to date running total for fiscal year

SELECT DISTINCT
ACCOUNTDATE
,PROPERTYNAME
,rt.management
from aaa t
cross apply
(select SUM(MANAGEMENT) as management
from aaa
where
PROPERTYNAME = t.PROPERTYNAME and
ACCOUNTDATE BETWEEN dateadd(MONTH, datediff(MONTH, 0,t.ACCOUNTDATE),0) -- start of month
AND t.ACCOUNTDATE
) as rt
WHERE AccountDate BETWEEN #STARTOFMONTH_MAN AND #ENDOFMONTH_MAN
ORDER BY AccountDate
This is the query to find month to date.
How to find year to date for fiscal year from the same query?
eg: running total from 01/04/2015-31/03/2016
Not sure from wich date you need the FY start. Suppose from #STARTOFMONTH_MAN. Then you can get FY start as
declare #fymonth int = 4; -- first month of FY.
declare #STARTOFMONTH_MAN date = '20160320';
select fyStart = dateadd(MONTH,
#fymonth - CASE WHEN month(#STARTOFMONTH_MAN) >= #fymonth THEN 1 ELSE 13 END,
dateadd(YEAR, datediff(YEAR, 0, #STARTOFMONTH_MAN),0));
You may simplify those dates calculations by creating calendar table.
EDIT
Idea is to restrict data in WHERE with the larger interval and conditionally SUM data for the subinterval.
declare #fymonth int = 4; -- first month of FY.
SELECT DISTINCT
ACCOUNTDATE
,PROPERTYNAME
,rt.FYManagement, rt.MonthManagement
FROM aaa t
CROSS APPLY
(SELECT
SUM(t2.MANAGEMENT) AS FYManagement
,SUM(CASE WHEN t2.ACCOUNTDATE BETWEEN
-- start of month for t.ACCOUNTDATE
dateadd(MONTH, datediff(MONTH, 0, t3.ACCOUNTDATE), 0)
AND t3.ACCOUNTDATE
THEN t2.MANAGEMENT END) AS MonthManagement
from aaa t2
JOIN aaa t3 ON t3.primarykey = t.primarykey -- change as needed to get 1 to 1 JOIN
where
t2.PROPERTYNAME = t.PROPERTYNAME and
t2.ACCOUNTDATE BETWEEN
-- FY start for t.ACCOUNTDATE
dateadd(MONTH,
#fymonth - CASE WHEN month(t.ACCOUNTDATE) >= #fymonth THEN 1 ELSE 13 END,
dateadd(YEAR, datediff(YEAR, 0, t.ACCOUNTDATE), 0));
AND t.ACCOUNTDATE
) as rt
WHERE AccountDate BETWEEN #STARTOFMONTH_MAN AND #ENDOFMONTH_MAN
ORDER BY AccountDate
declare #fymonth int = 4; -- first month of FY.
SELECT DISTINCT
ACCOUNTDATE
,PROPERTYNAME
,rt.FYManagement, rt.MonthManagement
FROM aaa t
CROSS APPLY
(SELECT
SUM(t2.MANAGEMENT) AS FYManagement
,SUM(CASE WHEN t2.ACCOUNTDATE BETWEEN
-- start of month for t.ACCOUNTDATE
dateadd(MONTH, datediff(MONTH, 0, t3.ACCOUNTDATE), 0)
AND t3.ACCOUNTDATE
THEN t2.MANAGEMENT END) AS MonthManagement
from aaa t2
JOIN aaa t3 ON t3.primarykey = t.primarykey -- change as needed to get 1 to 1 JOIN
where
t2.PROPERTYNAME = t.PROPERTYNAME and
t2.ACCOUNTDATE BETWEEN
-- FY start for t.ACCOUNTDATE
dateadd(MONTH,
#fymonth - CASE WHEN month(t.ACCOUNTDATE) >= #fymonth THEN 1 ELSE 13 END,
dateadd(YEAR, datediff(YEAR, 0, t.ACCOUNTDATE), 0));
AND t.ACCOUNTDATE
) as rt
WHERE AccountDate BETWEEN #STARTOFMONTH_MAN AND #ENDOFMONTH_MAN
ORDER BY AccountDate

How to select Months for data without any data existing in SQL

I have a line chart/graph that I need to include for all service outages we went through in the year. Currently, my SQL can pull data that includes any tickets creating in a ticketing system for these services.
However, if no ticket was created for a service, the service will not show in the rows or the service will show but not have a 100% Uptime for the months where no tickets were created for it.
Example of what I am looking for:
MONTH Service1 Service2 Service3
1 100% 99.7% 100%
2 99.8% 100% 96.5%
3 100% 99.8% 100%
But what it looks like is this:
MONTH Service1 Service2 Service3
1 99.7%
2 99.8% 96.5%
3 99.8%
The services get pulled by using a WHERE [Resource]='Affected Service' so they are dynamically brought into the table, but no data is pulled for the service if no ticket was created in that month.
Current SQL Coding:
WITH rslt (ResourceID, YearNumber, MonthNumber, AvailableMinutes, DowntimeMinutes) AS (
SELECT
ore.ResourceID,
DATEPART(yyyy, ipr.OpenDate_MST) YearNumber,
DATEPART(mm, ipr.OpenDate_MST) MonthNumber,
MAX(CASE
WHEN DATEADD(MONTH, DATEDIFF(MONTH, -1, ipr.OpenDate_MST), -1) = DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE()), -1)
THEN (DATEPART(DD, GETDATE()) * 1440.0)
ELSE (DATEPART(DD, DATEADD(MONTH, DATEDIFF(MONTH, -1, ipr.OpenDate_MST), -1)) * 1440.0)
END) AvailableMinutes,
ISNULL(SUM(DATEDIFF(mi, ipr.OutageStartTime, ipr.OutageEndTime)), 0) DowntimeMinutes
FROM
vIncidentProblemRequest ipr
INNER JOIN vOwnedResource ore ON ore.ResourceID = ipr.AffectedServiceID
WHERE
CONVERT(DATETIME, CONVERT(CHAR(10), ipr.OpenDate_MST, 101)) >= '1/1/2013 12:00:00'
AND CONVERT(DATETIME, CONVERT(CHAR(10), ipr.OpenDate_MST, 101)) <= '12/31/2013 11:59:59'
GROUP BY
ore.ResourceID,
DATEPART(yyyy, ipr.OpenDate_MST),
DATEPART(mm, ipr.OpenDate_MST),
),
rslt2 (ResourceID, Application, ResourceClass, YearNumber, MonthNumber, AvailableMinutes, DowntimeMinutes, UptimePercent) AS (
SELECT
ore.ResourceID,
ore.ResourceName Application,
ore.ResourceClass,
rslt.YearNumber,
rslt.MonthNumber,
rslt.AvailableMinutes,
ISNULL(rslt.DowntimeMinutes, 0) DowntimeMinutes,
CASE
WHEN rslt.DowntimeMinutes IS NULL
THEN 1.0
ELSE ((rslt.AvailableMinutes - rslt.DowntimeMinutes)/rslt.AvailableMinutes)
END UptimePercent
FROM
vOwnedResource ore
LEFT OUTER JOIN rslt ON rslt.ResourceID = ore.ResourceID
WHERE
ore.ResourceClass = 'Enterprise Service')
select
MIN(DATEPART(yyyy, d.Date)) Year,
MIN(DATEPART(mm, d.Date)) MonthNum,
SUBSTRING(MIN(DATENAME(mm, d.Date)), 1, 3) Month,
r.Application,
r.ResourceClass,
CASE
WHEN r.UptimePercent IS NULL
THEN 1.0
ELSE r.UptimePercent
END UptimePercent
FROM
DimDate d
INNER JOIN rslt2 r ON r.YearNumber = datepart(yyyy, d.Date) AND r.MonthNumber = datepart(mm, d.Date)
WHERE
d.Date >= '1/1/2013 12:00:00'
AND d.Date <= '12/31/2013 11:59:59'
GROUP BY
datepart(yyyy, d.Date),
datepart(mm, d.Date),
DATENAME(mm, d.Date),
r.Application,
r.ResourceClass,
r.UptimePercent
ORDER BY
4,1,2
How about this-
Create a temp table having all service values as 100% for each month.
After that, update the temp table with the real values based on your filter condition.
It would really help if you showed the query. However, somewhere along the way, you can solve your problem using coalesce(). Assuming the values are really numbers between 0 and 100:
select month, coalesce(service1, 100) as service1,
coalesce(service2, 100) as service2, coalesce(service3, 100) as service3
The following answer describes the 'EXISTS' command. I think it will help you greatly.
Using CASE to Return a String If No Results From SELECT Statement

Calculating number of days between Admission Date and today

I found the following in Stackoverflow and have been working with it to find the number of Patient Days each month. It works very well if I have both the Admit and Discharge dates.
I can't figure out how to edit it to calculate the Patient days when the discharge date has not been completed... the patient is still in the hospital. If feels like I should use Coalesce or ISNULL to find the records where the Discharge Date is NULL, but I'm not a programmer and would appreciate your help.
WITH Mos AS (
SELECT
D.ED_ADMIT_DATE,
D.ED_DISCHARGE_DATE,
Number,
DateAdd(Month, Number, D.ED_ADMIT_DATE - Day(D.ED_ADMIT_DATE) + 1) MoDate
FROM
cases_cstm D
INNER JOIN master.dbo.spt_values V ON V.Number <= DateDiff(Month, D.ED_ADMIT_DATE, D.ED_DISCHARGE_DATE)
WHERE
V.Type = 'P'), Dys AS (
SELECT
MoDate,
DateDiff(
Day,
CASE WHEN Number = 0 THEN ED_ADMIT_DATE ELSE MoDate END,
CASE WHEN Number = DateDiff(Month, ED_ADMIT_DATE, ED_DISCHARGE_DATE) THEN ED_DISCHARGE_DATE ELSE DateAdd(Month, 1, MoDate) -1
END
) + 1 Cnt
FROM Mos)
SELECT Year(MoDate) Yr,
Coalesce(DateName(Month, MoDate), 'Total') Mo,
Convert(varchar(11), Sum(Cnt)) + ' day' + CASE WHEN Sum(Cnt) = 1 THEN '' ELSE 's' END Descr
FROM Dys
GROUP BY MoDate
WITH ROLLUP
ORDER BY
Grouping(MoDate),
MoDate;
You could replace each occurance of ED_DISCHARGE_DATE with:
IsNull(ED_DISCHARGE_DATE,getdate())
This uses the current time whenever the discharge date is unavailable.