What is the best code (UDF) to calculate the age given birthday? Very optimized code. I can write the date related code just to make sure what best answer I get from best minds in the industry.
Would like to calculate based on days.
What is the best code (UDF) to calculate the age given birthday? Very optimized code. I can write the date related code just to make sure what best answer I get from best minds in the industry.
Would like to calculate based on days.
以下函数为我在此线程中评论的所有日期范围提供了非常合理的答案。您会注意到某些值可能会相差 1 天,但这是由于它们的长度不同而导致月份计算方式的伪影。二月总是给最麻烦的是最短的。但它不应该从航位推算开始超过 1 天(即使这归结为你应该如何计算月份的语义,仍然是一个有效的答案)。有时,它可能会给出您可能预期 1 个月的天数,但它仍然是一个合理的答案。
该函数直到最后只使用日期数学而不是字符串操作,当与返回类似准确计算的任何其他函数叠加时,该函数也应该产生非常好的性能。
CREATE FUNCTION dbo.AgeInYMDFromDates(
@FromDate datetime,
@ToDate datetime
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN (
SELECT
Convert(varchar(11), AgeYears) + 'y '
+ Convert(varchar(11), AgeMonths) + 'm '
+ Convert(varchar(11), DateDiff(day, DateAdd(month, AgeMonths,
DateAdd(year, AgeYears, @FromDate)), @ToDate)
) + 'd' Age
FROM (
SELECT
DateDiff(year, @FromDate, @ToDate)
- CASE WHEN Month(@FromDate) * 32 + Day(@FromDate)
> Month(@ToDate) * 32 + Day(@ToDate) THEN 1 ELSE 0 END AgeYears,
(DateDiff(month, @FromDate, @ToDate)
- CASE WHEN Day(@FromDate) > Day(@ToDate) THEN 1 ELSE 0 END) % 12 AgeMonths
) X
);
像这样使用(SQL 2008 脚本):
SELECT
FromDate,
ToDate,
ExpectedYears,
ExpectedMonths,
ExpectedDays,
(SELECT TOP 1 Age FROM dbo.AgeInYMDFromDates(FromDate, ToDate)) Age
FROM
(
VALUES
(Convert(datetime, '20120201'), Convert(datetime, '20120301'), 0, 1, 0),
('20120228', '20120328', 0, 1, 0),
('20120228', '20120331', 0, 1, 3),
('20120228', '20120327', 0, 0, 27),
('20120801', '20120802', 0, 0, 1),
('20120131', '20120301', 0, 1, 2),
('19920507', '20120506', 19, 11, 29),
('19920507', '20120507', 20, 0, 0)
) X (FromDate, ToDate, ExpectedYears, ExpectedMonths, ExpectedDays)
因为它是一个内联函数,所以它可以插入到查询的执行计划中,并且会尽可能地执行。如果将其转换为标量返回函数(因此您不必使用,(SELECT Age FROM func)
那么您将获得更差的性能。该WITH SCHEMABINDING
指令可以提供帮助,因为它预先计算出该函数不会对任何表进行数据访问,而不必在运行。
以下是上述执行的结果:
FromDate ToDate ExpectedYears ExpectedMonths ExpectedDays Age
---------- ---------- ------------- -------------- ------------ -----------
2012-02-01 2012-03-01 0 1 0 0y 1m 0d
2012-02-28 2012-03-28 0 1 0 0y 1m 0d
2012-02-28 2012-03-31 0 1 3 0y 1m 3d
2012-02-28 2012-03-27 0 0 27 0y 0m 28d
2012-08-01 2012-08-02 0 0 1 0y 0m 1d
2012-01-31 2012-03-01 0 1 2 0y 1m 1d
1992-05-07 2012-05-06 19 11 29 19y 11m 29d
1992-05-07 2012-05-07 20 0 0 20y 0m 0d
享受!
尝试这个。
CREATE TABLE patient(PatName varchar(100), DOB date, Age varchar(100))
INSERT INTO patient
VALUES ('d','06/02/2011',NULL)--,('b','07/10/1947',NULL),('c','12/21/1982',NULL)
;WITH CTE(PatName,DOB,years,months,days) AS
(SELECT PatName, DOB, DATEDIFF(yy,DOB,getdate()), DATEDIFF(mm,DOB,getdate()),
DATEDIFF(dd,DOB,getdate())
FROM patient)
SELECT PatName, DOB,
CAST(months/12 as varchar(5)) + ' Years' +
CAST((months % 12) as varchar(5)) + ' month/s ' +
CAST( CASE WHEN DATEADD(MM,(months % 12), DATEADD(YY,(months/12),DOB)) <= GETDATE()
THEN DATEDIFF(dd,DATEADD(MM,(months % 12), DATEADD(YY,(months/12),DOB)),GETDATE())
ELSE DAY(getdate())
END as varchar(5))+' days' AS Age
FROM CTE
标量函数的性能通常不是很好,按照你的要求我会把它写成一个表值函数。表值函数具有正确内联的好处。
其他形式的查询不会有很大的不同,因为它是对函数的调用占用了时间。
CREATE FUNCTION dbo.fn_age(@birthdate datetime, @current_date datetime)
RETURNS TABLE
WITH SCHEMABINDING
AS
return (
select CAST(DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END AS varchar(3)) + ' y, '
+ CAST(DATEDIFF(MONTH, DATEADD(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate),
@current_date) AS varchar(2)) + ' m, '
+ CAST(DATEDIFF(day, DATEADD(month
, datediff(MONTH, DATEADD(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate)
, @current_date)
, dateadd(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate)
)
, @current_date) AS varchar(2)) + ' d' as [Age]
)
GO
这个函数必须像这样调用:
SELECT Age = (SELECT Age FROM dbo.fn_age(birthDate, current_timestamp))
FROM Person
当把这个问题写成一个普通的标量函数时,我会创建这样的东西:
CREATE FUNCTION dbo.fn_age_slow(@birthdate datetime, @current_date datetime )
RETURNS VARCHAR(10)
WITH SCHEMABINDING
AS
begin
return CAST(DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END AS varchar(3)) + ' y, '
+ CAST(DATEDIFF(MONTH, DATEADD(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate),
@current_date) AS varchar(2)) + ' m, '
+ CAST(DATEDIFF(day, DATEADD(month
, datediff(MONTH, DATEADD(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate)
, @current_date)
, dateadd(year
, /* the year calculation*/ DATEDIFF(year, @birthdate, @current_date) - CASE WHEN((MONTH(@birthdate) * 100 + DAY(@birthdate)) > (MONTH(@current_date) * 100 + DAY(@current_date))) THEN 1 ELSE 0 END /* end of the year calculation*/
, @birthdate)
)
, @current_date) AS varchar(2)) + ' d'
end
GO
如您所见,它与表值函数完全相同。(它也是模式绑定,在某些情况下使函数更快)
针对第一个函数运行以下脚本时(在我的电脑上)
declare @a varchar(10) = ''
, @d datetime = '20120101'
, @i int = 1
, @begin datetime
select @begin = CURRENT_TIMESTAMP
while @i < 1000000
begin
select @a = Age
, @d = @begin - @i%1000
, @i += 1
from dbo.fn_age(@d, @begin)
end
select CURRENT_TIMESTAMP - @begin
GO
==> 00:00:07.920
declare @a varchar(10) = ''
, @d datetime = '19500101'
, @i int = 1
, @begin datetime
select @begin = CURRENT_TIMESTAMP
while @i < 1000000
begin
select @a = dbo.fn_age_slow(@d, @begin)
, @d = @begin - @i%1000
, @i += 1
end
select CURRENT_TIMESTAMP - @begin
==> 00:00:14.023
这绝不是一个适当的基准,但它应该让您了解性能差异。