I want to calculate a new value for the new column based on other columns in T-SQL.
My data is look like this:
Each row represents one person in one day.
The WorkHours is calculated based on the Portion column:
Round(FF.Portion * 7.4, 3) AS WorkHours
I want to calculate a percentage of hours which people not have been at work in relation to the TOTAL workhours for day for each school. For example if 10 people work full hour in one school for one day, it gives 74 working hours and if one person have been sick that day it will give (7,4 % 74 * 100) which is 10% (the WorkHours is calculated based on Portion column)
In your comment you state Peter had 6 hours on 1/1/2017 as "seek" but it was actually 7.4. With that in mind, we can calculate your results as follows:
declare #table table (Name varchar(16), Date date, School char(2), FreedayCode int, Freeday varchar(64), Portion decimal (6,4))
insert into #table
values
('Mike','20170101','AA',-1,'AtWork',1),
('Mike','20170201','AA',1,'Seek',1),
('Ali','20170101','BB',-1,'AtWork',0.94594),
('Ali','20170201','BB',-1,'AtWork',0.94594),
('Sara','20170101','CC',2,'holiday',1),
('Sara','20170201','CC',1,'Seek',1),
('Peter','20170101','AA',1,'Seek',1),
('Peter','20170201','AA',1,'Seek',1),
('Nina','20170101','AA',-1,'AtWork',0.81081),
('Nina','20170201','AA',-1,'AtWork',0.81081)
select
Name
,Date
,School
,FreeDayCode
,Freeday,Portion
,NewColumn = sum(case when Freeday <> 'AtWork' then Round(Portion * 7.4,3) else 0 end) over (partition by Date, School) / sum(Round(Portion * 7.4,3)) over (partition by Date, School)
from
#table
order by
Date
,School
RETURNS
+-------+------------+--------+-------------+---------+---------+-----------+
| Name | Date | School | FreeDayCode | Freeday | Portion | NewColumn |
+-------+------------+--------+-------------+---------+---------+-----------+
| Mike | 2017-01-01 | AA | -1 | AtWork | 1.0000 | 0.355769 |
| Peter | 2017-01-01 | AA | 1 | Seek | 1.0000 | 0.355769 |
| Nina | 2017-01-01 | AA | -1 | AtWork | 0.8108 | 0.355769 |
| Ali | 2017-01-01 | BB | -1 | AtWork | 0.9459 | 0.000000 |
| Sara | 2017-01-01 | CC | 2 | holiday | 1.0000 | 1.000000 |
| Peter | 2017-02-01 | AA | 1 | Seek | 1.0000 | 0.711538 |
| Mike | 2017-02-01 | AA | 1 | Seek | 1.0000 | 0.711538 |
| Nina | 2017-02-01 | AA | -1 | AtWork | 0.8108 | 0.711538 |
| Ali | 2017-02-01 | BB | -1 | AtWork | 0.9459 | 0.000000 |
| Sara | 2017-02-01 | CC | 1 | Seek | 1.0000 | 1.000000 |
+-------+------------+--------+-------------+---------+---------+-----------+
Related
I will provide examples and code where I can. Assume everything except [CycleStart] and [CycleEnd] datatypes are Varchar, I'm not too fussed about this at this stage.
Table A consists of the following RAW sample data:
+-------+---------+----------------+------------+------------+
| JobID | JobName | CycleDesc | CycleStart | CycleEnd |
+-------+---------+----------------+------------+------------+
| 10003 | Run1 | January 2019 | 31/12/2018 | 31/12/2018 |
| 10005 | Run2 | December 2018 | 31/12/2017 | 31/11/2018 |
| 10006 | Run3 | March 2019 | 31/12/2018 | 31/02/2019 |
| 10007 | Run4 | September 2019 | 31/12/2018 | 31/09/2019 |
| 10008 | Run5 | November 2019 | 31/12/2018 | 31/10/2019 |
+-------+---------+----------------+------------+------------+
Table B consists of the following sample data and the code used to generate this data is below:
+-------+---------+---------+
| JobID | PeriodID | Entity |
+-------+---------+---------+
| 10003 | 202101 | XYZ1 |
| 10003 | 202112 | XYZ2 |
| 10007 | 202008 | XYZ3 |
| 10007 | 202003 | XYZ4 |
| 10008 | 201904 | XYZ5 |
+-------+----------+--------+
Declare #Counter3 INT
SELECT #Counter3=1
WHILE #Counter3 <= 1000
BEGIN
INSERT INTO [dbo].[TableB]
SELECT
FLOOR(RAND()*(33979-1+1))+1 [JobID]
,CAST(ROUND(((2021 - 2019 -1) * RAND() + 2020), 0) AS VARCHAR) + RIGHT('0'+CAST(FLOOR(RAND()*(12-1+1))+1 AS VARCHAR),2) [PeriodID]
,FLOOR(RAND()*(23396-1+1))+1 [Entity]
The issue lies within Table B column [PeriodID]. This column represents an ID generated from [CycleStart] in Table A e.g. 31/12/2018 = 201812 (YYYYMM).
What I want to show in Table B is a Period ID for each Job ID but show EACH month + 30 years ahead of the [CycleStart] date. Example table of what I am looking to achieve:
+-------+---------+---------+
| JobID | PeriodID | Entity |
+-------+---------+---------+
| 10006 | 201812 | XYZ1 |
| 10006 | 201901 | XYZ2 |
| 10006 | 201902 | XYZ3 |
| 10006 | 201903 | XYZ4 |
| 10006 | 201904 | XYZ5 |
| 10006 | 201905 | XYZ5 |
| 10006 | 201906 | XYZ5 |
| 10006 | 201907 | XYZ5 |
| ... | +30yrs | ... |
| 10006 | 204812 | XYZ5 |
+-------+----------+--------+
How can I achieve this? Currently I am just randomly generating IDs which is not related to the [CycleStart] date and therefore just skewing my data but this is the only way I can think of doing it.
The best way is to create a calendar table / date dimension. You can use this table to solve this issue, and reuse it for other problems later. (Search online for some examples on how to build one).
If you have this table then you only need to join this table and that's it.
e.g.
INSERT INTO TableB ( JobID , PeriodID)
SELECT DISTINCT A.JobID , D.TheYear * 100 + D.TheMonth
FROM tableA A
JOIN myDateTable D
ON D.TheDate BETWEEN CONVERT(date , A.CycleStart , 103) AND DATEADD(YEAR,30, CONVERT(date , A.CycleStart , 103));
I'm trying to calculate depreciation on vehicles. If there is a rebate on a vehicle, I need to stop the depreciation, factor in the rebaste based on the month it look affect, and resume the depreciation calculation.
A vehicle depreciates at a flat rate of 2% every month with 50 months being the point of 100% depreciation. When a rebate appears, I can stop the depreciation, but I don't know how to make it start again from a certain month.
Below is an example of the table's deprecation up to directly before the rebate:
+----------+-------+------------+--------------+------------+------------+
| Vehicle# | month | depDate | Initial Cost | Monthlydep | totaldep |
+----------+-------+------------+--------------+------------+------------+
| 12451 | 1 | 2015-08-01 | 44953.24 | 899.06 | 899.0648 |
| 12451 | 2 | 2015-09-01 | 44953.24 | 899.06 | 1798.1296 |
| ------- | ----- | ----- | ----- | ----- | ----- |
| 12451 | 42 | 2019-01-01 | 44953.24 | 899.06 | 37760.7216 |
| 12451 | 43 | 2019-02-01 | 44953.24 | 899.06 | 38659.7864 |
+----------+-------+------------+--------------+------------+------------+
Then let's say that a rebate comes in this month (2019-03-01) it needs to be factored in and then the depreciation needs to be recalculated from that month onwards the. How do I restart the depreciation from month 43 instead of it going through everything?
For example let's say that we get a rebate in month 44 for $200 dollars. The table should look like something below:
+----------+-------+------------+--------------+------------+------------+
| Vehicle# | month | depDate | Initial Cost | Monthlydep | totaldep |
+----------+-------+------------+--------------+------------+------------+
| 12451 | 43 | 2019-02-01 | 44953.24 | 899.06 | 38659.7864 |
| 12451 | 44 | 2019-03-01 | 44953.24 | 1099.06 | 39758.8464 |
| 12451 | 45 | 2019-04-01 | 44953.24 | 1099.06 | 40857.9064 |
| 12451 | 46 | 2019-05-01 | 44953.24 | 1099.06 | 41956.9664 |
| 12451 | 47 | 2019-06-01 | 44953.24 | 1099.06 | 43056.0264 |
| 12451 | 48 | 2019-06-01 | 44953.24 | 1099.06 | 44155.0864 |
| 12451 | 49 | 2019-06-01 | 44953.24 | 1099.06 | 45254.1464 |
+----------+-------+------------+--------------+------------+------------+
So month 49 would be the final month because the totalDep is equal to or higher than the initial cost
My sample code is below. If you remove the first cte and the join inner join in the top part of the union then that is the working depreciation calculation:
;With cte As( Select bd.[VehicleID]
,Max(bd.[Month]) As month
,Max(DateAdd(DAY,1,EOMONTH(DepreciationReportDate,-1))) As DepreciationReportDate
,Max(bd.MonthlyDepreciation) As MonthlyDepreciation
,Max(bd.AdjustedPurchaseCost) As AdjustedPurchaseCost
,Max(AccumulatedDepreciation) As AccumulatedDepreciation
From Work.dbo.DepreciationSchedule bd
Group By bd.VehicleID
)
,cte_CreateRows As
(
Select bd.[VehicleID]
,bd.[Month]
,DATEADD(DAY,1,EOMONTH(bd.DepreciationReportDate,-1)) As DepreciationReportDate
,bd.MonthlyDepreciation
,bd.AdjustedPurchaseCost
,bd.AccumulatedDepreciation
From Work.dbo.DepreciationSchedule bd
Inner Join cte cte
On cte.VehicleID = bd.VehicleID
And cte.month = bd.Month
Union All
Select bd.[VehicleID]
,[Month] = Cast(cr.[Month]+1 As int)
,DATEADD(DAY,1,EOMONTH(DateAdd(Month, 1, cr.DepreciationReportDate),-1)) As DepreciationReportDate
,bd.MonthlyDepreciation
,bd.AdjustedPurchaseCost
,AccumulatedDepreciation = cr.AccumulatedDepreciation + cr.MonthlyDepreciation
From Work.dbo.DepreciationSchedule bd
Inner Join cte_CreateRows cr On bd.[VehicleID] = cr.[VehicleID]
Where cr.AccumulatedDepreciation < cr.AdjustedPurchaseCost
And DateAdd(Month,1, DateAdd(DAY,1,EOMONTH(cr.DepreciationReportDate,-1))) < DATEADD(DAY,1,EOMONTH(GetDate(),-1))
)
Select a.VehicleID
,a.Month
,a.DepreciationReportDate
,Cast(a.MonthlyDepreciation As Decimal(12,2)) As 'Monthly Depreciation Expense'
,a.AdjustedPurchaseCost
,a.AccumulatedDepreciation
From [cte_CreateRows] As a
Order By a.VehicleID, a.Month
Let's say I have the following table (data is completely fiction):
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck
1 | 2017-12 | 900 | Jack | Ill | -
2 | 2018-01 | 900 | Jack | Ill | 1
3 | 2018-02 | 900 | Jack | Ill | 2
4 | 2018-03 | 900 | Jack | Healthy | 1
5 | 2017-02 | 901 | Bill | Ill | -
6 | 2017-03 | 901 | Bill | Ill | 1
7 | 2017-05 | 901 | Bill | Healthy | 1
For each record, I would like to see the previous status that person had X months ago since last check (column MonthsAgoSinceLastCheck). Notice that MonthDate can skip months.
So in this case, the result would be
ID | MonthDate | PersonID | Name | Status | MonthsAgoSinceLastCheck | PreviousSatus
1 | 2017-12 | 900 | Jack | Ill | - | -
2 | 2018-01 | 900 | Jack | Ill | 1 | Ill
3 | 2018-02 | 900 | Jack | Ill | 2 | Ill
4 | 2018-03 | 900 | Jack | Healthy | 1 | Ill
5 | 2017-02 | 901 | Bill | Healthy | - | -
6 | 2017-03 | 901 | Bill | Healthy | 1 | Healthy
7 | 2017-05 | 901 | Bill | Ill | 2 | Healthy
Any sugestions/tips? I tried to do this with CTE's and self-joins but failed on both.
It's way easier to use full dates than year and months separately. The first thing you should do is generate a full date from your year + month. Then just self join with previous month, depending on the last check.
;WITH DataWithDates AS
(
SELECT
T.ID,
MonthDate = CONVERT(DATE, T.MonthDate + '-01'),
T.PersonID,
T.Name,
T.Status,
T.MonthsAgoSinceLastCheck
FROM
YourTable AS T
)
SELECT
D.ID,
D.MonthDate,
D.PersonID,
D.Name,
D.Status,
D.MonthsAgoSinceLastCheck,
PreviousStatus = N.Status
FROM
DataWithDates AS D
LEFT JOIN DataWithDates AS N ON
D.PersonID = N.PersonID AND
N.MonthDate = DATEADD(MONTH, -1 * D.MonthsAgoSinceLastCheck, D.MonthDate)
I'm assuming your MonthDate has values for all rows, otherwise the conversion will fail. I'm also assuming that your - values for MonthsAgoSinceLastCheck are actually NULL.
try this:
select *,LAG(Status) OVER(Partition by Name Order by MonthDate,Id) AS PreviousSatus
from tab1
order by id
SQl Fiddle:http://sqlfiddle.com/#!18/04407/4
I am trying to formulate a query in SQL Server where:
Date | Name | Amount | AmountX
------------+---------+--------+-------
2010-01-01 | Test | 0 | 0
2010-02-01 | Test | 0 | 0
2010-03-01 | Test | 0 | 0
2011-01-01 | Test | 62.61 | 63.86
2011-02-01 | Test | 62.61 | 63.86
2011-03-01 | Test | 62.61 | 63.86
2012-01-01 | Test | 62.61 | 65.14
2012-02-01 | Test | 62.61 | 65.14
2012-03-01 | Test | 62.61 | 65.14
2013-01-01 | Test | 62.61 | 66.44
2013-02-01 | Test | 62.61 | 66.44
2013-03-01 | Test | 62.61 | 66.44
2014-01-01 | Test | 62.61 | 67.77
2014-02-01 | Test | 62.61 | 67.77
2014-03-01 | Test | 62.61 | 67.77
2015-01-01 | Test | 0 | 0
2015-02-01 | Test | 0 | 0
2015-03-01 | Test | 0 | 0
2016-01-01 | Test | 67.95 | 69.31
2016-02-01 | Test | 67.95 | 69.31
2016-03-01 | Test | 67.95 | 69.31
2017-01-01 | Test | 67.95 | 70.70
2017-02-01 | Test | 67.95 | 70.70
2017-03-01 | Test | 67.95 | 70.70
2018-01-01 | Test | 67.95 | 72.11
2018-02-01 | Test | 67.95 | 72.11
2018-03-01 | Test | 67.95 | 72.11
2019-01-01 | Test | 67.95 | 73.55
2019-02-01 | Test | 67.95 | 73.55
2019-03-01 | Test | 67.95 | 73.55
The Date, Name and Amount columns come from the table.
I need to create a query to include to AmountX column based on the Date grouping. The Amount and a factor of 2% for calculation.
Year 2010 can be ignored because the Amount values are 0s.
For the year 2011, the Amount values are none 0s which is 62.61. I need to multiply that by 1.02 giving 63.86, for the whole year and display it in the AmountX column.
Now for the year 2012, it will be calculated 63.86 for previous year X 1.02 = 65.14.
So for 2013, it will be 64.14 X 1.02 = 66.44.
And for 2014, it will be 66.44 X 1.02 = 67.77.
Is this doable?
Any help is greatly appreciated.
RS..
I think you want:
select t.*,
(amount * power(1.02, year(date) - 2010)) as amountX
from t;
I have a table that holds values for particular months:
| MFG | DATE | FACTOR |
-----------------------------
| 1 | 2013-01-01 | 1 |
| 2 | 2013-01-01 | 0.8 |
| 2 | 2013-02-01 | 1 |
| 2 | 2013-12-01 | 1.55 |
| 3 | 2013-01-01 | 1 |
| 3 | 2013-04-01 | 1.3 |
| 3 | 2013-05-01 | 1.2 |
| 3 | 2013-06-01 | 1.1 |
| 3 | 2013-07-01 | 1 |
| 4 | 2013-01-01 | 0.9 |
| 4 | 2013-02-01 | 1 |
| 4 | 2013-12-01 | 1.8 |
| 5 | 2013-01-01 | 1.4 |
| 5 | 2013-02-01 | 1 |
| 5 | 2013-10-01 | 1.3 |
| 5 | 2013-11-01 | 1.2 |
| 5 | 2013-12-01 | 1.5 |
What I would like to do is pivot these using a calendar table (already defined):
And finally, cascade the NULL columns to use the previous value.
What I've got so far is a query that will populate the NULLs with the last value for mfg = 3. Each mfg will always have a value for the first of the year. My question is; how do I pivot this and extend to all mfg?
SELECT c.[date],
f.[factor],
Isnull(f.[factor], (SELECT TOP 1 factor
FROM factors
WHERE [date] < c.[date]
AND [factor] IS NOT NULL
AND mfg = 3
ORDER BY [date] DESC)) AS xFactor
FROM (SELECT [date]
FROM calendar
WHERE Datepart(yy, [date]) = 2013
AND Datepart(d, [date]) = 1) c
LEFT JOIN (SELECT [date],
[factor]
FROM factors
WHERE mfg = 3) f
ON f.[date] = c.[date]
Result
| DATE | FACTOR | XFACTOR |
---------------------------------
| 2013-01-01 | 1 | 1 |
| 2013-02-01 | (null) | 1 |
| 2013-03-01 | (null) | 1 |
| 2013-04-01 | 1.3 | 1.3 |
| 2013-05-01 | 1.2 | 1.2 |
| 2013-06-01 | 1.1 | 1.1 |
| 2013-07-01 | 1 | 1 |
| 2013-08-01 | (null) | 1 |
| 2013-09-01 | (null) | 1 |
| 2013-10-01 | (null) | 1 |
| 2013-11-01 | (null) | 1 |
| 2013-12-01 | (null) | 1 |
SQL Fiddle
Don't know if you need the dates to be dynamic from the calender table or if mfg can be more than 5 but this should give you some ideas.
select *
from (
select c.date,
t.mfg,
(
select top 1 f.factor
from factors as f
where f.date <= c.date and
f.mfg = t.mfg and
f.factor is not null
order by f.date desc
) as factor
from calendar as c
cross apply(values(1),(2),(3),(4),(5)) as t(mfg)
) as t
pivot (
max(t.factor) for t.date in ([20130101], [20130201], [20130301],
[20130401], [20130501], [20130601],
[20130701], [20130801], [20130901],
[20131001], [20131101], [20131201])
) as P
SQL Fiddle