Calculate duration in years if months increases from 12 months - sql-server

I have a column that accepts values in NoOfMonths which I want to calculate in years. My table structure let's say is given below
CREATE TABLE Duration
(
TotalMonths int
);
INSERT INTO Duration VALUES (24);
INSERT INTO Duration VALUES (18);
INSERT INTO Duration VALUES (7);
I want the result to be shown like this,
Months Duration
24 2 Years
18 1 Year 6 Months
7 7 Months

You can get the data like this
select duration,
duration / 12 as years,
duration % 12 as months
from your_table
You should do the rest in the presentation layer of your program.

Try This
SELECT
*,
CASE WHEN TotalMonths>12
THEN CAST(TotalMonths/12 AS VARCHAR(10))+' Years '
ELSE '' END
+
CASE WHEN TotalMonths%12>0
THEN CAST(TotalMonths%12 AS VARCHAR(10))+' Months'
ELSE '' END
FROM Duration

Something like this
DECLARE #Duration TABLE(TotalMonths int);
INSERT INTO #Duration VALUES (24),(18),(7);
SELECT d.TotalMonths
,A.*
, CASE WHEN A.CountYears>0 THEN CAST (A.CountYears AS VARCHAR(10)) + ' years ' ELSE '' END
+ CASE WHEN A.CountMonths>0 THEN CAST(A.CountMonths AS VARCHAR(10)) + ' months' ELSE '' END AS TextDescription
FROM #Duration AS d
CROSS APPLY(SELECT d.TotalMonths / 12 AS CountYears
,d.TotalMonths % 12 AS CountMonths) AS A;
Edited to using modulo operator %
The result
+-------------+------------+-------------+------------------+
| TotalMonths | CountYears | CountMonths | TextDescription |
+-------------+------------+-------------+------------------+
| 24 | 2 | 0 | 2 years |
+-------------+------------+-------------+------------------+
| 18 | 1 | 6 | 1 years 6 months |
+-------------+------------+-------------+------------------+
| 7 | 0 | 7 | 7 months |
+-------------+------------+-------------+------------------+
Hint: The integer division will silently round to integer values.

you can do that like this :
declare #Duration table (TotalMonths int)
INSERT INTO #Duration VALUES (24), (18), (7)
select d.TotalMonths as months,
case when d.TotalMonths / 12 > 0 then convert(varchar, d.TotalMonths / 12) + ' years ' else '' end
+
case when d.TotalMonths % 12 > 0 then convert(varchar, d.TotalMonths % 12) + ' Months' else '' end
as Duration
from #Duration d
the result is
months Duration
------ --------
24 2 years
18 1 years 6 Months
7 7 Months

You need to check how to display a dummy column, and do the math in it.
something like this
select TotalMonths, ((select TotalMonths from Duration) / 12) As DurationInYears from Duration

here is a simple way that you can convert an integer value into month and days:
SELECT Duration(dd,366,0)
this will convert duration value into months and years.

Related

SQL converting month into Years and Months

I have an X number of months, but I need to break it into xx years yy months in SQL.
For example: 31 months should be 2 years 7 months.
Is there any inbuilt function or something to help?
Many thanks for your help.
You can use modulus to get the months and floor to get the years
SELECT totalmonths, FLOOR(tottalmonths / 12) AS years, totalmonths % 12 as months
FROM -- ...
You can try this:
SELECT
(months_column_name / 12) AS years,
(months_column_name % 12) AS months
FROM table_name
You can create a solution the uses built-in functions, but I'd hardly call it helpful when olde fashioned arithmetic operators will provide the desired results in a much more concise manner, e.g. Hogan's answer. dbfiddle.
with
SomeMonths as (
-- Create some test data from 0 to 100.
select 0 as Months
union all
select Months + 1
from SomeMonths
where Months < 100 ),
CompleteDate as (
-- Convert the number of months into a date based at 1 January 1.
select Months, DateAdd( month, Months, Cast( '00010101' as Date ) ) as Sometime
from SomeMonths )
select Months,
-- Make the number of years zero-based.
Year( Sometime ) - 1 as TheYears,
-- Make the number of months zero-based.
Month( Sometime ) - 1 as TheMonths
from CompleteDate;
Hi there Unknown Coder,
I made some code here that will display the number of months and years..
It also has a more human friendly way of displaying the data, like in your example.
So that last one will display as "2 years 7 months" if your value was 31.
Here's my code with the temp database for testing:
Declare #monthtest TABLE
(
NumOfMonths int
);
INSERT INTO #monthtest (NumOfMonths) select 11
INSERT INTO #monthtest (NumOfMonths) select 13
INSERT INTO #monthtest (NumOfMonths) select 31
select
cast(x1.NumOfMonths / 12 as int) as years,
(x1.NumOfMonths ) % 12 as months,
concat(
cast(x1.NumOfMonths / 12 as int),
(case when cast(x1.NumOfMonths / 12 as int) = 1 then ' year ' else ' years ' end ),
(x1.NumOfMonths ) % 12,
(case when (x1.NumOfMonths ) % 12 = 1 then ' month' else ' months' end )
) as combined
from #monthtest as x1
This will output as
years
months
combined
0
11
0 years 11 months
1
1
1 year 1 month
2
7
2 years 7 months

convert time in string to number of hours

I am trying to convert time in string to number of hours.
For instance
| Hours in text | Number of hours |
| ------------------- | --------------- |
| 1 minute | 0.02 |
| 30 minutes | 0.5 |
| 2 Hours 15 Minutes | 2.25 |
| 8 Hours | 8 |
| 4 Hours 30 Minutes | 4.5 |
| 1 Hour | 1 |
DECLARE #tabvar TABLE(TimeInText VARCHAR(100));
INSERT INTO #tabvar(TimeInText)
SELECT '1 minute' UNION ALL
SELECT '30 minutes' UNION ALL
SELECT '2 Hours 15 Minutes' UNION ALL
SELECT '8 Hours' UNION ALL
SELECT '4 Hours 30 Minutes' UNION ALL
SELECT '1 Hour'
SELECT CONVERT(CHAR(5), DATEADD(MINUTE, 60 * NULLIF(RTRIM(LTRIM(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(TimeInText, ' Hours ', '.'), ' Hours', '.'), ' Hour', '.00'), ' Minutes', ''), ' ', ''))), ''), 0), 108)
FROM #tabvar
When trying to convert 8 Hours I am stuck with "8." instead of "8"
This is not pretty. T-SQL fortΓ© is not string manipulation and your choice of storing times as a descriptive string is a problematic design choice at best. I also, however, feel that having the values as a decimal is also a bad idea; there's a time data type that you should be making use of.
Saying that, this works for the sample data provided:
SELECT tv.TimeInText,
ISNULL(CONVERT(decimal(5,3),LEFT(tv.TimeInText,NULLIF(h.CI,0)-1)),0) + ISNULL((CONVERT(decimal(5,3),LEFT(s.m,NULLIF(m.CI,0)-1)) / 60),0) AS NumberOfHours
FROM #tabvar tv
CROSS APPLY (VALUES(CHARINDEX('hour',tv.TimeInText)))h(CI)
CROSS APPLY (VALUES(CHARINDEX(' ',tv.TimeInText,NULLIF(h.ci,0))))ws(ci)
CROSS APPLY (VALUES(STUFF(tv.TimeInText,1,ISNULL(ws.ci,0),'')))s(m)
CROSS APPLY (VALUES(CHARINDEX('minute',s.m)))m(CI);
Or without using VALUES to make the expressions easy to read... (enjoy this mess πŸ™ƒ):
SELECT tv.TimeInText,
ISNULL(CONVERT(decimal(5,3),LEFT(tv.TimeInText,NULLIF(CHARINDEX('hour',tv.TimeInText),0)-1)),0) + ISNULL((CONVERT(decimal(5,3),LEFT(STUFF(tv.TimeInText,1,ISNULL(CHARINDEX(' ',tv.TimeInText,NULLIF(CHARINDEX('hour',tv.TimeInText),0)),0),''),NULLIF(CHARINDEX('minute',STUFF(tv.TimeInText,1,ISNULL(CHARINDEX(' ',tv.TimeInText,NULLIF(CHARINDEX('hour',tv.TimeInText),0)),0),'')),0)-1)) / 60),0) AS NumberOfHours
FROM #tabvar tv;
db<>fiddle
select *, format(datepart(hour, _time) + datepart(minute, _time)/60., '0.##') as NumberOfHours
from
(
select *,
timefromparts
(
left(TimeInText, charindex(' hour', TimeInText)),
substring(TimeInText, charindex('minute', TimeInText)-3, 3),
0, 0, 0
) as _time
from #tabvar
) as t;
Another option
Example
Select A.*
,NewVal = convert(decimal(10,2),
case when Pos2 like 'Hour%' then try_convert(int,Pos1)+isnull(try_convert(decimal(10,2),Pos3),0)/60
else try_convert(decimal(10,2),Pos1)/60 end
)
From YourTable A
Cross Apply (
Select Pos1 = trim(JSON_VALUE(S,'$[0]'))
,Pos2 = trim(JSON_VALUE(S,'$[1]'))
,Pos3 = trim(JSON_VALUE(S,'$[2]'))
,Pos4 = trim(JSON_VALUE(S,'$[3]'))
From ( values ( '["'+replace(replace([Hours in text],'"','\"'),' ','","')+'"]' ) ) A(S)
) B
Returns
Hours in text NewVal
1 minute 0.02
30 minutes 0.50
2 Hours 15 Minutes 2.25
8 Hours 8.00
4 Hours 30 Minutes 4.50
1 Hour 1.00

How to get count for different results on same table [duplicate]

This question already has answers here:
How can I get multiple counts with one SQL query?
(12 answers)
Closed 4 years ago.
I have a set of queries that represent the data from different sysdates (from the last 5, 7 and 30 days).
My doubt is how to express in a query this results in this matter:
STATE | 5 DAYS | 7 DAYS | 30 DAYS
---------------------------------
INIT | 1 | 1 | 2
---------------------------------
SECN | 2 | 2 | 2
NOTE: This is from a single table with different sysdates in consideration
NOTE2: An query example is this
select
CASE WHEN STATUS = 'INI' then 'Initial'
WHEN STATUS = 'SECN' the 'Second'
END 'Status', count(*)
from db.FilesTable
where 1=1
and DAT_Files >= DATEADD(day,-5,GETDATE())
Use conditional aggregation to count records only when a particular condition occurs. The CASE will be computed before the aggregation occurs, so you can put any expression on any column.
select
State = T.Status,
[5 Days] = COUNT(CASE WHEN T.DAT_Files >= DATEADD(day, -5, GETDATE()) THEN 1 END),
[7 Days] = COUNT(CASE WHEN T.DAT_Files >= DATEADD(day, -7, GETDATE()) THEN 1 END),
[30 Days] = COUNT(1)
from
db.FilesTable AS T
where
T.Status IN ('INI', 'SECN') AND
DAT_Files >= DATEADD(day, -30, GETDATE()) -- Biggest period filter here
GROUP BY
T.Status
I think you could write a query of this type:
SELECT State,
SUM (CASE WHEN ColA < 6 THEN 1 ELSE 0 END) AS '5 Days',
SUM (CASE WHEN ColA IN (6, 7) THEN 1 ELSE 0 END) AS '7 Days',
SUM (CASE WHEN ColA > 7 AND ColA < 31 THEN 1 ELSE 0 END) AS '30 Days'
FROM TableA
Obviously you might want to adjust something, but this gives some direction.

Get data in groups of "Week of..." when dates may be missing

I have data in a table with dates, and want to count the rows by "Week of" (e.g., "Week of 2017-05-01"), where the result has the week's date (starting on Mondays) and the count of matching rows β€” even if there are no rows for that week. (This will all be in a date range.)
I can partition things into weeks readily enough by grouping on DATEPART(wk, D) (where D is the date column), but I'm struggling with:
How to get the "Week of" date and fill, and
How to have a row for a week where there are no matching rows in the data
Here's grouping by week:
SET DATEFORMAT ymd;
SET DATEFIRST 1; -- Monday is first day of week
DECLARE #startDate DATETIME = '2017-05-01';
DECLARE #endDate DATETIME = '2017-07-01';
SELECT DATEPART(wk, D) AS [Week Number], COUNT(*) AS [Count]
FROM #temp
GROUP BY DATEPART(wk, D)
ORDER BY DATEPART(wk, D);
Which gives me:
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
| Week Number | Count |
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
| 19 | 5 |
| 20 | 19 |
| 22 | 8 |
| 23 | 10 |
| 24 | 5 |
| 26 | 4 |
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
But ideally I want:
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
| Week | Count |
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
| 2017-05-01 | 5 |
| 2017-05-08 | 19 |
| 2017-05-15 | 0 |
| 2017-05-22 | 8 |
| 2017-05-29 | 10 |
| 2017-06-05 | 5 |
| 2017-06-12 | 0 |
| 2017-06-19 | 4 |
| 2017-06-26 | 0 |
+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’βˆ’+
How can I do that?
Set up information for testing:
SET DATEFIRST 1;
SET DATEFORMAT ymd;
CREATE TABLE #temp (
D DATETIME
);
GO
INSERT INTO #temp (D)
VALUES -- Week of 2017-05-01 (#19)
('2017-05-01'),('2017-05-01'),('2017-05-01'),
('2017-05-06'),('2017-05-06'),
-- Week of 2017-05-08 (#20) - note no data actually on the 8th
('2017-05-10'),
('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),('2017-05-11'),
('2017-05-12'),('2017-05-12'),('2017-05-12'),('2017-05-12'),
('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),('2017-05-13'),
('2017-05-14'),
-- Week of 2017-05-15 (#21)
-- (note we have no data for this week)
-- Week of 2017-05-22 (#22)
('2017-05-22'),('2017-05-22'),('2017-05-22'),
('2017-05-23'),('2017-05-23'),('2017-05-23'),('2017-05-23'),('2017-05-23'),
-- Week of 2017-05-29 (#23)
('2017-05-29'),('2017-05-29'),('2017-05-29'),
('2017-06-02'),('2017-06-02'),
('2017-06-03'),
('2017-06-04'),('2017-06-04'),('2017-06-04'),('2017-06-04'),
-- Week of 2017-06-05 (#24) - note no data actually on the 5th
('2017-06-08'),('2017-06-08'),('2017-06-08'),
('2017-06-11'),('2017-06-11'),
-- Week of 2017-06-12 (#25)
-- (note we have no data for this week)
-- Week of 2017-06-19 (#26)
('2017-06-19'),('2017-06-19'),('2017-06-19'),
('2017-06-20');
GO
To do this, you have to generate a table or CTE with the Monday dates and their week numbers (as shown in this answer, slightly modified for what we need to do below), then LEFT JOIN or OUTER APPLY that with your data grouped by week, using the week numbers:
SET DATEFORMAT ymd;
SET DATEFIRST 1;
DECLARE #startDate DATETIME = '2017-05-01';
DECLARE #endDate DATETIME = '2017-07-01';
;WITH Mondays AS (
SELECT #startDate AS D, DATEPART(WK, #startDate) AS W
UNION ALL
SELECT DATEADD(DAY, 7, D), DATEPART(WK, DATEADD(DAY, 7, D))
FROM Mondays m
WHERE DATEADD(DAY, 7, D) < #endDate
)
SELECT LEFT(CONVERT(NVARCHAR(MAX), Mondays.D, 120), 10) AS [Week Of], d.Count
FROM Mondays
OUTER APPLY (
SELECT COUNT(*) AS [Count]
FROM #temp
WHERE DATEPART(WK, D) = W
AND D >= #startDate
AND D < #endDate
) d
ORDER BY Mondays.D;
Two notes on that:
I'm assuming we can ensure that #startDate is a Monday, which is easily done outside the query or could be done with a simple loop in T-SQL if needed (backing up until WEEKPART(WEEKDAY, #startDate) is 1). (Or worst case we could generate all the dates and then filter them with WEEKPART(WEEKDAY, ...).)
I'm assuming the date range is always a year or less; otherwise, we'd have duplicated week numbers. If the date range could be longer than a year, combine the week number with the year everywhere we're just using a week number above (e.g., DATEPART(YEAR, D) * 100 + DATEPART(wk, D)).
You can use this.
SET DATEFORMAT ymd;
SET DATEFIRST 1; -- Monday is first day of week
DECLARE #startDate DATETIME = '2017-05-01';
DECLARE #endDate DATETIME = '2017-07-01';
;WITH OrgResult AS ( -- Grouping result with missing week. Answer of the first question
SELECT
DATEADD(DAY, 1 - DATEPART (WEEKDAY, D), D) [Week] -- Fist Day Of the Week
, COUNT(*) [Count]
FROM #temp
WHERE D BETWEEN #startDate AND #endDate
GROUP BY
DATEADD(DAY, 1 - DATEPART (WEEKDAY, D), D)
)
, Result AS -- Adds only missing weeks. Answer of the second question
(
SELECT * FROM OrgResult
UNION ALL
SELECT DATEADD( DAY, 7, R.[Week] ), 0 [Count]
FROM Result R
WHERE NOT EXISTS( SELECT * FROM OrgResult O WHERE [Week] = DATEADD( DAY, 7, R.[Week] ) )
AND DATEADD( DAY, 7, R.[Week] ) <= #endDate
)
SELECT * FROM Result
ORDER BY [Week]
Result:
Week Count
----------- -----------
2017-05-01 5
2017-05-08 19
2017-05-15 0
2017-05-22 8
2017-05-29 10
2017-06-05 5
2017-06-12 0
2017-06-19 4
2017-06-26 0
Here's another approach. I included this as it will generate less reads than the Recursive CTE Solution and will be a lot fast
WITH E(N) AS (SELECT 1 FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))x(x)),
iTally(N) AS
(
SELECT TOP (((DATEDIFF(day,#startdate, #endDate))/7)+1)
(ROW_NUMBER() OVER (ORDER BY (SELECT 1))-1)
FROM E a, E b, E c
)
SELECT WeekOf = DATEADD(WEEK,N,#startDate), [count] = COUNT(t.D)
FROM iTally i
LEFT JOIN #temp t ON t.D >= DATEADD(WEEK,N,#startDate) AND t.D < DATEADD(WEEK,N+1,#startDate)
GROUP BY DATEADD(WEEK,N,#startDate)
ORDER BY DATEADD(WEEK,N,#startDate); -- not required
Results:
WeekOf count
---------- -----------
2017-05-01 5
2017-05-08 19
2017-05-15 0
2017-05-22 8
2017-05-29 10
2017-06-05 5
2017-06-12 0
2017-06-19 4
2017-06-26 0

Return number of months and years from int?

This seems pretty easy, but I can't seem to figure it out.
If i have a number of months, like 61
That is 5 years and 1 month.
How can I write a select to give me the value of 0105?
Presumably you're going to have a query like this:
SELECT MONTHS
FROM MY_TABLE;
You could do some division and get the number of years:
SELECT MONTHS / 12 AS YEARS
FROM MY_TABLE;
Next, you need to get the number of months remaining from that. Modulo mathematical operations are used for this:
SELECT MONTHS % 12 AS MONTHS,
MONTHS / 12 AS YEARS
FROM MY_TABLE;
Now you'll need to format it:
SELECT FORMAT(MONTHS % 12 AS MONTHS, '00'),
FORMAT(MONTHS / 12 AS YEARS, '00')
FROM MY_TABLE;
Finally, concat the two results together:
SELECT CONCAT(FORMAT(MONTHS % 12 AS MONTHS, '00'),
FORMAT(MONTHS / 12 AS YEARS, '00')) AS RESULT
FROM MY_TABLE;
you need some case , cast , /12 and %12:
declare #value int
set #value=61
select
case when len(cast(#value%12 as int))=1 then '0'+cast(#value%12 as varchar(1)) else cast(cast(#value%12 as int)as varchar(2)) end+
case when len(cast(#value/12 as int))=1 then '0'+cast(#value/12 as varchar(1)) else cast(cast(#value/12 as int)as varchar(2)) end
output: 0105
Here You have two possible solutions:
DECLARE #n INT = 61
SELECT RIGHT(REPLACE(CONVERT(varchar(8),DATEADD(Month,#n,'2000-01-01'),1),'/',''),4)
SELECT RIGHT(CAST(10000+#n%12*100+#n/12 AS VARCHAR(5)),4)

Resources