T-SQL: calculate age then add character to result - sql

Been stuck on this one for a while now. Let's say I have a Client table like the one here:
Name BirthDayNum BirthMonthNum BirthYearNum
--------------------------------------------------
John 23 12 1965
Jane 4 9 1975
Joe 6 3 1953
Currently I am calculating the age using this syntax: (sorry if it is hard to read)
DATEDIFF(year, CONVERT(datetime, CAST(client.BirthMonthNum AS varchar(2))
+ '-' + CAST(client.BirthDayNum AS varchar(2))
+ '-' + CAST(client.BirthYearNum AS varchar(4)), 101), GETDATE())
- (CASE WHEN dateadd(YY, DATEDIFF(year, CONVERT(datetime, CAST(client.BirthMonthNum AS varchar(2))
+ '-' + CAST(client.BirthDayNum AS varchar(2))
+ '-' + CAST(client.BirthYearNum AS varchar(4)), 101), GETDATE()),
CONVERT(datetime, CAST(client.BirthMonthNum AS varchar(2))
+ '-' + CAST(client.BirthDayNum AS varchar(2))
+ '-' + CAST(client.BirthYearNum AS varchar(4)), 101)) > getdate() THEN 1 ELSE 0 END) AS 'Client Age'
This will give me the age in years. Of course if I want months, I just change the DATEDIFF(year to month. So, what I am trying to do now is this.
Continue to calculate the age, but instead of returning either years or months, I would like to return the age in years and months, but also, concat a 'y' and 'm' within the value as well. Ex. 41y 11m for Jane above.
So basically I am trying to figure out how to add a char to the return value, as well as calculate the remaining months beyond the year calculation.
Any help would be greatly appreciated!

Tired of twisting myself into knots with date calculations, I created a Table-Valued-Function to calculate elapsed time in Years, Months, Days, Hours, Minutes, and Seconds.
Example
Declare #YourTable table (Name varchar(50),BirthDayNum int, BirthMonthNum int, BirthYearNum int)
Insert Into #YourTable values
('John', 23, 12, 1965),
('Jane', 4, 9, 1975),
('Joe', 6, 3, 1953)
Select A.Name
,B.*
,Age = concat(C.Years,'y ',C.Months,'m')
From #YourTable A
Cross Apply (Select DOB = DateFromParts(A.BirthYearNum,A.BirthMonthNum,A.BirthDayNum)) B
Cross Apply [dbo].[udf-Date-Elapsed](B.DOB,GetDate()) C
Returns
Name DOB Age
John 1965-12-23 51y 3m
Jane 1975-09-04 41y 6m
Joe 1953-03-06 64y 0m
The UDF - May look like overkill, but it is very performant
CREATE FUNCTION [dbo].[udf-Date-Elapsed] (#D1 DateTime,#D2 DateTime)
Returns Table
Return (
with cteBN(N) as (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cteRN(R) as (Select Row_Number() Over (Order By (Select NULL))-1 From cteBN a,cteBN b,cteBN c),
cteYY(N,D) as (Select Max(R),Max(DateAdd(YY,R,#D1))From cteRN R Where DateAdd(YY,R,#D1)<=#D2),
cteMM(N,D) as (Select Max(R),Max(DateAdd(MM,R,D)) From (Select Top 12 R From cteRN Order By 1) R, cteYY P Where DateAdd(MM,R,D)<=#D2),
cteDD(N,D) as (Select Max(R),Max(DateAdd(DD,R,D)) From (Select Top 31 R From cteRN Order By 1) R, cteMM P Where DateAdd(DD,R,D)<=#D2),
cteHH(N,D) as (Select Max(R),Max(DateAdd(HH,R,D)) From (Select Top 24 R From cteRN Order By 1) R, cteDD P Where DateAdd(HH,R,D)<=#D2),
cteMI(N,D) as (Select Max(R),Max(DateAdd(MI,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteHH P Where DateAdd(MI,R,D)<=#D2),
cteSS(N,D) as (Select Max(R),Max(DateAdd(SS,R,D)) From (Select Top 60 R From cteRN Order By 1) R, cteMI P Where DateAdd(SS,R,D)<=#D2)
Select [Years] = cteYY.N
,[Months] = cteMM.N
,[Days] = cteDD.N
,[Hours] = cteHH.N
,[Minutes] = cteMI.N
,[Seconds] = cteSS.N
From cteYY,cteMM,cteDD,cteHH,cteMI,cteSS
)
--Max 1000 years
--Select * from [dbo].[udf-Date-Elapsed] ('1991-09-12 21:00:00.000',GetDate())
Just to Illustrate
The TVF without any secondary string manipulation would return
Select A.Name
,B.*
From #YourTable A
Cross Apply [dbo].[udf-Date-Elapsed](DateFromParts(A.BirthYearNum,A.BirthMonthNum,A.BirthDayNum),GetDate()) B
EDIT - READ ONLY VERSION
Select A.Name
,B.*
,Age = concat(DateDiff(MONTH,B.DOB,GetDate())/12,'y ',DateDiff(MONTH,B.DOB,GetDate()) % 12,'m')
From #YourTable A
Cross Apply (Select DOB = DateFromParts(A.BirthYearNum,A.BirthMonthNum,A.BirthDayNum)) B

Yes it is easier to save as DOB.. But one simple method
select concat( floor(datediff(year, datefromparts(birthyearnum,birthmonthnum,birthdaynum), getdate()))-1, 'y ', datediff(month, datefromparts(birthyearnum,birthmonthnum,birthdaynum), getdate())%12, 'm')
from #yourDates
How age for 1965 is 41y?
Input table:
create table #yourdates(Name varchar(10), BirthdayNum int, BirthMonthNum int, BirthYearNum int)
insert into #yourdates
(Name, BirthdayNum, BirthMonthNum, BirthYearNum) values
('John', 23 , 12 , 1965 )
,('Jane', 4 , 9 , 1975 )
,('Joe ', 6 , 3 , 1953 )

If you are on 2008 or lesser and can't use datefromparts...
declare #table table ([Name] varchar(4), BirthDayNum int, BirthMonthNum int, BirthYearNum int)
insert into #table
values
('John',23,12,1965),
('Jane',4,9,1975),
('Day',30,3,1990)
;with cte as(
select
[Name],
cast(BirthYearNum as varchar(4)) + '/' + cast(BirthMonthNum as varchar(2)) + '/' + cast(BirthDayNum as varchar(2)) as DOB
from
#table)
select
[Name]
,DOB
,datediff(year,DOB,GETDATE()) as Years
,datediff(month,DOB,GETDATE()) %12 as Months
,rtrim(cast(datediff(year,DOB,GETDATE()) as char(2))) + 'y ' + rtrim(cast(datediff(month,DOB,GETDATE()) %12 as char(2))) + 'm' as Age
from cte

Related

Years separated by comma CTE in SQL Server

I have a field called StartYear, the value of my field is 2000. I have another field called EndYear, the value of my field is 2005.
I want to create a field called YearsInTheProgram that has the values 2005,2004, 2003,2002,2001,2000.
Each of my rows have different values, so in essence I would like this field to have the difference of my fields separated my commas.
I was able to find something that would work, but this would give me a value in different rows. However, I want all of them in one row.
with CTE as
(
select datepart(year, '2006-12-25') as yr
union all
select yr + 1
from CTE
where yr < datepart(year, '2013-11-14')
)
select yr
from CTE
declare #tmp varchar(250)
SET #tmp = ''
;with CTE as
(
select datepart(year, '2006-12-25') as yr
union all
select yr + 1
from CTE
where yr < datepart(year, '2013-11-14')
)
select #tmp = #tmp + convert(varchar(500), yr) + ', ' from CTE
select SUBSTRING(#tmp, 0, LEN(#tmp)) as yr
You can bulid up your string of years using this CTE.
DECLARE #STARTYEAR varchar(10) = '2006-12-25'
DECLARE #ENDYEAR varchar(10) = '2013-11-14'
;with CTE as
(
select datepart(year, #STARTYEAR) as yr, CAST(datepart(year, #STARTYEAR) AS VARCHAR(MAX)) as c
union all
select yr + 1 as yr, CAST(concat(c, ',', yr+1) AS VARCHAR(MAX)) as c
from CTE
where yr < datepart(year, #ENDYEAR)
)
select yr, c
from CTE
where (yr = datepart(year, #ENDYEAR))
And here it is with StartYear and EndYear taken from DB table.
create table #yr (id int, start varchar(20), stop varchar(20))
insert into #yr values(1,'2005-01-01','2010-12-10'), (2,'2008-01-01','2011-12-10'), (3,'2007-01-01','2013-12-10'), (4,'2009-01-01','2012-10-10')
;with CTE as
(
select start as start, datepart(year, start) as yr, CAST(datepart(year, start) AS VARCHAR(MAX)) as c from #yr
union all
select CTE.start as start, yr + 1 as yr, CAST(concat(c, ',', yr+1) AS VARCHAR(MAX)) as c
from CTE join #yr on #yr.start = CTE.start
where yr < datepart(year, stop)
)
select id, #yr.start, #yr.stop, c
from CTE join #yr on #yr.start = CTE.start
where (yr = datepart(year, stop))
With following result
4 2009-01-01 2012-10-10 2009,2010,2011,2012
3 2007-01-01 2013-12-10 2007,2008,2009,2010,2011,2012,2013
2 2008-01-01 2011-12-10 2008,2009,2010,2011
1 2005-01-01 2010-12-10 2005,2006,2007,2008,2009,2010

Need monthly report where I need summation of Qty for each day

I have one table STOCK_REGISTER where I have columns Id, ItmId, Qty, CreatedDate. And there are daily transaction data like item inward, outward and all.
Now I am developing one Monthly Report where I want to display Day wise summation of item qty for whole month. I have written following query to do so.
SELECT CONVERT(Date,CreatedDate) AS CreatedDate,ItmId, SUM(Qty)
FROM STOCK_REGISTER
GROUP BY CONVERT(Date,CreatedDate),ItmId
This query returns correct data but if I am asking for April-2017 data and STOCK_REGISTER don't have any record on 5th April then it's not displaying 5th April at all where I need 0 value in qty for that item in 5th April.
Can you help me out to get this type of data?
Edit :
I have created query which gives all days of any particular month and have applied left join with STOCK_REGISTER, but still not solving my issue.
declare #month int, #year int
set #month = 4
set #year = 2017
SELECT CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number AS STKFG_CREATED_DATE,
B.STKFG_ITM_ID,
0 AS OPENING_STOCK,
SUM(STKFG_QTY)
FROM master..spt_values A
LEFT JOIN STOCK_REGISTER_FG B
ON CONVERT(DATE,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number) = CONVERT(DATE,B.STKFG_CREATED_DATE)
AND C.ITM_ID = B.STKFG_ITM_ID
WHERE type = 'P'
AND (CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number ) <
DATEADD(mm,1,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) )
AND STKFG_TRAT_ID = 6
GROUP BY A.Number, B.STKFG_ITM_ID
ORDER BY B.STKFG_ITM_ID,CAST(CAST(#year AS VARCHAR) + '-' + CAST(#Month AS VARCHAR) + '-01' AS DATETIME) + A.Number
Thank you for your comments.
I have found one solution which works fine for me.
Here #TEMP_TABLE contains all dates.
SELECT A.CREATED_DATE,B.ITM_NAME,ISNULL(C.STKFG_QTY,0)
FROM #TEMP_TABLE A
CROSS APPLY ITEM_MASTER B
LEFT JOIN ( SELECT CONVERT(DATE,STKFG_CREATED_DATE) AS STKFG_CREATED_DATE, STKFG_ITM_ID, SUM(STKFG_QTY) AS STKFG_QTY
FROM STOCK_REGISTER_FG GROUP BY CONVERT(DATE,STKFG_CREATED_DATE),STKFG_ITM_ID) C
ON CONVERT(DATE,A.CREATED_DATE) = CONVERT(DATE,C.STKFG_CREATED_DATE)
AND B.ITM_ID = C.STKFG_ITM_ID
ORDER BY B.ITM_NAME,A.CREATED_DATE
Try below script. Here #tblData is your actual table name.
declare #tblData table
(id int,datecolumn date)
INSERT INTO #tblData
(id,datecolumn)
SELECT 1,'2010-01-01' UNION ALL
SELECT 3,'2010-01-01' UNION ALL
SELECT 1,'2010-01-03' UNION ALL
SELECT 2,'2010-01-04' UNION ALL
SELECT 1,'2010-01-05' UNION ALL
SELECT 3,'2010-01-06' UNION ALL
SELECT 1,'2010-01-10' UNION ALL
SELECT 1,'2010-01-10' UNION ALL
SELECT 2,'2010-01-11' UNION ALL
SELECT 1,'2010-01-11' UNION ALL
SELECT 2,'2010-01-11' UNION ALL
SELECT 1,'2010-01-12' UNION ALL
SELECT 3,'2010-01-13'
DECLARE #MaxDate DATE,
#MinDate DATE,
#iDate DATE
DECLARE #DateSequence TABLE(
DATE1 DATE
)
SELECT #MaxDate = Convert(DATE,Max(datecolumn)),
#MinDate = Convert(DATE,Min(datecolumn))
FROM #tblData -- Put your month condition
SET #iDate = #MinDate
WHILE (#iDate <= #MaxDate)
BEGIN
INSERT #DateSequence
SELECT #iDate
SET #iDate = Convert(DATE,Dateadd(DAY,1,#iDate))
END
SELECT a.DATE1 ,isnull(sum(b.id),0) as total
FROM #DateSequence a left join #tblData b on a.DATE1=b.datecolumn
group by a.DATE1
Thanks,

How to get Exact Age basing on the Dates

Sample Data :
DECLARE #T Table (ID INT,Name VARCHAR(10),DOB DATE)
INSERT INTO #T (ID,Name,DOB) VALUES (1,'Mohan','1937-12-30')
INSERT INTO #T (ID,Name,DOB) VALUES (2,'Raj','1937-12-25')
INSERT INTO #T (ID,Name,DOB) VALUES (5,'Manny','1937-01-30')
INSERT INTO #T (ID,Name,DOB) VALUES (3,'kamal','1938-12-12')
INSERT INTO #T (ID,Name,DOB) VALUES (4,'Raj','1937-05-12')
My Query :
Select
cast((DATEDIFF(m, DOB, GETDATE())/12) as varchar) + ' Y & ' +
cast((DATEDIFF(m, DOB, GETDATE())%12) as varchar) + ' M & ' +
cast((DATEDIFF(D, DOB, GETDATE())%12) as varchar) + ' d' as Age from #T
This will give result in Years, months and Days .
But My question how to get data only who are reaching 79 years of age in Coming 45 days .
I'm struck can you please suggest me
People who are reaching 79 years of age in the next 45 days:
SELECT * from #T where
DOB > DATEADD(year,-79,GETDATE()) and
DOB < DATEADD(year,-79,DATEADD(day,45,GETDATE()))
(Adjust for < vs <= and possible employ additional DATEADD/DATEDIFFs if you wish to round GETDATE() down to midnight, to suit your exact requirements)
You can do this:
Select
cast((DATEDIFF(m, DOB, GETDATE())/12) as varchar) + ' Y & ' +
cast((DATEDIFF(m, DOB, GETDATE())%12) as varchar) + ' M & ' +
cast((DATEDIFF(D, DOB, GETDATE())%12) as varchar) + ' d'
from #T
WHERE DATEDIFF(m, DOB, GETDATE())/12 < 79 -- we need only people how are not already 79 years old
AND DATEDIFF(m, DATEADD(DAY,-45, DOB), GETDATE())/12 >= 79 -- we are making the DOB 45 days samller
SELECT * from #T
WHERE DATEDIFF(dd,GETDATE(),DATEADD(yy,79,dob))<45
AND
DATEDIFF(dd,GETDATE(),DATEADD(yy,79,dob))>0
What about this?
...
WHERE (DATEDIFF(m, DOB, GETDATE())/12) = 78 AND
(DATEDIFF(m, DOB, GETDATE())%12) = 11 AND
(DATEDIFF(D, DOB, GETDATE())%12) < 15
Try This :
;
WITH _CTE(_Id,_ConDate ,_Month , _Date ,Name , DOB,_DOBMonth , _DOBDate) AS
(SELECT ID , DATEADD(DAY,45,GETDATE()),DATEPART(M,DATEADD(DAY,45,GETDATE())),
DATEPART(DAY,DATEADD(DAY,45,GETDATE())),Name,DOB,DATEPART(MONTH,DOB),DATEPART(DAY,DOB)FROM #T WHERE (DATEDIFF(m, DOB, GETDATE())/12) = 78 )SELECT _Id Id,Name , DOB FROM _CTE WHERE _Month = _DOBMonth AND _Date = _DOBDate

How to get sum of time field in SQL server 2008

I am facing a problem in finding the sum of values stored in a column,
I have a table like this:
gs_cycle_no | from_time | to_time | total_hours(varchar) ...
GSC-334/2012 | 13:00 | 7:00 | 42:00
GSC-334/2012 | 8:30 | 3:45 | 6:00
.
.
.
What i need to find is the Sum(total_hours) group by gs_cycle_no.
But the Sum method will not work on the varchar column and also i cant convert it to decimal due to its format,
How can i find the sum of total_hours column, based on gs_cycle_no?
if you have no minutes and only hours, then you can do something like:
select
cast(sum(cast(replace(total_hours, ':', '') as int) / 100) as nvarchar(max)) + ':00'
from Table1
group by gs_cycle_no
if you don't, try this:
with cte as
(
select
gs_cycle_no,
sum(cast(left(total_hours, len(total_hours) - 3) as int)) as h,
sum(cast(right(total_hours, 2) as int)) as m
from Table1
group by gs_cycle_no
)
select
gs_cycle_no,
cast(h + m / 60 as nvarchar(max)) + ':' +
right('00' + cast(m % 60 as nvarchar(max)), 2)
from cte
sql fiddle demo
This will work:
;with times as (
select gs_cycle_no = 'GSC-334/2012', total_hours = '8:35'
union all SELECT gs_cycle_no = 'GSC-334/2012', '5:00'
union all SELECT gs_cycle_no = 'GSC-334/2012', '16:50'
union all SELECT gs_cycle_no = 'GSC-334/2012', '42:00'
union all SELECT gs_cycle_no = 'GSC-335/2012', '0:00'
union all SELECT gs_cycle_no = 'GSC-335/2012', '175:52'
union all SELECT gs_cycle_no = 'GSC-335/2012', '12:25')
SELECT
gs_cycle_no,
hrs = sum(mins) / 60 + sum(hrs),
mins = sum(mins) % 60
FROM
TIMES
cross apply(
select c = charindex(':', total_hours)
) idx
cross apply(
select
hrs = cast(substring(total_hours, 1, c - 1) as int),
mins = cast(substring(total_hours, c + 1, len(total_hours)) as int)
) ext
group by gs_cycle_no
order by gs_cycle_no
This query finds sum in minutes:
SQLFiddle demo
select gs_cycle_no,
SUM(
CAST(
ISNULL(
substring(total_hours,1,CHARINDEX(':',total_hours)-1)
,'0') as INT) * 60
+
CAST(
ISNULL(
substring(total_hours,CHARINDEX(':',total_hours)+1,100)
,'0') as INT)
)
from t
group by gs_cycle_no
Here is solution where I break varchar into two small pieces, hours and minutes, and then make minutes from them, and at last, SUM them:
SELECT
gs_cycle_no,
CAST(SUM(
SUBSTRING(total_hours,0 ,CHARINDEX(':', total_hours)) * 60 +
SUBSTRING(total_hours, CHARINDEX(':', total_hours) + 1, LEN(total_hours))) / 60 AS VARCHAR) + ':' +
CAST(SUM(
SUBSTRING(total_hours,0 ,CHARINDEX(':', total_hours)) * 60 +
SUBSTRING(total_hours, CHARINDEX(':', total_hours) + 1, LEN(total_hours))) % 60 AS VARCHAR)
FROM Table1
GROUP BY gs_cycle_no

sql : calculate the total birthday moth based on month

I want to code a query to calculate the total birthday moth based on month . Here is the sample data . Two person's bithday are April, 1980 .
How to write this query ?
John 02/21/1980
Peter 02/22/1980
Lucy 04/21/1980
------ result -----
01/1980 0
02/1980 2
03/1980 0
04/1980 1
05/1980 0
.....
You can loop for every months like this:
DECLARE #month INT
DECLARE #year INT
DECLARE #result TABLE (MonthYear varchar(7),BirthdaysCount INT)
SET #month = 1
SET #year = 1980
WHILE(#month < 13)
BEGIN
INSERT INTO #result
SELECT (CAST(#month as VARCHAR) + '/' + CAST(#year as VARCHAR)),
COUNT(*)
FROM test
WHERE MONTH(birth) = #month AND YEAR(birth) = #year
SET #month = #month + 1
END
select * from #result
See the fiddle: http://sqlfiddle.com/#!3/20692/2
In MySQL you would do this:
select concat(month(dob), '/', year(dob)) monthYear, count(*) cnt from t
group by monthYear
order by year(dob), month(dob)
However, in order to get the "missing dates" you'll have to generate data, because 01/1980 is not in any table, as far as I can see. Check this answer to see how.
how about this for sql-server
create table #temp
(
name varchar(50),
DOB datetime
)
insert into #temp values ('john', '2/21/1980')
insert into #temp values ('peter', '2/22/1980')
insert into #temp values ('lucy', '4/21/1980')
select convert(varchar(2), MONTH(DOB)) + '/' + Convert(varchar(4), YEAR(DOB)) as [MM/YYYY]
, count(*) as TotalCount
from #temp
group by convert(varchar(2), MONTH(DOB)) + '/' + Convert(varchar(4), YEAR(DOB))
drop table #temp
EDIT - This will get you all records for the dates included in the dates in the table. It will use the Min/Max Date in the table to get the date range, you then use this range to get the count of the birthdays in each month:
create table #temp
(
name varchar(50),
DOB datetime
)
insert into #temp values ('john', '2/21/1980')
insert into #temp values ('peter', '2/22/1980')
insert into #temp values ('lucy', '4/21/1980')
;with cte as
(
select min(DOB) as MinDate, max(DOB) as MaxDate
from #temp
union all
SELECT dateadd(mm, 1, t.MinDate), t.MaxDate
from cte t
where dateadd(mm, 1, t.MinDate) <= t.MaxDate
)
select convert(varchar(2), MONTH(c.MinDate)) + '/' + Convert(varchar(4), YEAR(c.MinDate))
, IsNull(t.TotalCount, 0) as TotalCount
from cte c
LEFT JOIN
(
SELECT convert(varchar(2), MONTH(DOB)) + '/' + Convert(varchar(4), YEAR(DOB)) as [MM/YYYY]
, count(*) as TotalCount
FROM #temp
group by convert(varchar(2), MONTH(DOB)) + '/' + Convert(varchar(4), YEAR(DOB))
) t
on convert(varchar(2), MONTH(C.MinDate)) + '/' + Convert(varchar(4), YEAR(C.MinDate))
= t.[MM/YYYY]
drop table #temp