Function to convert a datetime value into a harvest season year

  • Hi guys,

    I'm from a vba background and I'm looking to use an SQL function to convert a datetime value into a harvest season year. The harvest season runs from the beginning of July to the end of June the following year. So for example 2016-06-15 10:12:00 would return 2015, 2016-07-15 10:12:00 would return 2016 and 2017-03-12 14:30:00 should return 2016.

    In VBA I'd use something like this...

    Function GetHarvestYear(DateTimeInput As Variant) As Integer

    Dim intHarvestMonth As Integer

    intHarvestMonth = Month(DateTimeInput)

    Select Case intHarvestMonth

    Case 7 To 12

    GetHarvestYear = Year(DateTimeInput)

    Case Else

    GetHarvestYear = Year(DateTimeInput) - 1

    End Select

    End Function

    Can someone give me a starter on how to do this as an SQL function.

    Thank you!

  • something like this:

    declare @TestDate datetime;

    set @TestDate = '2016-06-15 10:12:00';

    select year(dateadd(year,datediff(year,0,dateadd(month,-6,@TestDate)),0));

    set @TestDate = '2016-07-15 10:12:00';

    select year(dateadd(year,datediff(year,0,dateadd(month,-6,@TestDate)),0));

    set @TestDate = '2017-03-12 14:30:00';

    select year(dateadd(year,datediff(year,0,dateadd(month,-6,@TestDate)),0));

  • I wouldn't do this in an SQL function. I'd put it as a column in a Calendar table and have it pre-calculated and queriable.

    Use Calendar tables for this kind of thing. It's faster that doing the math at runtime, since you only calculate it once, not once per query-row.

    - Gus "GSquared", RSVP, OODA, MAP, NMVP, FAQ, SAT, SQL, DNA, RNA, UOI, IOU, AM, PM, AD, BC, BCE, USA, UN, CF, ROFL, LOL, ETC
    Property of The Thread

    "Nobody knows the age of the human race, but everyone agrees it's old enough to know better." - Anon

  • But do NOT DO THIS AS A SCALAR UDF IN SQL SERVER!!! They are HORRIBLY BAD!!!!!!!!!

    If you cannot simply inline the code into your TSQL (best), then do this as an INLINE TABLE VALUED FUNCTION and use CROSS/OUTER APPLY.

    Best,
    Kevin G. Boles
    SQL Server Consultant
    SQL MVP 2007-2012
    TheSQLGuru on googles mail service

  • GSquared (6/14/2016)


    I wouldn't do this in an SQL function. I'd put it as a column in a Calendar table and have it pre-calculated and queriable.

    Use Calendar tables for this kind of thing. It's faster that doing the math at runtime, since you only calculate it once, not once per query-row.

    If I were to write an actual function I'd write it as an iTVF and call it in a CROSS APPLY in the FROM clause.

  • I expect that Lynn's option can be simplified without errors.

    DECLARE @Sample TABLE(

    SomeDate datetime,

    HarvestYr int

    );

    INSERT INTO @Sample

    VALUES

    ('2016-06-15T10:12:00', 2015),

    ('2016-07-15T10:12:00', 2016),

    ('2017-03-12T14:30:00', 2016);

    SELECT *, YEAR( DATEADD( MM, -6, SomeDate))

    FROM @Sample;

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • Luis Cazares (6/14/2016)


    I expect that Lynn's option can be simplified without errors.

    DECLARE @Sample TABLE(

    SomeDate datetime,

    HarvestYr int

    );

    INSERT INTO @Sample

    VALUES

    ('2016-06-15T10:12:00', 2015),

    ('2016-07-15T10:12:00', 2016),

    ('2017-03-12T14:30:00', 2016);

    SELECT *, YEAR( DATEADD( MM, -6, SomeDate))

    FROM @Sample;

    Simplification is always welcome.

  • Thank you so much for the replies this is what I ended up doing as a Scalar-Valued Function

    CREATE FUNCTION [dbo].[GetHarvestYear] (@InputDate datetime)

    RETURNS int

    AS

    BEGIN

    DECLARE @ret int;

    SELECT @ret = year(dateadd(year,datediff(year,0,dateadd(month,-6,@InputDate)),0));

    IF (@ret IS NULL)

    SET @ret = 0;

    RETURN @ret;

    END;

    and I call it like this ...

    SELECT GetHarvestYear([ArrivalDateTime]) as HarvestYear

    FROM [tblFuelFlow]

    Your help was greatly appreciated!

  • joe-584802 (6/14/2016)


    Thank you so much for the replies this is what I ended up doing as a Scalar-Valued Function

    CREATE FUNCTION [dbo].[GetHarvestYear] (@InputDate datetime)

    RETURNS int

    AS

    BEGIN

    DECLARE @ret int;

    SELECT @ret = year(dateadd(year,datediff(year,0,dateadd(month,-6,@InputDate)),0));

    IF (@ret IS NULL)

    SET @ret = 0;

    RETURN @ret;

    END;

    and I call it like this ...

    SELECT GetHarvestYear([ArrivalDateTime]) as HarvestYear

    FROM [tblFuelFlow]

    Your help was greatly appreciated!

    WOW. So even with the twelve or so exclamation points I put in my statements about Scalar UDFs being HORRIBLY BAD (plus another poster saying to not use them) you STILL went with an sUDF?!?

    Please see if you can find a copy of the SQL Server MVP Deep Dives 2 book and read my chapter there entitled "Death by UDF".

    Oh, and I note that your initial post didn't say anything about handling invalid data and returning a 0 for the YEAR if that was encountered.

    Best,
    Kevin G. Boles
    SQL Server Consultant
    SQL MVP 2007-2012
    TheSQLGuru on googles mail service

  • joe-584802 (6/14/2016)


    Thank you so much for the replies this is what I ended up doing as a Scalar-Valued Function

    CREATE FUNCTION [dbo].[GetHarvestYear] (@InputDate datetime)

    RETURNS int

    AS

    BEGIN

    DECLARE @ret int;

    SELECT @ret = year(dateadd(year,datediff(year,0,dateadd(month,-6,@InputDate)),0));

    IF (@ret IS NULL)

    SET @ret = 0;

    RETURN @ret;

    END;

    and I call it like this ...

    SELECT GetHarvestYear([ArrivalDateTime]) as HarvestYear

    FROM [tblFuelFlow]

    Your help was greatly appreciated!

    Better solution:

    create function dbo.Get_HarvestYear(@InputDate datetime)

    returns table with schemabinding return

    select year(dateadd(month, -6, @InputDate)) HarvestYear

    go

    Usage:

    SELECT

    HarvestYear

    FROM

    [tblFuelFlow]

    cross apply (select HarvestYear from dbo.Get_HarvestYear([ArrivalDateTime]);

  • Lynn Pettis (6/14/2016)


    joe-584802 (6/14/2016)


    Thank you so much for the replies this is what I ended up doing as a Scalar-Valued Function

    CREATE FUNCTION [dbo].[GetHarvestYear] (@InputDate datetime)

    RETURNS int

    AS

    BEGIN

    DECLARE @ret int;

    SELECT @ret = year(dateadd(year,datediff(year,0,dateadd(month,-6,@InputDate)),0));

    IF (@ret IS NULL)

    SET @ret = 0;

    RETURN @ret;

    END;

    and I call it like this ...

    SELECT GetHarvestYear([ArrivalDateTime]) as HarvestYear

    FROM [tblFuelFlow]

    Your help was greatly appreciated!

    Better solution:

    create function dbo.Get_HarvestYear(@InputDate datetime)

    returns table with schemabinding return

    select year(dateadd(month, -6, @InputDate)) HarvestYear

    go

    Usage:

    SELECT

    HarvestYear

    FROM

    [tblFuelFlow]

    cross apply (select HarvestYear from dbo.Get_HarvestYear([ArrivalDateTime]);

    Usage, modified to handle null:

    SELECT

    isnull(HarvestYear,0) HarvestYear

    FROM

    [tblFuelFlow]

    outer apply (select HarvestYear from dbo.Get_HarvestYear([ArrivalDateTime]);

  • Avoid that. User defined scalar functions in SQL Server are known for giving performance problems. They're slow and they prevent parallelism.

    EDIT: Took too long to reply, I just want to make clear that the comment is intended for joe (OP)

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • Thanks for the improvement Lynne, I'm guessing table valued functions are more efficient than in-line scalar valued functions?

  • I just tested a Calendar table version against the UDFs defined here.

    Calendar table was consistently about 10X faster.

    - Gus "GSquared", RSVP, OODA, MAP, NMVP, FAQ, SAT, SQL, DNA, RNA, UOI, IOU, AM, PM, AD, BC, BCE, USA, UN, CF, ROFL, LOL, ETC
    Property of The Thread

    "Nobody knows the age of the human race, but everyone agrees it's old enough to know better." - Anon

  • Hi Kevin, I didn't realise that there was such an issue in using a scalar function, the database that I'm using is not using huge record numbers so I thought it was a more simplistic approach. I thought to handle the null entry as an afterthought so I could flag it for another purpose. I do take all the comments on board and Lynne came back with an improved function that uses your suggestion.

    Appreciate your great advice and pointers, I do take all the comments on-board!!

Viewing 15 posts - 1 through 15 (of 21 total)

You must be logged in to reply to this topic. Login to reply