SQL Server calculating age from varchar date of birth - sql

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.

Related

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

SQL Server - find people between date range excluding the year

I have a table EMPLOYEE containing Employee info as mentioned below:
ID NAME DOB
1 ABC 1974-01-01
2 BDS 1984-12-31
3 QWE 1959-05-27
and so on
I want to list all the employees whose DOB is in the given range.
select * from EMPLOYEE where DOB BETWEEN '1970-01-01' AND '1980-02-27'
I have a filter condition to 'include year in date comparison', which when selected 'NO', the employee DOB day and DOB month
only should be considered for comparison. and not the year.
For example: If I enter the date range as '1970-01-01' and '1980-02-27' and the filter is selected as 'NO' then it should search for only those employees
whose DOB is greater than equal to JAN-01 and less than equal to FEB-27.
When selected 'Yes', it is simply date range as mentioned in above query.
Here is what I have tried so far:
select * from EMPLOYEE where DOB BETWEEN '1970-01-01' AND '1980-02-27'
AND MONTH(DOB) >= CASE WHEN 'NO'='NO' THEN MONTH('1970-01-01')
ELSE MONTH(DOB) END
AND MONTH(DOB) <= CASE WHEN 'NO'='NO' THEN MONTH('1980-02-27')
ELSE MONTH(DOB) END
AND DAY(DOB) >= CASE WHEN 'NO'='NO' THEN DAY('1970-01-01')
ELSE DAY(DOB) END
AND DAY(DOB) <= CASE WHEN 'NO'='NO' THEN DAY('1980-02-27')
ELSE DAY(DOB) END
It works when I pass the date range where the FROM date has smaller number month than the TO date month.
For example:
It doesnt work when I pass the date range as '1970-12-01' to '1980-01-31'.
It should list the employees whose DOB is in DEC and JAN month.
Need help please.
Sample Data;
DECLARE #Date_From date; SET #Date_From = '1970-12-01'
DECLARE #Date_To date; SET #Date_To = '1974-01-31'
DECLARE #IncludeYear bit; SET #IncludeYear = 0
CREATE TABLE #Employee (ID int, Name varchar(10), DOB date)
INSERT INTO #Employee (ID, Name, DOB)
VALUES
(1,'ABC','1974-01-01')
,(2,'BDS','1984-12-31')
,(3,'QWE','1959-05-27')
This is the query I've made. Tried to cover for every eventuality.
SELECT
e.ID
,e.Name
,e.DOB
FROM #Employee e
WHERE
(
#IncludeYear = 1
AND
DOB BETWEEN #Date_From AND #Date_To
)
OR
(
#IncludeYear = 0
AND
(
(
DATEPART(DAYOFYEAR, #Date_From) = DATEPART(DAYOFYEAR, #Date_To)
AND
DATEPART(DAYOFYEAR, DOB) = DATEPART(DAYOFYEAR, #Date_To)
)
OR
(
DATEPART(DAYOFYEAR, #Date_From) < DATEPART(DAYOFYEAR, #Date_To)
AND
DATEPART(DAYOFYEAR, DOB) BETWEEN DATEPART(DAYOFYEAR, #Date_From) AND DATEPART(DAYOFYEAR, #Date_To)
)
OR
(
DATEPART(DAYOFYEAR, #Date_From) > DATEPART(DAYOFYEAR, #Date_To)
AND
(
DATEPART(DAYOFYEAR, DOB) > DATEPART(DAYOFYEAR, #Date_From)
OR
DATEPART(DAYOFYEAR, DOB) < DATEPART(DAYOFYEAR, #Date_To)
)
)
)
)
First part of the where clause checks if the #date_from and #date_to
are the same date, then only returns these.
Second part checks if the day of year for #date_from comes before
#date_to. If it does then return everything between these days of the
year.
Final part checks if the day of year for #date_to comes before
#date_from then it gets everything with the day of year after
#date_from or before #date_to
The results for this one come out as this;
ID Name DOB
1 ABC 1974-01-01
2 BDS 1984-12-31
DECLARE #includeYear bit = 0, -- if 0 - we don't include year, 1 - include
#dateFrom date ='1970-12-01',
#dateTo date ='1980-05-30'
IF #includeYear = 1
BEGIN
SELECT e.*
FROM EMPLOYEE e
INNER JOIN (SELECT #dateFrom as dF, #dateTo as dT) d
ON e.DOB BETWEEN dF AND dT
END
ELSE
BEGIN
SELECT e.*
FROM EMPLOYEE e
INNER JOIN (SELECT #dateFrom as dF, #dateTo as dT) d
ON e.DOB BETWEEN
(CASE WHEN MONTH(dF) > MONTH(dT)
THEN DATEADD(year,YEAR(e.DOB)-YEAR(d.dF)-1,dF)
ELSE DATEADD(year,YEAR(e.DOB)-YEAR(d.dF),dF) END)
AND DATEADD(year,YEAR(e.DOB)-YEAR(d.dT),dT)
OR e.DOB BETWEEN DATEADD(year,YEAR(e.DOB)-YEAR(d.dF),dF) AND
(CASE WHEN MONTH(dF) > MONTH(dT)
THEN DATEADD(year,YEAR(e.DOB)-YEAR(d.dT)+1,dT)
ELSE DATEADD(year,YEAR(e.DOB)-YEAR(d.dT),dT) END)
END
For
dateFrom dateTo
1970-12-01 1980-01-30
Output:
ID NAME DOB
1 ABC 1974-01-01
2 BDS 1984-12-31
For
dateFrom dateTo
1970-05-01 1980-06-30
Output:
ID NAME DOB
3 QWE 1959-05-27
For
dateFrom dateTo
1970-05-01 1980-05-30
Output:
ID NAME DOB
3 QWE 1959-05-27
etc
Try the function DATEPART(dayofyear, date)
In case the day-of-year of the first date is smaller than the day-of-year of the second date, then the day-of-year of the DOB should be between the specified days-of-year.
Otherwise, the day-of-year of the DOB should be either smaller than the day-of-year of the second date or greater than the day-of-year of the first date.
I hope I expressed myself well.
Rather than working case-by-case and disassembling and reassembling parts of dates, I've tried to make life easier:
declare #t table (ID int not null, Name varchar(17) not null, DOB date not null)
insert into #t(ID,NAME,DOB) values
(1,'ABC','19740101'),
(2,'BDS','19841231'),
(3,'QWE','19590527')
declare #Start date
declare #End date
declare #IncludeYear bit
select #Start='19701201',#End='19800131',#IncludeYear=0
;With Normalized as (
select
ID,
Name,
CASE WHEN #IncludeYear=1 THEN DOB
ELSE DATEADD(year,DATEDIFF(year,DOB,'20000101'),DOB)
END as DOB,
CASE WHEN #IncludeYear=1 THEN #Start
ELSE DATEADD(year,DATEDIFF(year,#Start,'20000101'),#Start)
END as StartRange,
CASE WHEN #IncludeYear=1 THEN #End
ELSE DATEADD(year,DATEDIFF(year,#End,'20000101'),#End)
END as EndRange
from
#t
)
select * from Normalized
where
DOB between StartRange and EndRange or
(
#IncludeYear=0 and StartRange>EndRange and
(
DOB < EndRange or DOB > StartRange
)
)
We create the Normalized CTE that, does nothing if #IncludeYear is 1 or, if it is zero, it resets all dates so that they occur in 2000 (arbitrarily selected).
We then do the straightforward query based on the CTE. The one circumstance where it won't correctly match is when you have your range defined over a year-end transition and we don't care about years - which we can check specifically for and cater for within the end of the WHERE clause.
Results:
ID Name DOB StartRange EndRange
----------- ----------------- ---------- ---------- ----------
1 ABC 2000-01-01 2000-12-01 2000-01-31
2 BDS 2000-12-31 2000-12-01 2000-01-31
Results with #Start='19700101',#End='19800227',#IncludeYear=1:
ID Name DOB StartRange EndRange
----------- ----------------- ---------- ---------- ----------
1 ABC 1974-01-01 1970-01-01 1980-02-27
Here is another solution
DECLARE #employee table(EmployeeID varchar(10), DOB date);
INSERT INTO #employee(EmployeeID, DOB)
VALUES('0001', '01-Dec-1990'),
('0002', '06-Jan-1993'),
('0003', '04-Mar-1987'),
('0004', '12-Feb-1996');
DECLARE #dateStart date = '01-Jan-1990';
DECLARE #dateEnd date = '27-Feb-1997';
DECLARE #includeYear bit = 0;
If #includeYear = 0
Begin
SET #dateStart = CAST(('2000-' + CAST(MONTH(#dateStart) AS varchar(10)) + '-' + CAST(DAY(#dateStart) as varchar(10))) AS date);
SET #dateEnd = CAST(('2000-' + CAST(MONTH(#dateEnd) AS varchar(10)) + '-' + CAST(DAY(#dateEnd) as varchar(10))) AS date);
End
If #includeYear = 1
Begin
SELECT *
FROM #employee
WHERE DOB BETWEEN #dateStart AND #dateEnd
End
Else
Begin
SELECT *
FROM #employee
WHERE CAST(('2000-' + CAST(MONTH(DOB) AS varchar(10)) + '-' + CAST(DAY(DOB) as varchar(10))) AS date) BETWEEN #dateStart AND #dateEnd
End
As you can see we are just making the year part of the query a constant if you don't want to include year. This query seems to be a bit slower but if you add another computed column in your table, where you save the date with a constant year then you just need to put where criteria on that particular column.
Try this.
declare #flag varchar(3) ='NO';
declare #sd date ='1980-02-27';
declare #ed date ='1970-01-01';
select tt.*
from (select sd = month(#sd)*100 + day(#sd),
ed = month(#ed)*100 + day(#ed)
) prm
cross join
-- test data, place real table here
(
values
(1,'ABC', cast('1974-01-05' as date)),
(2,'BDS','1984-12-31'),
(3,'QWE','1959-05-27')
) tt(ID,NAME, DOB)
cross apply (select md = month(DOB)*100 + day(DOB)) tx
where #flag ='YES' and DOB between #sd and #ed
or #flag ='NO' and (prm.sd<=prm.ed and tx.md between prm.sd and prm.ed
or prm.sd>prm.ed and (tx.md <= prm.ed or tx.md >= prm.sd));
Always use a SARG in your predicate. Any answer that fails to do this only results in lost performance and your DBA getting upset.
What you want is two different queries to run depending on the answer to the Procedure. Since this is a proc that likely runs a lot, store the answers into a variable in your PROC and run any adjusting code from there. Not only will this make your code more robust by flushing out errors beforehand, but SQL Server has a better chance of guessing the variables to use with your indexes.
The following PROC will work. Feel free to use part or all of it:
CREATE TABLE #table_E (ID INT, Name VARCHAR(3), DOB DATE)
INSERT INTO #table_E (ID , Name, DOB)
VALUES (1, 'ABC', '1997-01-02' )
, (2, 'BDS', '1984-12-31' )
, (3, 'QWE', '1993-03-22' )
GO
CREATE PROC USP_EmpCompare (#Date_1 DATE, #Date_2 DATE, #Compare_Year VARCHAR(3))
AS
BEGIN
DECLARE #MONTH_1 INT
, #Month_2 INT
, #Day_1 INT
, #Day_2 INT
, #Date1 DATE
, #Date2 DATE
SET #Date1 = CASE WHEN #Date_1 > #Date_2 THEN #Date_2 ELSE #Date_1 END
SET #Date2 = CASE WHEN #Date_1 > #Date_2 THEN #Date_1 ELSE #Date_2 END
SET #Month_1 = CASE WHEN DATEPART(MM, #Date2) > DATEPART(MM, #Date1) THEN DATEPART(MM, #Date1) ELSE DATEPART(MM, #Date2) END
SET #Month_2 = CASE WHEN DATEPART(MM, #Date1) > DATEPART(MM, #Date2) THEN DATEPART(MM, #Date1) ELSE DATEPART(MM, #Date2) END
SET #Day_1 = CASE WHEN DATEPART(DD, #Date2) > DATEPART(DD, #Date1) THEN DATEPART(DD, #Date1) ELSE DATEPART(DD, #Date2) END
SET #Day_2 = CASE WHEN DATEPART(DD, #Date1) > DATEPART(DD, #Date2) THEN DATEPART(DD, #Date1) ELSE DATEPART(DD, #Date2) END
-- SELECT #Date1, #Date2
IF #Compare_Year = 'no'
BEGIN
;WITH C AS (SELECT ID
, Name
, DATEPART(DD, DOB) AS Day
, DATEPART(MM, DOB) AS Month
FROM #table_E)
SELECT ID, Name, #Date1, #Date2
FROM C
WHERE C.Month >= #MONTH_1
AND C.Month <= #Month_2
AND C.Day >= #Day_1
AND C.DAy <= #Day_2
END
IF #Compare_Year = 'yes'
BEGIN
SELECT ID, Name, DOB
FROM #table_E
WHERE DOB <= #Date2
AND DOB >= #Date1
END
ELSE
PRINT WHAT! FOLLOW THE RULES YOU FOOL!!!
END
jk. that last part about fools is probably not included in your final draft. ;)
I would make it very simple not so much complex.
SELECT * FROM EMPLOYEE WHERE DOB >= '1970-01-01' AND DOB <= '1980-02-27'
This filters all dates between these two dates

How to calculate age (in years) based on Date of Birth in SQL

I want to get the 1st of the month following the date the person turns 70. How to achive this in SQL
i am calculating age using the formula
DECLARE #Date_of_birth DATETIME = '1915-10-02 00:00:00.000'
DECLARE #AGE INT
SELECT #AGE = FLOOR((CAST (GETDATE() AS INTEGER) - CAST(#Date_of_birth AS INTEGER)) / 365.25)
IF(#AGE > 70)
How to find the first of the month following the date ??
IF (#AGE >80)
You can use datediff to calculate their age, and then date add to find their 70th birthday. To find the first of the month afterwards, you can use the Month and Year functions.
create table #people (name varchar(30), birthdate date)
insert into #people
values ('Bob', '07/08/1976'), ('Tasha','05/30/1996'),('April','04/01/1971')
--This will give you everyone's age
select DATEDIFF(YY,birthdate,GETDATE()) as age
from #people
--This will give you the first month following the date that they turn 70
select Name, DATEADD(yy,70,birthdate) as [70thBday], convert(varchar,month(dateadd(m,1,DATEADD(yy,70,birthdate)))) + '/01/' + convert(varchar,YEAR(dateadd(m,1,DATEADD(yy,70,birthdate))))
from #people
declare #dob datetime = '1954-06-08'
declare #age int = 70
select DATEADD(m, DATEDIFF(m, -1, DATEADD(yy, #age, #dob)), 0)
you can use dateadd function like this
select dateadd(d,1 - datepart(d,dateadd(m,1,dateadd(yy,70",,#Date_of_birth)),dateadd(m,1,dateadd(yy,70",,#Date_of_birth)))
You can break down this query like this
declare #BD_70 date = dateadd(yy,70",,#Date_of_birth)
declare #Nxt_Month_70 = dateadd(m,1,#BD_70 date)
declare #First_Of_Month_70 = dateadd(d,1 - datepart(d,#Nxt_Month_70),#Nxt_Month_70)
Basically, you add 70 year, find next month and replace the day to the first of the month.
Here is how you would calculate age base on current date.
select case
when cast(getdate() as date) = cast(dateadd(year, (datediff(year, '1996-09-09', getdate())), '1996-09-09') as date)
then dateDiff(yyyy,'1996-09-09',dateadd(year, 0, getdate()))
else dateDiff(yyyy,'1996-09-09',dateadd(year, -1, getdate()))
end as MemberAge
go
This is how I calculate the age:
SELECT DATEDIFF(yy, [DateOfBirth], GETDATE()) + (CASE WHEN DATEPART(MONTH, GETDATE()) - DATEPART(MONTH, [DateOfBirth]) < 0 THEN -1 ELSE 0 END ) AS Age FROM [User]
You can try TIMESTAMPDIFF function.(Support MySQL, Apache Doris)
Example:
select TIMESTAMPDIFF(year,'1996-09-09',now()) as age;

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.

Age Calculation Query [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to calculate age in T-SQL with years, months, and days
calculating age from sysdate and birthdate using SQL Server
In the process of some age calculations. I'm a bit of a novice when it comes to SQL but I've been tasked with singling out persons who will turn 65yrs old as of 31 March 2013. So far I'm able to calculate age based on given DOB, however I'm having issues singling out these persons. I'm thinking this is a simple issue but I'm stumped, see sql statement. Can someone please point me in the right direction.
SELECT ip_master.ssn, ip_master.firstname, ip_master.surname, ip_master.status,
CASE WHEN DATEADD(YEAR, DATEDIFF (YEAR, dob, '2013/03/31'), dob) > '2013/03/31'
THEN DATEDIFF(YEAR, dob, '2013/03/31') - 1
ELSE DATEDIFF(YEAR, dob, '2013/03/31')
END AS 'Age'
FROM test_db.dbo.ip_master ip_master
Append a WHERE clause to your statement:
...
WHERE dob = '1948-03-31'
Would the following do? I just buried your query inside another query and applied restriction on age.
select * from
(
SELECT ip_master.ssn, ip_master.firstname, ip_master.surname, ip_master.status,
CASE WHEN DATEADD(YEAR, DATEDIFF (YEAR, dob, '2013/03/31'), dob) > '2013/03/31'
THEN DATEDIFF(YEAR, dob, '2013/03/31') - 1
ELSE DATEDIFF(YEAR, dob, '2013/03/31')
END AS 'Age'
FROM test_db.dbo.ip_master ip_master
)
where Age >= 65
Try the Below Query... it will work perfectly...
DECLARE #CalDate datetime;
DECLARE #CurDate datetime;
Declare #count INT
SET #CalDate ='03/31/2013'
SET #count =0
SELECT #CurDate = DateAdd(Month, 1, #CalDate)
SET #count = #count + datepart(dd,dateadd(dd,-1,dateadd(mm,1,cast(cast(year(#CurDate) as varchar)+'-'+cast(month(#CurDate) as varchar)+'-01' as datetime))))
SELECT ip_master.ssn, ip_master.firstname, ip_master.surname, ip_master.status,DATEDIFF (yy, DOB, cast(#CalDate as DateTime) + #count) - (
CASE SIGN (MONTH (DOB) - MONTH (cast(#CalDate as DateTime) + #count))
WHEN 1 THEN
1
WHEN -1 THEN
0
WHEN 0 THEN
CASE SIGN (DAY (DOB) - DAY (#CalDate + #count))
WHEN 1 THEN
1
WHEN 0 THEN
1
ELSE
0
END
END) as AGE into #temp
FROM test_db.dbo.ip_master ip_master
select * from #temp where AGE >= 65
Whatever you have done is correct.
For singling out you need a where clause.
SELECT ip_master.ssn, ip_master.firstname, ip_master.surname, ip_master.status,
CASE WHEN DATEADD(YEAR, DATEDIFF (YEAR, dob, '2013/03/31'), dob) > '2013/03/31'
THEN DATEDIFF(YEAR, dob, '2013/03/31') - 1
ELSE DATEDIFF(YEAR, dob, '2013/03/31')
END AS 'Age'
FROM test_db.dbo.ip_master ip_master
WHERE dob = '1948-03-31'