How to get Exact Age basing on the Dates - sql

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

Related

T-SQL: calculate age then add character to result

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

SQL Server calculating age from varchar date of birth

The stored procedure is provided with #minAge and #maxAge varchar values.
The table contains DOB column as a varchar, now I need to select rows where where DOB (Age) falls between minimum and maximum age.
For instance #minAge = 12 and #maxAge = 40, we have to first convert the DOB string into Current Age and then only select the rows which fall between (inclusive) of 12 and 40.
I have the following code in mind but that produces errors
SELECT *
FROM tableName
WHERE
FLOOR((CAST(GetDate() AS INTEGER) - CAST(DOB AS INTEGER)) / 365.25) AS Age
BETWEEN CAST(#minage AS INT) AND CAST(#maxAge AS INT)
One way of calculating the age is this:
DATEDIFF(YY, CAST(dob AS DATETIME), GETDATE()) - CASE WHEN( (MONTH(CAST(dob AS DATETIME))*100 + DAY(CAST(dob AS DATETIME))) > (MONTH(GETDATE())*100 + DAY(GETDATE())) ) THEN 1 ELSE 0 END AS Age
where dob is the date of birth stored as varchar (in a way that can be cast to a datetime).
An example:
declare #people table(name varchar(20), dob varchar(10))
insert #people values ('adam', '1970-01-01')
insert #people values ('burt', '2002-01-13')
insert #people values ('dave', '1992-11-13')
insert #people values ('eric', '1973-11-13')
SELECT
name,
DATEDIFF(YY, CAST(dob AS DATETIME), GETDATE()) - CASE WHEN( (MONTH(dob)*100 + DAY(dob)) > (MONTH(GETDATE())*100 + DAY(GETDATE())) ) THEN 1 ELSE 0 END AS Age
FROM #people
WHERE DATEDIFF(YY, dob, GETDATE()) - CASE WHEN( (MONTH(dob)*100 + DAY(dob)) > (MONTH(GETDATE())*100 + DAY(GETDATE())) ) THEN 1 ELSE 0 END
BETWEEN 12 AND 40
This would be the result:
name Age
burt 12
dave 21
eric 40
This might not be the best way though, but it should work.

How to sum set of period times in specific format

If I have result set like that :
Work_hour(hh:mm)
10:24
12:59
06:28
where Work_hour is of type varchar
How to sum those hours and minutes with the same format ?
SELECT CAST(FLOOR(TMP1.MINS/60) AS VARCHAR) + ':' + CAST((TMP1.MINS % 60) AS VARCHAR) FROM (
SELECT SUM (CAST(LEFT(time_column, 2) AS INT) * 60 + CAST(RIGHT(time_column, 2) AS INT)) as MINS FROM table1
) AS TMP1
Where time_column is the column in the table and table1 is the name of the table. Example:
create table table1 (
time_column varchar(10)
);
insert into table1 (time_column) values ('12:20'), ('10:40'), ('15:50');
Results in: 38:50
Try this one -
Query:
DECLARE #temp TABLE
(
work_hour CHAR(5)
)
INSERT INTO #temp (work_hour)
VALUES
('10:24'),
('12:59'),
('06:28')
;WITH cte AS
(
SELECT mn = SUM(DATEDIFF(MINUTE, '19000101', CAST('19000101 ' + work_hour AS DATETIME)))
FROM #temp
)
SELECT CAST(FLOOR(mn / 60) AS VARCHAR(5)) + ':' + CAST(mn % 60 AS VARCHAR(2))
FROM cte
Output:
hm
--------
29:51
Update 2:
DECLARE #temp TABLE
(
transtime_out DATETIME
, transtime_in DATETIME
)
INSERT INTO #temp (transtime_out, transtime_in)
VALUES
('2013-05-19 16:40:53.000', '2013-05-19 08:58:07.000'),
('2013-05-19 16:40:53.000', '2013-05-19 08:58:07.000')
SELECT diff = LEFT(CONVERT(VARCHAR(10), CAST(SUM(CAST(a.transtime_out - a.transtime_in AS FLOAT)) AS DATETIME), 108), 5)
FROM #temp a

I still get a "Arithmetic overflow" when I filter on a cast datetime even if I use IsDate()

I have a data set that I need to filter a date that is stored as a string (changing the source column to a DateTime is NOT a option, this data is coming from a 3rd party source that I can not control).
One of the dates is malformed so if I do the following query I get one result
select ClientID, StartDate from boarding_appts where isdate(StartDate) = 0
ClientID StartDate
---------- --------------------
5160 5/6/210 12:00:00
If I do a cast(StartDate as datetime) I get "Arithmetic overflow error converting expression to data type datetime.", which I expected. and if I filter by IsDate alone everything works fine
select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1
ClientID StartDate age
---------- ----------------------- ----------
10207 2012-06-09 12:00:00.000 1
2843 2012-06-23 12:00:00.000 1
2843 2012-06-23 12:00:00.000 1
8292 2012-05-11 12:00:00.000 1
7935 2012-04-24 12:00:00.000 1
... (1000's of more rows) ...
Here is my problem:
I want to filter out records so only records a year old or newer show up, however no-matter how I attempt to perform the filter every one of these queries give me an arithmetic overflow error.
select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age
from boarding_appts
where isdate(StartDate) = 1
and datediff(year, cast(StartDate as dateTime), getdate()) < 1 --If you comment out this line it works fine
select *
from (select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1) as Filtered
where age < 1 --If you comment out this line it works fine
select *
from (select ClientID, cast(StartDate as dateTime) as StartDateCast from boarding_appts where isdate(StartDate) = 1) as Filtered
where datediff(year, StartDateCast, getdate()) < 1 --If you comment out this line it works fine
;with Filtered as
(select ClientID, cast(StartDate as dateTime) as StartDateCast from boarding_appts where isdate(StartDate) = 1)
select * from Filtered
where datediff(year, StartDateCast, getdate()) < 1 --If you comment out this line it works fine
;with Filtered as
(select ClientID, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age from boarding_appts where isdate(StartDate) = 1)
select * from Filtered
where age < 1 --If you comment out this line it works fine
Here is a test set of data on SQL Fiddle for you to try out any solutions on. I am out of ideas on how to fix this. The ONLY solution I could think of that worked was selecting in to a temporary table first then selecting it out
select ClientID, StartDate, cast(StartDate as dateTime) as StartDateCast, datediff(year, cast(StartDate as dateTime), getdate()) as age
into #t
from boarding_appts
where isdate(StartDate) = 1
select * from #t where age < 1 --Works.
SQL is a declarative language. The SQL optimizer is free to rearrange parts of the where clause as long as it retains its original meaning. So it can run datediff before isdate even if you specify isdate first. A subquery or CTE provides no sure relief, since that too can be rewritten.
The second suggestion from Aaron Bertrand in the comments:
WHERE CASE ISDATE(StartDate)
WHEN 1 THEN StartDate
ELSE '19000101'
END >= DATEADD(YEAR, -1, GETDATE());
Makes it unlikely that SQL Server will cast StartDate to a datetime when ISDATE = 0. That seems like the best solution.
I've marked this answer community wiki, if Aaran Bertrand posts an answer, accept that :)
SQL Server's DateTime has the domain 1753-01-01 00:00:00.000 ≤ x ≤ 9999-12-31 23:59:59.997. The year 210 CE is outside that domain. Hence the problem.
If you were using SQL Server 2008 or later, you could cast it to a DateTime2 datatype and you'd be golden (its domain is 0001-01-01 00:00:00.0000000 &le x ≤ 9999-12-31 23:59:59.9999999. But with SQL Server 2005, you're pretty much SOL.
This is really a problem of data cleaning. My inclination in cases like this is to load the 3rd party data into a staging table with each field as character strings. Then cleanse the data in place, replacing, for instance, invalid dates with NULL. Once cleansed, then do the necessary conversion work to move it to its final destination.
Another approach is to use pattern matching and do the date filtering without converting anything to datetime. ISO 8601 date/time values are character strings that have the laudable property of being (A) human-readable and (B) collating and comparing properly.
What I've done in the past is some analytical work to identify all the patterns in the datetime field by replacing decimal digits with a 'd' and then running group by to compute the counts of each different pattern found. Once you have that you can create some pattern tables to guide you. Something like these:
create table #datePattern
(
pattern varchar(64) not null primary key clustered ,
monPos int not null ,
monLen int not null ,
dayPos int not null ,
dayLen int not null ,
yearPos int not null ,
yearLen int not null ,
)
insert #datePattern values ( '[0-9]/[0-9]/[0-9] %' ,1,1,3,1,5,1)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9] %' ,1,1,3,1,5,2)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9] %' ,1,1,3,1,5,3)
insert #datePattern values ( '[0-9]/[0-9]/[0-9][0-9][0-9][0-9] %' ,1,1,3,1,5,4)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9] %' ,1,1,3,2,6,1)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9] %' ,1,1,3,2,6,2)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9] %' ,1,1,3,2,6,3)
insert #datePattern values ( '[0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %' ,1,1,3,2,6,4)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9] %' ,1,2,4,1,6,1)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9] %' ,1,2,4,1,6,2)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9] %' ,1,2,4,1,6,3)
insert #datePattern values ( '[0-9][0-9]/[0-9]/[0-9][0-9][0-9][0-9] %' ,1,2,4,1,6,4)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9] %' ,1,2,4,2,7,1)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9] %' ,1,2,4,2,7,2)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9] %' ,1,2,4,2,7,3)
insert #datePattern values ( '[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9] %' ,1,2,4,2,7,4)
create table #timePattern
(
pattern varchar(64) not null primary key clustered ,
hhPos int not null ,
hhLen int not null ,
mmPos int not null ,
mmLen int not null ,
ssPos int not null ,
ssLen int not null ,
)
insert #timePattern values ( '[0-9]:[0-9]:[0-9]' ,1,1,3,1,5,1 )
insert #timePattern values ( '[0-9]:[0-9]:[0-9][0-9]' ,1,1,3,1,5,2 )
insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9]' ,1,1,3,2,6,1 )
insert #timePattern values ( '[0-9]:[0-9][0-9]:[0-9][0-9]' ,1,1,3,2,6,2 )
insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9]' ,1,2,4,1,6,1 )
insert #timePattern values ( '[0-9][0-9]:[0-9]:[0-9][0-9]' ,1,2,4,1,6,2 )
insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9]' ,1,2,4,2,7,1 )
insert #timePattern values ( '[0-9][0-9]:[0-9][0-9]:[0-9][0-9]' ,1,2,4,2,7,2 )
You could combine these two tables into 1 but the number of combinations tends to explode things, though it greatly simplifies the query then.
Once you have that, the query is [fairly] easy, given that SQL is not exactly the world's best language choice for string processing:
---------------------------------------------------------------------
-- first, get your lower bound in ISO 8601 format yyyy-mm-dd hh:mm:ss
-- This will compare/collate properly
---------------------------------------------------------------------
declare #dtLowerBound varchar(255)
set #dtLowerBound = convert(varchar,dateadd(year,-1,current_timestamp),121)
-----------------------------------------------------------------
-- select rows with a start date more recent than the lower bound
-----------------------------------------------------------------
select isoDate = + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 )
+ case
when tm.pattern is not null then
' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 )
else ''
end
,*
from someTableWithBadData t
left join #datePattern dt on t.startDate like dt.pattern
left join #timePattern tm on ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) )
like tm.pattern
where #lowBound <= + right( '0000' + substring( t.startDate , coalesce(dt.yearPos,1) , coalesce(dt.YearLen,0) ) , 4 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.monPos,1) , coalesce(dt.MonLen,0) ) , 2 )
+ '-' + right( '00' + substring( t.startDate , coalesce(dt.dayPos,1) , coalesce(dt.dayLen,0) ) , 2 )
+ case
when tm.pattern is not null then
' ' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.hhPos , tm.hhLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.mmPos , tm.mmLen ) , 2 )
+ ':' + right( '00' + substring(ltrim(rtrim( substring(t.startDate,dt.YearPos+dt.YearLen,1+len(t.startDate)-(dt.YearPos+dt.YearLen) ) ) ), tm.ssPos , tm.ssLen ) , 2 )
else ''
end
Like I said, SQL not the best choice for munging strings.
This should get you ... 90% there. Experience tells me that you'll still find more bad dates: months less than 1 or greater than 12 , days less than 1 or greater than 31, or days out of range for that month (nothing like February 31st to make the computer whine), etc. Old cobol programs in particular, loved to use a field of all 9s to indicate missing data, for instance (though that is an easy case to deal with).
My preferred technique is to write a perl script to scrub the data and bulk load it into SQL Server, using perl's BCP facilities. That's exactly the sort of problem space perl is designed for.

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