2013-06-28 1 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 «s день рождения в 2014 году как 2/28/2014 и на 2016 год как 2/29/2016.

Кроме того, если вы находитесь в настроении, мои полные намерения являются следующие:

Я хочу передать 2 даты, не имеет значения, как далеко друг от друга: @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

EDIT: 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

ну, в любом случае, это будет полная проверка, если вы не создадите специальные поля, такие как месяц, а make и индекс на 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 Отличная работа! Я предпочитаю метод CTE, как описано @ RomanPekar – 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