Вопрос

I have a table with employee birthdays. I'm trying to create a stored procedure that returns everyone's birthdays within 2 given dates. We have employees born on a leap year.

I can successfully get return a person when their birthday falls on a leap year following the example at 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

However, with the data above, my goal is to display Alex Black's birthday for year 2014 as 2/28/2014 and for year 2016 as 2/29/2016.

Also, if you are in the mood, my full intentions are the following:

I want to pass 2 dates, not matter how far apart: @DateFrom date = '1/1/2014' and @DateTo date = '12/31/2016'. The result I want back is

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

you can try this

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 remind me a great way to calculate birthday, so here's edited version

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

Другие советы

Here is a way:

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

Of course, you don't need to create that @Years table everytime, I suggest you create a calendar table with this information.

Results:

╔════════════╦═════════════════════════╦═════════════════════════╗
║  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 ║
╚════════════╩═════════════════════════╩═════════════════════════╝
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 
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top