2013-06-28 96 views
2

我有一個包含員工生日的表格。我正在嘗試創建一個存儲過程,在給定的日期內返回所有人的生日。我們有員工在閏年出生。SQL Server:閏年的生日

我能順利拿到返回一個人的時候,他們的生日在http://www.berezniker.com/content/pages/sql/microsoft-sql-server/birthday-query-ms-sql-server

DECLARE @StartDate DATETIME, @EndDate DATETIME 

SET @StartDate = '2009-02-22' 
SET @EndDate = '2009-02-28' 

--SET @StartDate = '2008-02-22' 
--SET @EndDate = '2008-02-29' 

SELECT 
    FullName, 
    DATEPART(MONTH, dob) AS MONTH, 
    DATEPART(DAY, dob) AS DAY, 
    CONVERT(VARCHAR(10), dob, 111) AS dob 
FROM 
    People 
WHERE 
    DATEADD(YEAR, DATEDIFF(YEAR, dob, @StartDate), dob) BETWEEN @StartDate AND @EndDate 
OR 
    DATEADD(YEAR, DATEDIFF(YEAR, dob, @EndDate), dob) BETWEEN @StartDate AND @EndDate 
ORDER BY 
    CASE WHEN DATEADD(YEAR, DATEDIFF(YEAR, dob, @StartDate), dob) 
    BETWEEN @StartDate AND @EndDate THEN 1 ELSE 2 END, 
    DATEPART(MONTH, dob), DATEPART(DAY, dob) 


CREATE TABLE People 
    (
     PK INT IDENTITY(1,1) NOT NULL PRIMARY KEY, 
     FullName VARCHAR(30) NOT NULL, 
     dob DATETIME NULL 
    ) 
GO 

INSERT INTO People (FullName, dob) VALUES ('John Smith', '1965-02-28') 
INSERT INTO People (FullName, dob) VALUES ('Alex Black', '1960-02-29') 
INSERT INTO People (FullName, dob) VALUES ('Bill Doors', '1968-02-27') 
... 
--shortened for clarity 

但是落在下面的例子閏年,與上面的數據,我的目標是要顯示Alex Black的生日爲2014年如2/28/2014和2016年爲2/29/2016

另外,如果你的心情,我完全意向如下:

我想傳遞兩個日期,不管相隔多遠:@DateFrom date = '1/1/2014'@DateTo date = '12/31/2016'。我想後面的結果是

FULLNAME  DOB 
Bill Doors  2014-02-27 
John Smith  2014-02-28 
Alex Black  2014-02-28 
Bill Doors  2015-02-27 
John Smith  2015-02-28 
Alex Black  2015-02-28 
Bill Doors  2016-02-27 
John Smith  2016-02-28 
Alex Black  2016-02-29 -- note this year the date is feb 29th 

回答

2

你可以試試這個

declare 
    @DateFrom date = '20140101', 
    @DateTo date = '20161231' 

;with 
-- All years between @DateFrom and @DateTo 
CTE_Years as (
    select datepart(yy, @DateFrom) as y 
    union all 
    select y + 1 as y 
    from CTE_Years 
    where y < datepart(yy, @DateTo) 
), 
-- Calculate leap years 
CTE_Years2 as (
    select 
    cast(y as nvarchar(4)) as y, 
    case 
     when y/400 * 400 = y then 1 
     when y/100 * 100 = y then 0 
     when y/4 * 4 = y then 1 
     else 0 
    end as Is_Leap_Year 
    from CTE_Years 
), 
-- get peoples birth day and month in form 'mmdd' 
CTE_People as (
    select 
    FullName, 
    right(convert(nvarchar(8), dob, 112), 4) as dob 
    from People 
), 
-- get peoples birth date in given years 
CTE_DOB as (
    select 
    P.FullName, 
    convert(
     date, 
     Y.y + 
     case 
     when Y.Is_Leap_Year = 0 and P.dob = '0229' then '0228' 
     else P.dob 
     end, 
     112 
    ) as dob 
    from CTE_Years2 as Y 
    cross join CTE_People as P 
) 
-- Final query 
select * 
from CTE_DOB 
where dob > @DateFrom and dob < @DateTo 
order by DOB asc 

TAKE A LOOK AT SQL FIDDLE EXAMPLE

編輯:Lamak讓我想起了一個偉大的方式來計算的生日,所以這裏的編輯版本

declare 
    @DateFrom date = '1/1/2014', 
    @DateTo date = '12/31/2016' 

;with 
CTE_Years as (
    select dateadd(yy, datediff(yy, 0, @DateFrom), 0) as y 
    union all 
    select dateadd(yy, 1, y) as y 
    from CTE_Years 
    where y < @DateTo 
), 
CTE_DOB as (
    select 
    P.FullName, 
    dateadd(yy, datediff(yy, P.dob, Y.y), P.dob) as dob 
    from CTE_Years as Y 
     cross join People as P 
) 
select * 
from CTE_DOB 
where dob > @DateFrom and dob < @DateTo 
order by DOB asc 

SQL FIDDLE EXAMPLE

+0

謝謝@RomanPekar。像魅力一樣工作,喜歡小提琴的例子!例如,如果我們的數據庫中有100,000,000多人,並且我們正在搜索一個較小的時間範圍,比如說1個月,那麼'WHERE'子句不會放在'CTE_DOB'內部,所以我們不會交叉行?或者有更好的方法嗎? – RoLYroLLs

+0

好吧,無論如何,它會被全面掃描,除非你創建一個特殊的領域,如月,並建立和索引t –

+0

謝謝!我估計。 – RoLYroLLs

4

這裏有一個辦法:

DECLARE @StartDate DATETIME, @EndDate DATETIME, @I INT 

SET @StartDate = '20140101' 
SET @EndDate = '20161231' 
SET @I = 0 

DECLARE @Years TABLE(Years DATE) 


WHILE @I <= DATEDIFF(YEAR,@StartDate,@EndDate) 
BEGIN 
    INSERT INTO @Years 
    SELECT DATEADD(YEAR,DATEDIFF(YEAR,0,DATEADD(YEAR,@I,@StartDate)),0) 

    SET @I = @I + 1 
END 

SELECT B.FullName, 
     B.dob, 
     DATEADD(YEAR,DATEDIFF(YEAR,dob,Years),dob) BirthDay 
FROM @Years A 
CROSS JOIN People B 
WHERE DATEADD(YEAR,DATEDIFF(YEAR,dob,Years),dob) >= @StartDate 
AND DATEADD(YEAR,DATEDIFF(YEAR,dob,Years),dob) <= @EndDate 

當然,你並不需要創建@Years表每次,我建議你創建一個日曆表信息。

結果:

╔════════════╦═════════════════════════╦═════════════════════════╗ 
║ FullName ║   dob   ║  BirthDay   ║ 
╠════════════╬═════════════════════════╬═════════════════════════╣ 
║ John Smith ║ 1965-02-28 00:00:00.000 ║ 2014-02-28 00:00:00.000 ║ 
║ Alex Black ║ 1960-02-29 00:00:00.000 ║ 2014-02-28 00:00:00.000 ║ 
║ Bill Doors ║ 1968-02-27 00:00:00.000 ║ 2014-02-27 00:00:00.000 ║ 
║ John Smith ║ 1965-02-28 00:00:00.000 ║ 2015-02-28 00:00:00.000 ║ 
║ Alex Black ║ 1960-02-29 00:00:00.000 ║ 2015-02-28 00:00:00.000 ║ 
║ Bill Doors ║ 1968-02-27 00:00:00.000 ║ 2015-02-27 00:00:00.000 ║ 
║ John Smith ║ 1965-02-28 00:00:00.000 ║ 2016-02-28 00:00:00.000 ║ 
║ Alex Black ║ 1960-02-29 00:00:00.000 ║ 2016-02-29 00:00:00.000 ║ 
║ Bill Doors ║ 1968-02-27 00:00:00.000 ║ 2016-02-27 00:00:00.000 ║ 
╚════════════╩═════════════════════════╩═════════════════════════╝ 
+0

+1好方法計算生日,我已經改變了我的答案了一下,如果你不介意 –

+0

@RomanPekar沒問題,我不介意 – Lamak

+0

+1偉大的工作!我比較喜歡用@RomanPekar描述的CTE方法 – RoLYroLLs

0
DECLARE @bd DATE = '1960-02-29'; 

WITH years 
    AS (SELECT 
      * 
      FROM (VALUES (2013), 
         (2014), 
         (2015), 
         (2016)) AS x(y)) 
SELECT 
    y, DATEADD(year, y - DATEPART(year, @bd), @bd) 
FROM years