SQL Query Question: Working with Dates

  • Greetings everyone, I will do my best to explain my issue. I apologize if I am missing any required information, I am very new to writing SQL Queries.

    I am currently running SQL Server 2008 R2 and I will be inserting this query into a SSRS Report to be published in a SharePoint 2010 Environment.

    The goal is to be able to forecast incoming payments from a table that contains a payment schedule. Here is a sample of the information contained in the table:

    SELECT

    CONVERT(DATE, '2012-02-01', 111) AS 'Posting Date'

    ,'131224' AS 'Customer ID'

    ,'John Smith' AS 'Customer Name'

    ,CONVERT(MONEY, '25') AS 'Amount'

    ,NULL AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-02-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-02-15', 111) AS 'Posting Date'

    ,'213234' AS 'Customer ID'

    ,'George Graham' AS 'Customer Name'

    ,CONVERT(MONEY, '5') AS 'Amount'

    ,CONVERT(DATE,'2014-10-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-02-18', 111) AS 'Posting Date'

    ,'100455' AS 'Customer ID'

    ,'Janet Cambridge' AS 'Customer Name'

    ,CONVERT(MONEY, '25') AS 'Amount'

    ,NULL AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-02-20', 111) AS 'Posting Date'

    ,'131224' AS 'Customer ID'

    ,'Mary Tyler' AS 'Customer Name'

    ,CONVERT(MONEY, '200') AS 'Amount'

    ,CONVERT(DATE,'2012-05-20', 111) AS 'End Date'

    Looking at my sample table above, if I were to filter the query results on "Bob Parkett", I would hope to see the following result: (NOTE: 1 Row Per Month is Expected)

    SELECT

    CONVERT(DATE, '2012-02-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-03-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-04-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-05-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-06-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-07-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    UNION ALL

    SELECT

    CONVERT(DATE, '2012-08-15', 111) AS 'Posting Date'

    ,'331125' AS 'Customer ID'

    ,'Bob Parkett' AS 'Customer Name'

    ,CONVERT(MONEY, '10') AS 'Amount'

    ,CONVERT(DATE, '2012-08-15', 111) AS 'End Date'

    To be honest, I have no idea where to start, or if it is even possible. Would anyone be able to offer any suggestions?

    Thank you so much in advance!

  • So, you want to look at the number of months between posting date and end date and basically copy data for each month remaining except that posting date should be incremented? Is this correct? Is this for 1 person at a time (WHERE CustomerID = 123456) or do you want to generate it for many customers at a time?

    Also, please see the link in my signature about posting questions to get the best help.

    Jared
    CE - Microsoft

  • Hi Jared, thank you so much for your quick reply.

    So, you want to look at the number of months between posting date and end date and basically copy data for each month remaining except that posting date should be incremented? Is this correct?

    You are correct. The "day" portion of the "date" would be the same while the month would increase as long as it falls within the "End Date" range. (Example: "2012-02-01" would have "2012-03-01", "2012-04-01", "2012-05-01", etc.)

    Is this for 1 person at a time (WHERE CustomerID = 123456) or do you want to generate it for many customers at a time?

    This report would run for many people at a time. "Expected Results" table from my Original Post I was just showing one customer to get a better idea of the expected results. Ideally this result would have a row for every month <= [End Date] and I could isolate one Customer from the table by using a Parameter on the SSRS Report.

    Thanks again!

  • Also, I took a look at the post in your signature about best practices for asking question on these forums. Perhaps I am missing something though, I tried to provide example data and expected results in a format that wouldn't require any formatting.

    Is there something particular that I can do to make it easier for others to respond to my post?

    Thanks in advance for the advice!

  • krhind (3/2/2012)


    Also, I took a look at the post in your signature about best practices for asking question on these forums. Perhaps I am missing something though, I tried to provide example data and expected results in a format that wouldn't require any formatting.

    Is there something particular that I can do to make it easier for others to respond to my post?

    Thanks in advance for the advice!

    USE [tempdb]

    GO

    IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[tmptab]') AND type in (N'U'))

    DROP TABLE [dbo].[tmptab]

    GO

    CREATE TABLE [dbo].[tmptab](

    [Posting Date] [date] NULL,

    [Customer ID] [varchar](6) NOT NULL,

    [Customer Name] [varchar](15) NOT NULL,

    [Amount] [money] NULL,

    [End Date] [date] NULL

    )

    INSERT INTO [dbo].[tmptab]([Posting Date], [Customer ID], [Customer Name], [Amount], [End Date])

    SELECT '20120201', N'131224', N'John Smith', 25.0000, NULL UNION ALL

    SELECT '20120215', N'331125', N'Bob Parkett', 10.0000, '20120815' UNION ALL

    SELECT '20120215', N'213234', N'George Graham', 5.0000, '20141015' UNION ALL

    SELECT '20120218', N'100455', N'Janet Cambridge', 25.0000, NULL UNION ALL

    SELECT '20120220', N'131224', N'Mary Tyler', 200.0000, '20120520'

    select * from tmptab

    ________________________________________________________________
    you can lead a user to data....but you cannot make them think
    and remember....every day is a school day

  • You have to loop through the dates, personal preference is a WHILE loop. below is a stored procedure where you can pass the string all or a specific customer id and get the results. Also where you see #tmptab is where your customer table would go.

    CREATE PROCEDURE usp_FuturePayments (@CustomerID VARCHAR(20)) AS

    SET NOCOUNT ON

    DECLARE @tempID Table (ID INT) -- Could also be temp table

    DECLARE @SortT TABLE (

    CustomerID varchar(6) NOT NULL,

    CustomerName varchar(15) NOT NULL,

    Amount money NULL,

    Date SMALLDATETIME NULL

    )

    DECLARE @CustID INT

    DECLARE @Count INT

    DECLARE @EndDate DATETIME

    IF @CustomerID = 'ALL' -- can use ALL for every customer or a speciffic customer ID

    BEGIN

    Insert INTO @tempID SELECT CustomerID from #tmptab -- making a table we can delete from with out touching the original data

    END

    ELSE

    INSERT INTO @tempID SELECT @CustomerID

    WHILE (SELECT COUNT(*) from @tempID) > 0 -- while there is something our temp table

    BEGIN -- while loop

    SET @CustID = (SELECT TOP 1 ID FROM @tempID) -- Get the first customer ID out of our temp table

    SET @EndDate = (Select CASE WHEN EndDate IS NULL THEN DATEADD(Year,1,GETDATE()) ELSE EndDate END -- Deal with the Null end dates

    FROM #tmptab where CustomerID = @CustID)

    SET @Count = DATEDIFF(MONTH,GETDATE(),@EndDate) -- How many times do we have to add to the date

    WHILE @Count > 0 -- While there is still counting to be done can set it to

    BEGIN -- While Count

    INSERT INTO @SortT -- insert into another temp table so we can sort it all nice when its done

    SELECT CustomerID, CustomerName, Amount, DATEADD(MONTH,(-1*@Count),@EndDate) -- subtract count from end date

    FROM #tmptab

    WHERE CustomerID = @CustID

    SET @Count = @Count - 1 -- change the count so we can end the loop when we are done

    END -- While Count

    DELETE FROM @tempID where ID = @CustID -- delete the customer id from our temp table so we process each one

    END -- While Loop

    Select CustomerID,CustomerName,Amount, CONVERT(DATE,date,111) from @SortT ORDER BY Date, CustomerName -- display our output

    GO

    The only thing im not sure on is if you could call a SP from SSRS. if not you may need to modify the above a bit.


    For faster help in answering any problems Please read How to post data/code on a forum to get the best help - Jeff Moden[/url] for the best way to ask your question.

    For performance Issues see how we like them posted here: How to Post Performance Problems - Gail Shaw[/url]

    Need to Split some strings? Jeff Moden's DelimitedSplit8K[/url]
    Jeff Moden's Cross tab and Pivots Part 1[/url]
    Jeff Moden's Cross tab and Pivots Part 2[/url]

  • capn.hector (3/2/2012)


    You have to loop through the dates, personal preference is a WHILE loop. below is a stored procedure where you can pass the string all or a specific customer id and get the results. Also where you see #tmptab is where your customer table would go.

    CREATE PROCEDURE usp_FuturePayments (@CustomerID VARCHAR(20)) AS

    SET NOCOUNT ON

    DECLARE @tempID Table (ID INT) -- Could also be temp table

    DECLARE @SortT TABLE (

    CustomerID varchar(6) NOT NULL,

    CustomerName varchar(15) NOT NULL,

    Amount money NULL,

    Date SMALLDATETIME NULL

    )

    DECLARE @CustID INT

    DECLARE @Count INT

    DECLARE @EndDate DATETIME

    IF @CustomerID = 'ALL' -- can use ALL for every customer or a speciffic customer ID

    BEGIN

    Insert INTO @tempID SELECT CustomerID from #tmptab -- making a table we can delete from with out touching the original data

    END

    ELSE

    INSERT INTO @tempID SELECT @CustomerID

    WHILE (SELECT COUNT(*) from @tempID) > 0 -- while there is something our temp table

    BEGIN -- while loop

    SET @CustID = (SELECT TOP 1 ID FROM @tempID) -- Get the first customer ID out of our temp table

    SET @EndDate = (Select CASE WHEN EndDate IS NULL THEN DATEADD(Year,1,GETDATE()) ELSE EndDate END -- Deal with the Null end dates

    FROM #tmptab where CustomerID = @CustID)

    SET @Count = DATEDIFF(MONTH,GETDATE(),@EndDate) -- How many times do we have to add to the date

    WHILE @Count > 0 -- While there is still counting to be done can set it to

    BEGIN -- While Count

    INSERT INTO @SortT -- insert into another temp table so we can sort it all nice when its done

    SELECT CustomerID, CustomerName, Amount, DATEADD(MONTH,(-1*@Count),@EndDate) -- subtract count from end date

    FROM #tmptab

    WHERE CustomerID = @CustID

    SET @Count = @Count - 1 -- change the count so we can end the loop when we are done

    END -- While Count

    DELETE FROM @tempID where ID = @CustID -- delete the customer id from our temp table so we process each one

    END -- While Loop

    Select CustomerID,CustomerName,Amount, CONVERT(DATE,date,111) from @SortT ORDER BY Date, CustomerName -- display our output

    GO

    The only thing im not sure on is if you could call a SP from SSRS. if not you may need to modify the above a bit.

    First, you do not need a while loop for this. Try to avoid those whenever you can, and I believe in this case you most certainly can. I will explain below. Second, J showed above what we really need. Table structures and the code to populate it with the sample data. The way you wanted the results I think was a great way to post it, because most people try to display sets of data in these forums with text and it is really hard to understand, so thanks for that.

    Avoid a while loop by developing a TVF (table valued function). This way you can treat each row as an input to the function that will output a table of values according to the function. Are you familiar with TVFs?

    Jared
    CE - Microsoft

  • i am not familliar with TVF's off to the all mighty google to eliminate my while loop


    For faster help in answering any problems Please read How to post data/code on a forum to get the best help - Jeff Moden[/url] for the best way to ask your question.

    For performance Issues see how we like them posted here: How to Post Performance Problems - Gail Shaw[/url]

    Need to Split some strings? Jeff Moden's DelimitedSplit8K[/url]
    Jeff Moden's Cross tab and Pivots Part 1[/url]
    Jeff Moden's Cross tab and Pivots Part 2[/url]

  • SQL KnowItAll thanks for pointing out the TVF's made this alot more efficient. krhind, Here is the function followed by the query that will produce your requested output. to limit by a speciffic customer id just add a where clause to the query.

    CREATE FUNCTION udf_DateInc (@CustID INT,

    @CustName VARCHAR(64),

    @Payment MONEY,

    @EndDate Date)

    RETURNS @SortTable TABLE

    (

    CustomerID varchar(6) NOT NULL,

    CustomerName varchar(15) NOT NULL,

    Amount money NULL,

    PayDate date NULL

    )

    AS

    BEGIN

    DECLARE @Count INT

    IF @EndDate IS NULL

    BEGIN

    SET @EndDate = DATEADD(YEAR,1,GETDATE())

    END

    SET @Count = DATEDIFF(Month, GetDate(), @EndDate)

    WHILE (@Count > 0)

    BEGIN

    INSERT INTO @SortTable

    SELECT @CustID, @CustName, @Payment, DATEADD(MONTH,(-1*@Count),@EndDate)

    SET @Count = @Count - 1

    END

    RETURN

    END

    GO

    SELECT p.CustomerID,p.CustomerName,p.Amount,d.paydate

    FROM DateProblem p

    CROSS APPLY udf_DateInc(p.CustomerID, P.CustomerName,p.Amount,p.EndDate) d

    ORDER BY d.PayDate


    For faster help in answering any problems Please read How to post data/code on a forum to get the best help - Jeff Moden[/url] for the best way to ask your question.

    For performance Issues see how we like them posted here: How to Post Performance Problems - Gail Shaw[/url]

    Need to Split some strings? Jeff Moden's DelimitedSplit8K[/url]
    Jeff Moden's Cross tab and Pivots Part 1[/url]
    Jeff Moden's Cross tab and Pivots Part 2[/url]

  • Hey Hector,

    I apologize for the late reply. Just wanted to say thanks for the code you wrote up for me. I had to wait to get the necessary permissions to create functions in our database. This looks like it will work great.

    Really appreciate your help as well Jared.

    Kyle

  • capn.hector (3/4/2012)


    SQL KnowItAll thanks for pointing out the TVF's made this alot more efficient. krhind, Here is the function followed by the query that will produce your requested output. to limit by a speciffic customer id just add a where clause to the query.

    CREATE FUNCTION udf_DateInc (@CustID INT,

    @CustName VARCHAR(64),

    @Payment MONEY,

    @EndDate Date)

    RETURNS @SortTable TABLE

    (

    CustomerID varchar(6) NOT NULL,

    CustomerName varchar(15) NOT NULL,

    Amount money NULL,

    PayDate date NULL

    )

    AS

    BEGIN

    DECLARE @Count INT

    IF @EndDate IS NULL

    BEGIN

    SET @EndDate = DATEADD(YEAR,1,GETDATE())

    END

    SET @Count = DATEDIFF(Month, GetDate(), @EndDate)

    WHILE (@Count > 0)

    BEGIN

    INSERT INTO @SortTable

    SELECT @CustID, @CustName, @Payment, DATEADD(MONTH,(-1*@Count),@EndDate)

    SET @Count = @Count - 1

    END

    RETURN

    END

    GO

    SELECT p.CustomerID,p.CustomerName,p.Amount,d.paydate

    FROM DateProblem p

    CROSS APPLY udf_DateInc(p.CustomerID, P.CustomerName,p.Amount,p.EndDate) d

    ORDER BY d.PayDate

    You can change your TVF from a multi-statment TVF to an Inline TVF and improve the performance. You don't need a loop or any of the setup code for the loop.

  • Here is a rewrite of the TVF as an inline TVF without a loop:

    IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[udf_DateInc]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))

    DROP FUNCTION [dbo].[udf_DateInc]

    GO

    CREATE FUNCTION dbo.udf_DateInc (@CustID INT,

    @CustName VARCHAR(64),

    @Payment MONEY,

    @EndDate DATETIME)

    RETURNS TABLE WITH SCHEMABINDING AS

    RETURN

    WITH E1(N) AS (

    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL

    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1

    ), --10E+1 or 10 rows

    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows

    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max

    cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front

    -- for both a performance gain and prevention of accidental "overruns"

    SELECT TOP (DATEDIFF(MONTH, GETDATE(), ISNULL(@ENDDATE, DATEADD(YEAR, 1, GETDATE()))))

    ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4

    )

    SELECT

    @CustID CustomerID,

    @CustName CustomerName,

    @Payment Amount,

    DATEADD(MONTH,(-1*(DATEDIFF(MONTH, GETDATE(), ISNULL(@ENDDATE, DATEADD(YEAR, 1, GETDATE()))) - N)),ISNULL(@ENDDATE, DATEADD(YEAR, 1, GETDATE()))) PayDate

    FROM cteTally

    ;

    GO

  • Lynn Pettis (3/13/2012)


    Here is a rewrite of the TVF as an inline TVF without a loop:

    looking at your code the only thing i dont understand (after looking at it and smacking my self for not knowing about row_number()) is the order by (Select NULL).

    does this just say to add a row number in whatever order they come back since in this case its just returning 1's?


    For faster help in answering any problems Please read How to post data/code on a forum to get the best help - Jeff Moden[/url] for the best way to ask your question.

    For performance Issues see how we like them posted here: How to Post Performance Problems - Gail Shaw[/url]

    Need to Split some strings? Jeff Moden's DelimitedSplit8K[/url]
    Jeff Moden's Cross tab and Pivots Part 1[/url]
    Jeff Moden's Cross tab and Pivots Part 2[/url]

  • You could probably change null to 1 and get the same effect. It is generating an increasing value (row_number()) for however many rows are being generated for the dynamic tally table.

  • after playing with it in SSMS and figuring out what most of the code did that was the only part i was wondering about. i joined up here to further me on my way to a self taught DBA. im currently in a job where i can slowly wade into the ocean as i learn more. i know enough that i can get functionally correct code but realize i have allot to learn to get efficient code.


    For faster help in answering any problems Please read How to post data/code on a forum to get the best help - Jeff Moden[/url] for the best way to ask your question.

    For performance Issues see how we like them posted here: How to Post Performance Problems - Gail Shaw[/url]

    Need to Split some strings? Jeff Moden's DelimitedSplit8K[/url]
    Jeff Moden's Cross tab and Pivots Part 1[/url]
    Jeff Moden's Cross tab and Pivots Part 2[/url]

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

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