Funktion för att testa om en händelse inträffar inom ett givet tidsintervall


Det finns flera fall där man vill ta reda på om någonting inträffar t.ex nästa dag, vecka eller månad. Det kan vara en födelsedag eller en annan högtidsdag.

För att kunna göra denna jämförelse behöver man tre datum; ett datum för när någonting inträffade och två datum som anger inom vilket tidsintervall man vill undersöka om händelsen kommer att inträffa igen.

Det finns en inbyggs funktion i SQL Server för att ta reda på en tidsdifferens mellan två datum och den heter DATEDIFF. Var bara beredd på att den inte uppför sig om man kan förvänta sig. Läs mer här http://www.sqlteam.com/article/datediff-function-demystified.

Kontentan är att om dagen för händelsen ligger senare i månaden än vad dagens datum är får man lägga till en extra månad för att kompensera för hur hjärnan fungerar och hur DATEDIFF fungerar.

Börja med att deklarera tre datum

DECLARE @DoB DATETIME,
        @FromDate DATETIME,
        @ToDate DATETIME;

För att kunna kontrollera om händelsen kommer att inträffa inom det givna tidsintervallet behöver vi en referenspunkt och två händelsedatum att jämföra med varandra. I vårt fall blir referenspunkten @DoB. Sedan får vi helt enkelt undersöka hur många hela månader som inträffat till den ena händelsedatumet @FromDate och dessutom hur många hela månader som inträffat till det andra händelsedatumet @ToDate.

CASE
        WHEN DATEPART(DAY, @DoB) >= DATEPART(DAY, @FromDate) THEN DATEDIFF(MONTH, @DoB, @FromDate) – 1
        ELSE DATEDIFF(MONTH, @DoB, @FromDate)
END / 12

Sedan gör du likadant, nästan, med det andra datumet. När jag säger nästan så är det för att om händelsedatumet ligger exakt på det andra datumet så får vi hantera det lite annorlunda.

CREATE FUNCTION dbo.fnHasAnniversary
(
    @DoB DATETIME,
    @FromDate DATETIME,
    @ToDate DATETIME
)
RETURNS INT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
    DECLARE @From INT,
            @To INT

    IF @FromDate > @ToDate
        SELECT  @From = CASE
                            WHEN DATEPART(DAY, @DoB) >= DATEPART(DAY, @ToDate) THEN DATEDIFF(MONTH, @DoB, @ToDate) – 1
 
                           ELSE DATEDIFF(MONTH, @DoB, @ToDate)
 
                       END / 12,
 
               @To = CASE
                          WHEN DATEPART(DAY, @DoB) > DATEPART(DAY, @FromDate) THEN DATEDIFF(MONTH, @DoB, @FromDate) – 1
                          ELSE DATEDIFF(MONTH, @DoB, @FromDate)
                      END / 12
    ELSE
        SELECT  @From = CASE
                            WHEN DATEPART(DAY, @DoB) >= DATEPART(DAY, @FromDate) THEN DATEDIFF(MONTH, @DoB, @FromDate) – 1
 
                           ELSE DATEDIFF(MONTH, @DoB, @FromDate)
 
                       END / 12,
 
               @To = CASE
                          WHEN DATEPART(DAY, @DoB) > DATEPART(DAY, @ToDate) THEN DATEDIFF(MONTH, @DoB, @ToDate) – 1
                          ELSE DATEDIFF(MONTH, @DoB, @ToDate)
                      END / 12

   
RETURN  NULLIF(@To, @From)
END

Det blir inte svårare än så här. Jag tänkte istället berätta om två kanske nya funktioner som inte är så kända. Den första är “RETURNS NULL ON NULL INPUT”. Det gör att funktion själv känner av att om en av parametrarna är NULL så exekveras ingen kod i funktionen alls, utan funktionen returnernerar NULL direkt.

Den andra funktionen är NULLIF. Den funktionen returnerar NULL om båda parametrarna är lika med varandra. I vårt fall passar det det, för om den första åldern är lika med den andra åldern så har ingen födelsedag inträffat och då returnerar funktionen NULL. Om de två variablerna däremot är olika varandra, t.ex om @From är 39 och @To är 40, så har personen fyllt 40 under det angivna tidsintervallet och då vill vi returnera 40 istället för 39. Då sätter vi @From före @To.

Funktionen är enkel att använda.

SELECT  Name,
        DateOfBirth,
        dbo.fnHasAnniversary(DateOfBirth, ‘20130603’, ‘20130609’) AS Age
FROM   dbo.Employees

Lycka till!

Have any Question or Comment?

Leave a Reply